diff --git a/Documentation/Doxygen/3-DeveloperManual/Concepts/Selection.dox b/Documentation/Doxygen/3-DeveloperManual/Concepts/Selection.dox index 562426f2f2..646405bd73 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Concepts/Selection.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Concepts/Selection.dox @@ -1,302 +1,302 @@ /** \page SelectionConceptPage Selection Concept \tableofcontents Selecting data nodes is frequently done when working with the MITK workbench. Most of the views in MITK require one or more selected data nodes that will be somehow processed by the underlying algorithms, such as image segmentation, image registration, basic image processing, image statistics computation etc.. In order to overcome some disadvantages of the old data node seletion concept, the following new selection concept has been implemented.
This page introduces the concept and the corresponding implementation. The document starts with a brief explanation of the disadvantages of the old concepts and justifies a need for the new concept. \section Migration Migration idea / background The old data node selection concept is a mixture of a \a global and a \a local selection concept: \subsection GlobalSelection Global selection The global selection was realized by the data manager view: Selecting one or more data nodes in the data manager view lead to a propagation of the selected nodes via the blueberry framework. Other views that derive from QmitkAbstractView received the new selection and processed it individually. All activated views were able to receive the new selection successively, thus it is called a \a global selection. \subsection LocalSelection Local selection The local selection was realized by Qt elements inside a view. An example of such an element is the QmitkDataStorageComboBox. This combo box contains the nodes from the data storage and allows to select one of it. A combo box can be included in a view multiple times. Additionally a node predicate can be set for a combo box so that only a filtered subset of all nodes in the data storage is contained in the combo box. A selection inside a combo box does not affect the selection of another combo box (in the same or another view), thus it is called a \a local selection.
Both concepts have reached their functional limit: -# the global selection does not offer the functionality to filter data nodes -# the global selection does not allow to select different data nodes in different views -# the selection order for multi-node selection might be important, which can be cumbersome to achieve via the data manager -# the local selection does not offer enough flexibility: -# it does not allow to propagate data node selections -# it does not allow multi node selection (multiple combo boxes are needed) -# it does not show the visibility / hierarchy of the data nodes The latest trend was to change the design of the view of the MITK workbench so that the local concept (using the QmitkDataStorageComboBox) was used wherever possible. This was done since the problems of the global selection concept where severe. In cases where the local concept was used positive feedback was received. \section NewSelectionConcept New selection concept The new selection concept is based on the idea of a local selection concept. This accounts for some fundamental requirements, such as individual data node selection for each view and data node filtering. Furthermore it clearly defines the responsibility of the data manager view: the data manager is only used to manage the data to render, e.g. defining the node hierarchy (rendering order) or setting the color or transparency of the pixel data. Additionally the new local selection concept is also able to overcome the problems of the previous local concept: It allows data node selection propagation, multi node selection and is able to show additional information such as data node hierarchy or node visibility. How this is achieved will be explained in the following sections. \subsection BasicIdea Basic idea The basic idea is to provide a set of classes that define the basic functionality of the new selection concept. These classes allow the use of existing and the development of new data storage inspectors or node selection widgets.
\imageMacro{selection_concept_class_diagram.png, "", 16}
Included in this concept is a set of basic data storage inspectors and node selection widgets that are frequently needed. A developer can extend these classes to create own data storage inspectors or node selection widgets to customize a plugins behavior and design. The following paragraphs further describe the most important classes: \b QmitkAbstractDataStorageModel The QmitkAbstractDataStorageModel extends the QAbstractItemModel to provide a custom item model for the data storage. This custom item model extends the Qt-model functionality in order to accept an mitk::DataStorage and an mitk::NodePredicateBase. The data storage is used to define on which data storage the model operates. In order to update the data storage model if the data storage changes, the QmitkAbstractDataStorageModel provides four pure virtual functions, QAbstractItemModel::DataStorageChanged, QAbstractItemModel::NodeAdded, QAbstractItemModel::NodeChanged and QAbstractItemModel::NodeRemoved. This abstract model calls the first function if the data storage has been reset using the SetDataStorage-function (i.e. to a new data storage or to a nullptr). The last three functions are connected to the corresponding events of the data storage (AddNodeEvent, ChangedNodeEvent and RemoveNodeEvent). The node predicate can be set and used to filter the data storage, so that only a subset of the contained data nodes is represented by the data storage model. In order to update the data storage model if the node predicate changes, the QmitkAbstractDataStorageModel provides a pure virtual function, QAbstractItemModel::NodePredicateChanged. This abstract model calls this function if the node predicate has been reset using the SetNodePredicate-function (i.e. to a new node predicate or to a nullptr). Any subclass of this abstract data storage model can define its own functionality by overriding the five corresponding functions. The abstract data storage model does not implement any Qt model functionality, so the functions of the QAbstractItemModel have to be overwritten in the subclasses, according to the model type (e.g. list model, table model). \b QmitkAbstractDataStorageInspector The QmitkAbstractDataStorageInspector serves as a base class that can be derived from to provide a custom view onto the data storage using a QmitkAbstractDataStorageModel derivative. This custom widget extends the QWidget functionality in order to accept an mitk::DataStorage and an mitk::NodePredicateBase. The data storage is used to define which data storage the widget should inspect. The node predicate can be set and later be used to filter the data storage, so that only a subset of the contained data nodes is inspected by the data storage inspector. The data storage and the node predicate can be set by the corresponding setter, which in turn calls the pure virtual function QmitkAbstractDataStorageInspector::Initialize. Any subclass of this abstract data storage inspector can now define its own functionality inside the Initialize-function to define what happens if the data storage or the node predicate is (re-)set. Typically an inspector will forward the data storage and the node predicate to the data storage model to make it aware of the data it should hold. Additionally a data storage inspector holds a QmitkModelViewSelectionConnector and initializes it with a QAbstractItemView derived class (e.g. a QListView) and the data storage model. The idea behind this connector-class will be explained in the next section. Furthermore the abstract class provides a QSignal, CurrentSelectionChanged(NodeList), that is emitted if the selection of the underlying data storage model has changed. The abstract class also provides two QSlots, SetSelectOnlyVisibleNodes(bool) and SetCurrentSelection(NodeList). Calling these slots will forward the given arguments to the model view selection connector member which in turn can manipulate the data storage models selection. \b QmitkModelViewSelectionConnector The QmitkModelViewSelectionConnector is used to transform a model selection into a list of selected data nodes and vice versa. The class accepts a QAbstractItemView with a corresponding QmitkAbstractDataStorageModel that operates on a data storage. The connector class provides a QSlot, QmitkModelViewSelectionConnector::ChangeModelSelection(const QItemSelection\&, const QItemSelection\&) that is called if the selectionChanged-signal of the selection model of the given QAbstractItemView is emitted. This indicates a change in the model selection. The slot itself will emit a QSignal, QmitkModelViewSelectionConnector::CurrentSelectionChanged(QList), where the selection of the item view has been transformed to a list of selected data nodes. The transformation is done by using the member functions QmitkModelViewSelectionConnector::GetSelectedNodes and QmitkModelViewSelectionConnector::GetInternalSelectedNodes: The selected indices of the item view's selection model are used to receive the corresponding data node from the data storage model. Additionally the connector class provides a function QmitkModelViewSelectionConnector::SetCurrentSelection(QList), which can be used to change the selection of the QAbstractItemView from outside of this class. The nodes of the list are compared to the nodes that are valid for the data storage model, according to the current node predicate. If the given nodes are valid the corresponding indices are selected in the view. If the given list is equal to the current selection, nothing happens. Using the QmitkModelViewSelectionConnector::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) function it is possible to set a variable that defines if only those nodes should be de-/selectable that are included in the list of filtered data nodes. This means that a new selection ca be received, which might change the current selection of the view. However, those nodes that are not visible, as defined by the node predicate of the data storage model, will not be de-/selectable. \b \anchor QmitkSelectionServiceConnector_ref QmitkSelectionServiceConnector The QmitkSelectionServiceConnector is used to interact with the global selection bus: It provides a private QSlot, QmitkSelectionServiceConnector::OnServiceSelectionChanged(const berry::IWorkbenchPart::Pointer&, const berry::ISelection::ConstPointer&), which is internally called if the selection of the selection bus has been changed. This slot transforms the berry selection into a list of selected data nodes and emits the QSignal QmitkSelectionServiceConnector::ServiceSelectionChanged(QList). Typically this signal is used to react in another class on the change of selected nodes from the selection bus, e.g. inside the QmitkModelViewSelectionConnector to change the selection of an item view via the selection bus. Additionally the connector class provides a function, QmitkSelectionServiceConnector::SetAsSelectionProvider(QmitkDataNodeSelectionProvider*), which can be used to set the connector as a provider for data node selections that will be sent via the global selection bus. This way a QmitkModelViewSelectionConnector can propagate its selection to all listeners of the selection bus via the QmitkSelectionServiceConnector. The following code shows how the two connector classes can be connected: \code void QmitkDataStorageViewerTestView::SetAsSelectionProvider(bool enable) { if (enable) { m_SelectionServiceConnector->SetAsSelectionProvider(GetSite()->GetSelectionProvider().Cast().GetPointer()); connect(m_ModelViewSelectionConnector.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector.get(), SLOT(ChangeServiceSelection(QList))); } else { m_SelectionServiceConnector->RemoveAsSelectionProvider(); disconnect(m_ModelViewSelectionConnector.get(), SIGNAL(CurrentSelectionChanged(QList)), m_SelectionServiceConnector.get(), SLOT(ChangeServiceSelection(QList))); } } \endcode
\code void QmitkDataStorageViewerTestView::SetAsSelectionListener(bool enable) { if (enable) { m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector.get(), SLOT(SetCurrentSelection(QList))); } else { m_SelectionServiceConnector->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector.get(), SIGNAL(ServiceSelectionChanged(QList)), m_ModelViewSelectionConnector.get(), SLOT(SetCurrentSelection(QList))); } } \endcode \subsection DataStorageInspectors The data storage inspectors \b \anchor QmitkNodeSelectionDialog_ref QmitkNodeSelectionDialog The QmitkNodeSelectionDialog is the main widget to be used in order to select data nodes from the data storage. It is a pop-up-dialog that can be included in any other widget, such as the QmitkSingleNodeSelectionWidget or the QmitkMultiNodeSelectionWidget. The node selection dialog serves as a container for different QmitkAbstractDataStorageInspector: It uses a QTabWidget that holds the different data storage inspectors. These inspectors are received by using the mitk::DataStorageInspectorGenerator class. Each data storage inspector is then added to the tab widget and its \a CurrentSelectionChanged-signal is connected to the dialog's \a OnSelectionChanged-slot. If the selection inside a data storage inspector changes, the slot sets the selected nodes of the dialog and propagates this selection to all its known data storage inspectors. This way all inspectors that are currently held in the tab widget show the same selection. In order to dynamically add and receive new data storage inspectors the microservice approach is used: Each concrete implementation of a data storage inspector provider registers itself as an mitk::IDataStorageInspectorProvider via the service registry. For this the QmitkDataStorageInspectorProviderBase can be used. Any class can use the mitk::DataStorageInspectorGenerator to either receive all registered data storage inspector providers or a specific one. A provider in turn provides a specific data storage inspector, which can be used to display the data nodes of the data storage. The user can define which of the registered data storage inspectors should be available when using a QmitkNodeSelectionDialog: Using the workbench preferences one can select the Node Selection entry and enable or disable certain data storage inspectors. \b mitk::IDataStorageInspectorProvider The mitk::IDataStorageInspectorProvider declares the service interface for all data storage inspector providers. It must be implemented to override the mitk::IDataStorageInspectorProvider::CreateInspector-function. This function should return the concrete data storage inspector created by the provider. In order to simplify the use of this interface and to add some basic functionality, the QmitkDataStorageInspectorProviderBase class should be used. \b QmitkDataStorageInspectorProviderBase The QmitkDataStorageInspectorProviderBase implements the mitk::IDataStorageInspectorProvider-interface and is a template implementation: It can be used with a concrete implementation of the QmitkAbstractDataStorageInspector; the data storage inspector that the provider base should create. When the provider base is initialized it registers itself as an mitk::IDataStorageInspectorProvider service reference. The provider base overrides the mitk::IDataStorageInspectorProvider::CreateInspector-function of the interface to create and return a concrete instance of the provided data storage inspector. This way each concrete data storage inspector can be created by receiving the corresponding data storage inspector provider as a service and using the mitk::IDataStorageInspectorProvider::CreateInspector-function. \b mitk::DataStorageInspectorGenerator The mitk::DataStorageInspectorGenerator simply provides two static functions to receive all or a specific data storage provider. These providers have been registered before as an mitk::IDataStorageInspectorProvider service. Using the microservice approach the provider can be received without any further knowledge about the concrete provider. This way a developer can easily add their own concrete data storage inspector by creating a new QmitkDataStorageInspectorProviderBase with the concrete inspector as the template argument. \subsection CustomDataStorageInspector Adding a custom data storage inspector As mentioned above, the microservice approach is used inside the mitk::DataStorageInspectorGenerator to receive all known or a specific data storage provider. In order to let the microservice know about a new custom data storage inspector, it has to be provided by a data storage provider. This data storage provider has to be registered as an mitk::IDataStorageInspectorProvider service. The new selection concept provides the QmitkDataStorageInspectorProviderBase class (see above), which automatically registers the data storage provider as a provider service. For this the following constructor can be used: \code QmitkDataStorageInspectorProviderBase(const std::string& id, const std::string& displayName, const std::string& desc= "") \endcode The constructor can be used like this: \code mitk::IDataStorageInspectorProvider* dataStorageInspectorProvider = new QmitkDataStorageInspectorProviderBase("org.mitk.QmitkDataStorageListInspector", "Simple list", "Displays the filtered content of the data storage in a simple list.")); \endcode This registers a new QmitkDataStorageInspectorProviderBase instance as an mitk::IDataStorageInspectorProvider service. The data storage provider and its specific data storage inspector can later be received using the following code: \code mitk::IDataStorageInspectorProvider* dataStorageProvider = mitk::DataStorageInspectorGenerator::GetProvider("org.mitk.QmitkDataStorageListInspector"); QmitkAbstractDataStorageInspector* dataStorageInspector = dataStorageProvider->CreateInspector(); \endcode In the code snippets the QmitkDataStorageListInspector class was used. This is an example of a concrete data storage inspector which is provided by the presented selection concept. It is a frequently used data storage inspector and already registered via a corresponding provider base. This is done inside the mitk::QtWidgetsActivator class; a class that is used during module activation. Currently four frequently used custom data storage inspectors are already registered. The following sections describe these four frequently used basic inspectors. \subsubsection QmitkDataStorageListInspector The QmitkDataStorageListInspector is an example of a concrete data storage inspector, subclassing the QmitkAbstractDataStorageInspector. Its only purpose is to provide a custom model-view pair that represents a list view onto the data storage. The view is a simple QListView. The model is a custom model derived from the above mentioned QmitkAbstractDataStorageModel: The QmitkDataStorageDefaultListModel overrides the five pure virtual functions to react on changes of the data storage or the node predicate: each function calls the private UpdateModelData-function, which will simply retrieve the currently available (possibly filtered) data nodes of the data storage. Additionally the default list model needs to override some functions of the QAbstractItemModel in order to represent a list model. One important function to override is the data(const QModelIndex\& index, int role = Qt::DisplayRole)}-function: This function is used by Qt to define what has to be displayed in the list view. It is important that a custom model returns an mitk::DataNode::Pointer object for a model index when the role is mitkDataNodeRole. \subsubsection QmitkDataStorageTreeInspector The QmitkDataStorageTreeInspector is another example of a concrete data storage inspector, subclassing the QmitkAbstractDataStorageInspector. Its only purpose is to provide a custom model-view pair that represents a tree view onto the data storage. The view is a simple QTreeView. The model is a custom model derived from the above mentioned QmitkAbstractDataStorageModel: The QmitkDataStorageSimpleTreeModel needs to override some functions of the QAbstractItemModel in order to represent a tree model. Again, one important function to override is the data(const QModelIndex\& index, int role = Qt::DisplayRole)}-function. It is important that the simple tree model returns an mitk::DataNode::Pointer object for a model index when the role is mitkDataNodeRole. The QmitkDataStorageTreeInspector is designed to reflect the classical data manager view. \subsubsection QmitkDataStorageSelectionHistoryInspector The QmitkDataStorageSelectionHistoryInspector is a concrete data storage inspector that allows to view the recently used nodes of the data storage. It uses the same QmitkDataStorageDefaultListModel as the QmitkDataStorageListInspector. The QmitkDataStorageSelectionHistoryInspector provides a function to add nodes to the selection history. The selection history will be displayed in chronological order and a selected node will only appear once in the history list. \subsubsection QmitkDataStorageFavoriteNodesInspector The QmitkDataStorageFavoriteNodesInspector is a concrete data storage inspector that allows to view a user's favorite nodes of the data storage. It extends the QmitkDataStorageListInspector. The QmitkNodeSelectionDialog provides a function to set nodes as favorite nodes using a specific node property. It allows the QmitkDataStorageFavoriteNodesInspector to use a customized node predicate to filter nodes according to their favorite-property-state. This favorite-property-state is defined by the node property "org.mitk.selection.favorite" The QmitkDataStorageFavoriteNodesInspector provides a function to unset nodes as favorite nodes by setting this specific node property to "false", so that they won't appear anymore in the list of favorite nodes. \subsection NodeSelectionWidgets The node selection widgets \b QmitkAbstractNodeSelectionWidget The QmitkAbstractNodeSelectionWidget extends the QWidget to provide a custom widget to show data nodes after they have been selected using the QmitkNodeSelectionDialog with its data storage inspectors. This custom widget extends the QWidget functionality in order to accept an mitk::DataStorage and an mitk::NodePredicateBase. The data storage is used to define which data storage should be used by the selection dialog. The node predicate is used to filter the data storage. Any subclass of this abstract node selection widget needs to override the following functions in order to customize its behavior: - void QmitkAbstractNodeSelectionWidget::SetCurrentSelection(NodeList), in order to set the selection of this widget given an external list of selected nodes. This function may be customized to filter the incoming selected nodes. - void QmitkAbstractNodeSelectionWidget::UpdateInfo(), in order to set the information text and enable / disable the control buttons according to the current selection. - void QmitkAbstractNodeSelectionWidget::OnNodePredicateChanged(), in order to react to a change of the used node predicate. Changing the node predicate might alter the selection. This function is called if QmitkAbstractNodeSelectionWidget::SetNodePredicate(mitk::NodePredicateBase*) was called. - void QmitkAbstractNodeSelectionWidget::OnDataStorageChanged(), in order to react to a change of the used data storage. Changing the data storage might alter the selection. This function is called if QmitkAbstractNodeSelectionWidget::SetDataStorage(mitk::DataStorage*) was called. Furthermore QmitkAbstractNodeSelectionWidget::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) can be overridden to set the modus to (not) include invisible nodes in the selection. Default value is true. \b QmitkSingleNodeSelectionWidget The QmitkSingleNodeSelectionWidget is an example of a concrete node selection widget, subclassing the QmitkAbstractNodeSelectionWidget. It overrides the virtual functions to react on changes of the node selection. If the QmitkNodeSelectionButton of the widget is pressed, a QmitkNodeSelectionDialog is opened as a pop-up for data node selection. This dialog is a \ref QmitkNodeSelectionDialog_ref "QmitkNodeSelectionDialog" (see \ref DataStorageInspectors). It presents the currently known and active data storage inspectors for the user to select a single data node. If a selection is confirmed, the pop-up closes and the selected data node will be shown in the QmitkSingleNodeSelectionWidget. The user will see a thumbnail or an icon of the data node (if available) along with an HTML text of the data node info. The QmitkSingleNodeSelectionWidget is currently used inside plugin views to allow to select specific nodes of the data storage for further processing. A plugin view can connect a function to the CurrentSelectionChanged-signal to immediately react on a new selection of the QmitkSingleNodeSelectionWidget. Using QmitkSingleNodeSelectionWidget::GetSelectedNode the currently selected node of the selection widget can be retrieved anytime. Additionally an auto-selection-mode is available that automatically selects a node from the data storage if such a data storage is set and contains a node that matches the given predicate. The auto-selection-mode only works if the QmitkSingleNodeSelectionWidget does not already show a selected node. \b QmitkMultiNodeSelectionWidget The QmitkMultiNodeSelectionWidget is another example of a concrete node selection widget, subclassing the QmitkAbstractNodeSelectionWidget. It overrides the virtual functions to react on changes of the node selection. If the 'Change selection'-button is pressed a QmitkNodeSelectionDialog is opened as a pop-up for data node selection. This dialog is a \ref QmitkNodeSelectionDialog_ref "QmitkNodeSelectionDialog" (see \ref DataStorageInspectors). It presents the currently known and active data storage inspectors for the user to select a single data node. If a selection is confirmed, the pop-up closes and the selected data nodes will be shown in the QmitkMultiNodeSelectionWidget. Unlike single node selection the multi node selection is presented as a list of all the selected nodes. The QmitkMultiNodeSelectionWidget is currently used inside plugin views to allow to select a set of specific nodes of the data storage for further processing. A plugin view can connect a function to the CurrentSelectionChanged-signal to immediately react on a new selection of the QmitkMultiNodeSelectionWidget. Using QmitkAbstractNodeSelectionWidget::GetSelectedNodes the currently selected node of the selection widget can be retrieved anytime. QmitkMultiNodeSelectionWidget::SetSelectionCheckFunction allows to define a function that puts a constraint on the node selection (dialog) to only allow specific combinations or seletions of nodes. The result of the constraint check has to be either an empty string (indicating a valid node selection) or a string that explains the error / constraint violation. A simple check function can look like this: \code auto checkFunction = [](const QmitkMultiNodeSelectionWidget::NodeList & nodes) { if (!(nodes.size() % 2)) { std::stringstream ss; - ss << "

Invalid selection.

The number of selected nodes must be uneven! the current number is " << nodes.size() << ".

"; + ss << "

Invalid selection.

The number of selected nodes must be uneven! the current number is " << nodes.size() << ".

"; return ss.str(); } return std::string(); }; multNodeSelectionWidget->SetSelectionCheckFunction(checkFunction); \endcode This check function will be passed to the QmitkNodeSelectionDialog and is used to print an error message, if an invalid selection is made inside the node selection dialog. This will prevent the user from confirming an even selection. If a valid selection is made no error message will be printed and the user can confirm the selection. \b QmitkNodeSelectionButton The QmitkNodeSelectionButton is a customized QPushButton and is used for example inside the QmitkSingleNodeSelectionWidget: It displays the thumbnail or icon of the selected data node (if available) along with an HTML text of the data node info. \section DataStorageViewerTestPlugin The DataStorageViewerTest plugin The \a org.mitk.gui.qt.datastorageviewertest plugin can be enabled and used inside the MITK workbench. It serves as a test / demo plugin with a single view that shows different uses of the new selection concept and its related classes. The following classes are used: -# Top row: A QmitkDataStorageListInspector -# Top right: A QmitkDataStorageTreeInspector -# Bottom left: A QmitkSingleNodeSelectionWidget -# Bottom right: A QmitkMultiNodeSelectionWidget The top row presents the two examples of an implementation of the QmitkAbstractDataStorageInspector. They display the whole content of the given data storage in two different ways. No node predicates are used in this case. Both inspectors come with two checkboxes to set each inspectors as a selection provider and a selection listener. With these checkboxes turned on the QmitkSelectionServiceConnector for each inspector is connected to the QmitkModelViewSelectionConnector of each inspector (as shown in \ref QmitkSelectionServiceConnector_ref "QmitkSelectionServiceConnector").
See how the selection also changes in the inspectors if nodes are selected in the mitk::DataManager and the Set as selection listener-checkbox is checked for the inspector. The bottom row presents the two examples of an implementation of the QmitkAbstractNodeSelectionWidget: They display a specific data node selection that was made by the user. Both widgets come with several checkboxes to modify the behavior of the selection widgets. */ diff --git a/Modules/AlgorithmsExt/include/mitkMaskAndCutRoiImageFilter.h b/Modules/AlgorithmsExt/include/mitkMaskAndCutRoiImageFilter.h index be17583bbb..195fa6675b 100644 --- a/Modules/AlgorithmsExt/include/mitkMaskAndCutRoiImageFilter.h +++ b/Modules/AlgorithmsExt/include/mitkMaskAndCutRoiImageFilter.h @@ -1,77 +1,72 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkMaskAndCutRoiImageFilter_h #define mitkMaskAndCutRoiImageFilter_h #include "MitkAlgorithmsExtExports.h" #include "mitkImageToImageFilter.h" #include "itkRegionOfInterestImageFilter.h" #include "mitkAutoCropImageFilter.h" #include "mitkBoundingObject.h" #include "mitkDataNode.h" #include "mitkMaskImageFilter.h" namespace mitk { /** \brief Cuts a region of interest (ROI) out of an image In the first step, this filter reduces the image region of the given ROI to a minimum. Using this region, a subvolume ist cut out of the given input image. The ROI is then used to mask the subvolume. Pixel inside the ROI will have their original value, pixel outside will be replaced by m_OutsideValue */ class MITKALGORITHMSEXT_EXPORT MaskAndCutRoiImageFilter : public ImageToImageFilter { typedef itk::Image ItkImageType; typedef itk::Image ItkMaskType; typedef itk::ImageRegion<3> RegionType; typedef itk::RegionOfInterestImageFilter ROIFilterType; public: mitkClassMacro(MaskAndCutRoiImageFilter, ImageToImageFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - itkGetMacro(MaxValue, mitk::ScalarType); - itkGetMacro(MinValue, mitk::ScalarType); void SetRegionOfInterest(mitk::BaseData *roi); // void SetRegionOfInterest(Image::Pointer image); // void SetRegionOfInterest(BoundingObject::Pointer boundingObject); // void SetRegionOfInterestByNode(mitk::DataNode::Pointer node); // temporary fix for bug # mitk::Image::Pointer GetOutput(); protected: MaskAndCutRoiImageFilter(); ~MaskAndCutRoiImageFilter() override; void GenerateData() override; ROIFilterType::Pointer m_RoiFilter; mitk::AutoCropImageFilter::Pointer m_CropFilter; mitk::MaskImageFilter::Pointer m_MaskFilter; // needed for temporary fix mitk::Image::Pointer m_outputImage; - mitk::ScalarType m_MaxValue; - mitk::ScalarType m_MinValue; - }; // class } // namespace #endif diff --git a/Modules/AlgorithmsExt/include/mitkMaskImageFilter.h b/Modules/AlgorithmsExt/include/mitkMaskImageFilter.h index ced7d4cb2b..9a8e1b1482 100644 --- a/Modules/AlgorithmsExt/include/mitkMaskImageFilter.h +++ b/Modules/AlgorithmsExt/include/mitkMaskImageFilter.h @@ -1,111 +1,93 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkMaskImageFilter_h #define mitkMaskImageFilter_h #include "MitkAlgorithmsExtExports.h" #include "mitkCommon.h" -#include "mitkImageTimeSelector.h" #include "mitkImageToImageFilter.h" #include "itkImage.h" namespace mitk { //##Documentation //## @brief //## @ingroup Process class MITKALGORITHMSEXT_EXPORT MaskImageFilter : public ImageToImageFilter { public: mitkClassMacro(MaskImageFilter, ImageToImageFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void SetMask(const mitk::Image *mask); - const mitk::Image *GetMask() const; - - /** - * get/set the min Value of the original image in the masked area - **/ - itkGetMacro(MinValue, mitk::ScalarType); - itkSetMacro(MinValue, mitk::ScalarType); - - /** - * get/set the max Value of the original image in the masked area - **/ - itkGetMacro(MaxValue, mitk::ScalarType); - itkSetMacro(MaxValue, mitk::ScalarType); + void SetMask(const mitk::Image *mask); + const Image *GetMask() const; + Image* GetMask(); /** * This value is used as outside value. This only works * if OverrideOutsideValue is set to true. Default is 0. **/ itkSetMacro(OutsideValue, mitk::ScalarType); /** * This value is used as outside value. This only works * if OverrideOutsideValue is set to true. Default is 0. */ itkGetMacro(OutsideValue, mitk::ScalarType); /** * If OverrideOutsideValue is set to false, this minimum * of the pixel type of the output image is taken as outside * value. If set to true, the value set via SetOutsideValue is * used as background. */ itkSetMacro(OverrideOutsideValue, bool); /** * If OverrideOutsideValue is set to false, this minimum * of the pixel type of the output image is taken as outside * value. If set to true, the value set via SetOutsideValue is * used as background. */ itkGetMacro(OverrideOutsideValue, bool); itkBooleanMacro(OverrideOutsideValue); protected: MaskImageFilter(); ~MaskImageFilter() override; void GenerateInputRequestedRegion() override; void GenerateOutputInformation() override; void GenerateData() override; - template - void InternalComputeMask(itk::Image *itkImage); - - mitk::Image::Pointer m_Mask; - mitk::ImageTimeSelector::Pointer m_InputTimeSelector; - mitk::ImageTimeSelector::Pointer m_MaskTimeSelector; - mitk::ImageTimeSelector::Pointer m_OutputTimeSelector; + template + void InternalComputeMask(itk::Image* itkInput, itk::Image* itkMask); //##Description //## @brief Time when Header was last initialized itk::TimeStamp m_TimeOfHeaderInitialization; mitk::ScalarType m_OutsideValue; - mitk::ScalarType m_MinValue; - mitk::ScalarType m_MaxValue; + TimeStepType m_CurrentOutputTS; bool m_OverrideOutsideValue; }; } // namespace mitk #endif diff --git a/Modules/AlgorithmsExt/src/mitkMaskAndCutRoiImageFilter.cpp b/Modules/AlgorithmsExt/src/mitkMaskAndCutRoiImageFilter.cpp index 255d1316e1..eb3490c4a7 100644 --- a/Modules/AlgorithmsExt/src/mitkMaskAndCutRoiImageFilter.cpp +++ b/Modules/AlgorithmsExt/src/mitkMaskAndCutRoiImageFilter.cpp @@ -1,92 +1,90 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkBoundingObjectToSegmentationFilter.h" #include "mitkImageCast.h" mitk::MaskAndCutRoiImageFilter::MaskAndCutRoiImageFilter() { this->SetNumberOfRequiredInputs(2); m_CropFilter = mitk::AutoCropImageFilter::New(); m_RoiFilter = ROIFilterType::New(); m_MaskFilter = mitk::MaskImageFilter::New(); } mitk::MaskAndCutRoiImageFilter::~MaskAndCutRoiImageFilter() { } void mitk::MaskAndCutRoiImageFilter::SetRegionOfInterest(mitk::BaseData *roi) { mitk::Image::Pointer image = dynamic_cast(roi); if (image.IsNotNull()) { this->SetInput(1, image); return; } mitk::BoundingObject::Pointer boundingObject = dynamic_cast(roi); if (boundingObject.IsNotNull() && this->GetInput(0) != nullptr) { mitk::BoundingObjectToSegmentationFilter::Pointer filter = mitk::BoundingObjectToSegmentationFilter::New(); filter->SetBoundingObject(boundingObject); filter->SetInput(this->GetInput(0)); filter->Update(); this->SetInput(1, filter->GetOutput()); return; } } mitk::Image::Pointer mitk::MaskAndCutRoiImageFilter::GetOutput() { return m_outputImage; } void mitk::MaskAndCutRoiImageFilter::GenerateData() { mitk::Image::ConstPointer inputImage = this->GetInput(0); mitk::Image::ConstPointer maskImage = this->GetInput(1); // mitk::Image::Pointer outputImage = this->GetOutput(); // temporary fix for bug # m_outputImage = this->GetOutput(); ItkImageType::Pointer itkImage = ItkImageType::New(); mitk::Image::Pointer tmpImage = mitk::Image::New(); m_CropFilter->SetInput(maskImage); m_CropFilter->SetBackgroundValue(0); m_CropFilter->Update(); RegionType region = m_CropFilter->GetCroppingRegion(); mitk::CastToItkImage(inputImage, itkImage); m_RoiFilter->SetInput(itkImage); m_RoiFilter->SetRegionOfInterest(region); m_RoiFilter->Update(); mitk::CastToMitkImage(m_RoiFilter->GetOutput(), tmpImage); m_MaskFilter->SetInput(0, tmpImage); m_MaskFilter->SetMask(m_CropFilter->GetOutput()); m_MaskFilter->SetOutsideValue(-32765); m_MaskFilter->Update(); - m_MaxValue = m_MaskFilter->GetMaxValue(); - m_MinValue = m_MaskFilter->GetMinValue(); // temporary fix for bug # m_outputImage = m_MaskFilter->GetOutput(); m_outputImage->DisconnectPipeline(); } diff --git a/Modules/AlgorithmsExt/src/mitkMaskImageFilter.cpp b/Modules/AlgorithmsExt/src/mitkMaskImageFilter.cpp index c4d13bff6c..070345dbf0 100644 --- a/Modules/AlgorithmsExt/src/mitkMaskImageFilter.cpp +++ b/Modules/AlgorithmsExt/src/mitkMaskImageFilter.cpp @@ -1,241 +1,129 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMaskImageFilter.h" #include "mitkImageTimeSelector.h" #include "mitkProperties.h" #include "mitkTimeHelper.h" +#include "mitkImageTimeSelector.h" #include "mitkImageAccessByItk.h" #include "mitkImageToItk.h" -#include "itkImageRegionConstIterator.h" -#include "itkImageRegionIteratorWithIndex.h" +#include "itkMaskImageFilter.h" #include -mitk::MaskImageFilter::MaskImageFilter() : m_Mask(nullptr) +mitk::MaskImageFilter::MaskImageFilter() { this->SetNumberOfIndexedInputs(2); this->SetNumberOfRequiredInputs(2); - m_InputTimeSelector = mitk::ImageTimeSelector::New(); - m_MaskTimeSelector = mitk::ImageTimeSelector::New(); - m_OutputTimeSelector = mitk::ImageTimeSelector::New(); m_OverrideOutsideValue = false; m_OutsideValue = 0; + m_CurrentOutputTS = 0; } mitk::MaskImageFilter::~MaskImageFilter() { } void mitk::MaskImageFilter::SetMask(const mitk::Image *mask) { // Process object is not const-correct so the const_cast is required here - m_Mask = const_cast(mask); - this->ProcessObject::SetNthInput(1, m_Mask); + auto nonconstMask = const_cast(mask); + this->ProcessObject::SetNthInput(1, nonconstMask); } const mitk::Image *mitk::MaskImageFilter::GetMask() const { - return m_Mask; + return this->GetInput(1); +} + +mitk::Image* mitk::MaskImageFilter::GetMask() +{ + return this->GetInput(1); } void mitk::MaskImageFilter::GenerateInputRequestedRegion() { Superclass::GenerateInputRequestedRegion(); mitk::Image *output = this->GetOutput(); mitk::Image *input = this->GetInput(); - mitk::Image *mask = m_Mask; + mitk::Image *mask = this->GetMask(); if ((output->IsInitialized() == false) || (mask == nullptr) || (mask->GetTimeGeometry()->CountTimeSteps() == 0)) return; input->SetRequestedRegionToLargestPossibleRegion(); mask->SetRequestedRegionToLargestPossibleRegion(); GenerateTimeInInputRegion(output, input); - GenerateTimeInInputRegion(output, mask); } void mitk::MaskImageFilter::GenerateOutputInformation() { mitk::Image::ConstPointer input = this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); if ((output->IsInitialized()) && (this->GetMTime() <= m_TimeOfHeaderInitialization.GetMTime())) return; itkDebugMacro(<< "GenerateOutputInformation()"); output->Initialize(input->GetPixelType(), *input->GetTimeGeometry()); output->SetPropertyList(input->GetPropertyList()->Clone()); m_TimeOfHeaderInitialization.Modified(); } -template -void mitk::MaskImageFilter::InternalComputeMask(itk::Image *inputItkImage) +template +void mitk::MaskImageFilter::InternalComputeMask(itk::Image* itkInput, itk::Image* itkMask) { - // dirty quick fix, duplicating code so both unsigned char and unsigned short are supported - // this should be changed once unsigned char segmentations can be converted to unsigned short - mitk::PixelType pixelType = - m_MaskTimeSelector->GetOutput()->GetImageDescriptor()->GetChannelDescriptor().GetPixelType(); - if (pixelType.GetComponentType() == itk::IOComponentEnum::UCHAR) - { - typedef itk::Image ItkInputImageType; - typedef itk::Image ItkMaskImageType; - typedef itk::Image ItkOutputImageType; - - typedef itk::ImageRegionConstIterator ItkInputImageIteratorType; - typedef itk::ImageRegionConstIterator ItkMaskImageIteratorType; - typedef itk::ImageRegionIteratorWithIndex ItkOutputImageIteratorType; - - typename mitk::ImageToItk::Pointer maskimagetoitk = mitk::ImageToItk::New(); - maskimagetoitk->SetInput(m_MaskTimeSelector->GetOutput()); - maskimagetoitk->Update(); - typename ItkMaskImageType::Pointer maskItkImage = maskimagetoitk->GetOutput(); - - typename mitk::ImageToItk::Pointer outputimagetoitk = - mitk::ImageToItk::New(); - outputimagetoitk->SetInput(m_OutputTimeSelector->GetOutput()); - outputimagetoitk->Update(); - typename ItkOutputImageType::Pointer outputItkImage = outputimagetoitk->GetOutput(); - - // create the iterators - typename ItkInputImageType::RegionType inputRegionOfInterest = inputItkImage->GetLargestPossibleRegion(); - ItkInputImageIteratorType inputIt(inputItkImage, inputRegionOfInterest); - ItkMaskImageIteratorType maskIt(maskItkImage, inputRegionOfInterest); - ItkOutputImageIteratorType outputIt(outputItkImage, inputRegionOfInterest); - - // typename ItkOutputImageType::PixelType outsideValue = itk::NumericTraits::min(); - if (!m_OverrideOutsideValue) - m_OutsideValue = itk::NumericTraits::min(); - - m_MinValue = std::numeric_limits::max(); - m_MaxValue = std::numeric_limits::min(); - - for (inputIt.GoToBegin(), maskIt.GoToBegin(), outputIt.GoToBegin(); !inputIt.IsAtEnd() && !maskIt.IsAtEnd(); - ++inputIt, ++maskIt, ++outputIt) - { - if (maskIt.Get() > itk::NumericTraits::Zero) - { - outputIt.Set(inputIt.Get()); - m_MinValue = std::min((float)inputIt.Get(), (float)m_MinValue); - m_MaxValue = std::max((float)inputIt.Get(), (float)m_MaxValue); - } - else - { - outputIt.Set(m_OutsideValue); - } - } - } - else - { - { - typedef itk::Image ItkInputImageType; - typedef itk::Image ItkMaskImageType; - typedef itk::Image ItkOutputImageType; - - typedef itk::ImageRegionConstIterator ItkInputImageIteratorType; - typedef itk::ImageRegionConstIterator ItkMaskImageIteratorType; - typedef itk::ImageRegionIteratorWithIndex ItkOutputImageIteratorType; - - typename mitk::ImageToItk::Pointer maskimagetoitk = mitk::ImageToItk::New(); - maskimagetoitk->SetInput(m_MaskTimeSelector->GetOutput()); - maskimagetoitk->Update(); - typename ItkMaskImageType::Pointer maskItkImage = maskimagetoitk->GetOutput(); - - typename mitk::ImageToItk::Pointer outputimagetoitk = - mitk::ImageToItk::New(); - outputimagetoitk->SetInput(m_OutputTimeSelector->GetOutput()); - outputimagetoitk->Update(); - typename ItkOutputImageType::Pointer outputItkImage = outputimagetoitk->GetOutput(); - - // create the iterators - typename ItkInputImageType::RegionType inputRegionOfInterest = inputItkImage->GetLargestPossibleRegion(); - ItkInputImageIteratorType inputIt(inputItkImage, inputRegionOfInterest); - ItkMaskImageIteratorType maskIt(maskItkImage, inputRegionOfInterest); - ItkOutputImageIteratorType outputIt(outputItkImage, inputRegionOfInterest); - - // typename ItkOutputImageType::PixelType outsideValue = itk::NumericTraits::min(); - if (!m_OverrideOutsideValue) - m_OutsideValue = itk::NumericTraits::min(); - - m_MinValue = std::numeric_limits::max(); - m_MaxValue = std::numeric_limits::min(); - - for (inputIt.GoToBegin(), maskIt.GoToBegin(), outputIt.GoToBegin(); !inputIt.IsAtEnd() && !maskIt.IsAtEnd(); - ++inputIt, ++maskIt, ++outputIt) - { - if (maskIt.Get() > itk::NumericTraits::Zero) - { - outputIt.Set(inputIt.Get()); - m_MinValue = std::min((float)inputIt.Get(), (float)m_MinValue); - m_MaxValue = std::max((float)inputIt.Get(), (float)m_MaxValue); - } - else - { - outputIt.Set(m_OutsideValue); - } - } - } - } + using MaskFilterType = itk::MaskImageFilter< itk::Image, itk::Image>; + + auto maskFilter = MaskFilterType::New(); + maskFilter->SetInput(itkInput); + maskFilter->SetMaskImage(itkMask); + maskFilter->SetOutsideValue(m_OutsideValue); + maskFilter->Update(); + + auto output = maskFilter->GetOutput(); + + // re-import to MITK + auto mitkOutput = this->GetOutput(); + mitkOutput->SetVolume(output->GetBufferPointer(), m_CurrentOutputTS); } void mitk::MaskImageFilter::GenerateData() { - mitk::Image::ConstPointer input = this->GetInput(); - mitk::Image::Pointer mask = m_Mask; + auto input = this->GetInput(); + auto mask = this->GetMask(); mitk::Image::Pointer output = this->GetOutput(); - if ((output->IsInitialized() == false) || (mask.IsNull()) || (mask->GetTimeGeometry()->CountTimeSteps() == 0)) + if ((output->IsInitialized() == false) || (nullptr == mask) || (mask->GetTimeGeometry()->CountTimeSteps() == 0)) return; - m_InputTimeSelector->SetInput(input); - m_MaskTimeSelector->SetInput(mask); - m_OutputTimeSelector->SetInput(this->GetOutput()); - - mitk::Image::RegionType outputRegion = output->GetRequestedRegion(); - const mitk::TimeGeometry *outputTimeGeometry = output->GetTimeGeometry(); const mitk::TimeGeometry *inputTimeGeometry = input->GetTimeGeometry(); - const mitk::TimeGeometry *maskTimeGeometry = mask->GetTimeGeometry(); - ScalarType timeInMS; - int timestep = 0; - int tstart = outputRegion.GetIndex(3); - int tmax = tstart + outputRegion.GetSize(3); - - int t; - for (t = tstart; t < tmax; ++t) + for (TimeStepType t = 0; t < inputTimeGeometry->CountTimeSteps(); ++t) { - timeInMS = outputTimeGeometry->TimeStepToTimePoint(t); - - timestep = inputTimeGeometry->TimePointToTimeStep(timeInMS); - - m_InputTimeSelector->SetTimeNr(timestep); - m_InputTimeSelector->UpdateLargestPossibleRegion(); - m_OutputTimeSelector->SetTimeNr(t); - m_OutputTimeSelector->UpdateLargestPossibleRegion(); - - timestep = maskTimeGeometry->TimePointToTimeStep(timeInMS); - m_MaskTimeSelector->SetTimeNr(timestep); - m_MaskTimeSelector->UpdateLargestPossibleRegion(); + auto timeInMS = inputTimeGeometry->TimeStepToTimePoint(t); - AccessByItk(m_InputTimeSelector->GetOutput(), InternalComputeMask); + m_CurrentOutputTS = t; + auto inputFrame = SelectImageByTimePoint(input, timeInMS); + auto maskFrame = SelectImageByTimePoint(mask, timeInMS); + AccessTwoImagesFixedDimensionByItk(inputFrame, maskFrame, InternalComputeMask, 3); } m_TimeOfHeaderInitialization.Modified(); } diff --git a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp index 4764497413..ac53fc9dd6 100644 --- a/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkTemporoSpatialStringProperty.cpp @@ -1,426 +1,425 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include "mitkTemporoSpatialStringProperty.h" #include using CondensedTimeKeyType = std::pair; using CondensedTimePointsType = std::map; using CondensedSliceKeyType = std::pair; using CondensedSlicesType = std::map; namespace { /** Helper function that checks if between an ID and a successive ID is no gap.*/ template bool isGap(const TValue& value, const TValue& successor) { return value successor + 1; } template void CheckAndCondenseElement(const TNewKey& newKeyMinID, const TNewValue& newValue, TMasterKey& masterKey, TMasterValue& masterValue, TCondensedContainer& condensedContainer) { if (newValue != masterValue || isGap(newKeyMinID, masterKey.second)) { condensedContainer[masterKey] = masterValue; masterValue = newValue; masterKey.first = newKeyMinID; } masterKey.second = newKeyMinID; } /** Helper function that tries to condense the values of time points for a slice as much as possible and returns all slices with condensed timepoint values.*/ CondensedSlicesType CondenseTimePointValuesOfProperty(const mitk::TemporoSpatialStringProperty* tsProp) { CondensedSlicesType uncondensedSlices; auto zs = tsProp->GetAvailableSlices(); for (const auto z : zs) { CondensedTimePointsType condensedTimePoints; auto timePointIDs = tsProp->GetAvailableTimeSteps(z); CondensedTimeKeyType condensedKey = { timePointIDs.front(),timePointIDs.front() }; auto refValue = tsProp->GetValue(timePointIDs.front(), z); for (const auto timePointID : timePointIDs) { const auto& newVal = tsProp->GetValue(timePointID, z); CheckAndCondenseElement(timePointID, newVal, condensedKey, refValue, condensedTimePoints); } condensedTimePoints[condensedKey] = refValue; uncondensedSlices[{ z, z }] = condensedTimePoints; } return uncondensedSlices; } } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const char *s) { if (s) { SliceMapType slices{{0, s}}; m_Values.insert(std::make_pair(0, slices)); } } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const std::string &s) { SliceMapType slices{{0, s}}; m_Values.insert(std::make_pair(0, slices)); } mitk::TemporoSpatialStringProperty::TemporoSpatialStringProperty(const TemporoSpatialStringProperty &other) : BaseProperty(other), m_Values(other.m_Values) { } bool mitk::TemporoSpatialStringProperty::IsEqual(const BaseProperty &property) const { return this->m_Values == static_cast(property).m_Values; } bool mitk::TemporoSpatialStringProperty::Assign(const BaseProperty &property) { this->m_Values = static_cast(property).m_Values; return true; } std::string mitk::TemporoSpatialStringProperty::GetValueAsString() const { return GetValue(); } bool mitk::TemporoSpatialStringProperty::IsUniform() const { auto refValue = this->GetValue(); for (const auto& timeStep : m_Values) { auto finding = std::find_if_not(timeStep.second.begin(), timeStep.second.end(), [&refValue](const mitk::TemporoSpatialStringProperty::SliceMapType::value_type& val) { return val.second == refValue; }); if (finding != timeStep.second.end()) { return false; } } return true; } itk::LightObject::Pointer mitk::TemporoSpatialStringProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); result->UnRegister(); return result; } mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValue() const { std::string result = ""; if (!m_Values.empty()) { if (!m_Values.begin()->second.empty()) { result = m_Values.begin()->second.begin()->second; } } return result; }; std::pair mitk::TemporoSpatialStringProperty::CheckValue( const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { std::string value = ""; bool found = false; auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter == timeEnd && allowCloseTime) { // search for closest time step (earlier preverd) timeIter = m_Values.upper_bound(timeStep); if (timeIter != m_Values.begin()) { // there is a key lower than time step timeIter = std::prev(timeIter); } } if (timeIter != timeEnd) { const SliceMapType &slices = timeIter->second; auto sliceIter = slices.find(zSlice); auto sliceEnd = slices.end(); if (sliceIter == sliceEnd && allowCloseSlice) { // search for closest slice (earlier preverd) sliceIter = slices.upper_bound(zSlice); if (sliceIter != slices.begin()) { // there is a key lower than slice sliceIter = std::prev(sliceIter); } } if (sliceIter != sliceEnd) { value = sliceIter->second; found = true; } } return std::make_pair(found, value); }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).second; }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValueBySlice( const IndexValueType &zSlice, bool allowClose) const { return GetValue(0, zSlice, true, allowClose); }; mitk::TemporoSpatialStringProperty::ValueType mitk::TemporoSpatialStringProperty::GetValueByTimeStep( const TimeStepType &timeStep, bool allowClose) const { return GetValue(timeStep, 0, allowClose, true); }; bool mitk::TemporoSpatialStringProperty::HasValue() const { return !m_Values.empty(); }; bool mitk::TemporoSpatialStringProperty::HasValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const { return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).first; }; bool mitk::TemporoSpatialStringProperty::HasValueBySlice(const IndexValueType &zSlice, bool allowClose) const { return HasValue(0, zSlice, true, allowClose); }; bool mitk::TemporoSpatialStringProperty::HasValueByTimeStep(const TimeStepType &timeStep, bool allowClose) const { return HasValue(timeStep, 0, allowClose, true); }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableSlices() const { std::set uniqueSlices; for (const auto& timeStep : m_Values) { for (const auto& slice : timeStep.second) { uniqueSlices.insert(slice.first); } } return std::vector(std::begin(uniqueSlices), std::end(uniqueSlices)); } std::vector mitk::TemporoSpatialStringProperty::GetAvailableSlices( const TimeStepType &timeStep) const { std::vector result; auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter != timeEnd) { for (auto const &element : timeIter->second) { result.push_back(element.first); } } return result; }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps() const { std::vector result; for (auto const &element : m_Values) { result.push_back(element.first); } return result; }; std::vector mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps(const IndexValueType& slice) const { std::vector result; for (const auto& timeStep : m_Values) { if (timeStep.second.find(slice) != std::end(timeStep.second)) { result.push_back(timeStep.first); } } return result; } void mitk::TemporoSpatialStringProperty::SetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, const ValueType &value) { auto timeIter = m_Values.find(timeStep); auto timeEnd = m_Values.end(); if (timeIter == timeEnd) { SliceMapType slices{{zSlice, value}}; m_Values.insert(std::make_pair(timeStep, slices)); } else { timeIter->second[zSlice] = value; } this->Modified(); }; void mitk::TemporoSpatialStringProperty::SetValue(const ValueType &value) { - this->Modified(); m_Values.clear(); this->SetValue(0, 0, value); }; bool mitk::TemporoSpatialStringProperty::ToJSON(nlohmann::json& j) const { // We condense the content of the property to have a compact serialization. // We start with condensing time points and then slices (in difference to the // internal layout). Reason: There is more entropy in slices (looking at DICOM) // than across time points for one slice, so we can "compress" at a higher rate. // We didn't want to change the internal structure of the property as it would // introduce API inconvenience and subtle changes in behavior. auto uncondensedSlices = CondenseTimePointValuesOfProperty(this); CondensedSlicesType condensedSlices; if(!uncondensedSlices.empty()) { auto& masterSlice = uncondensedSlices.begin()->second; auto masterSliceKey = uncondensedSlices.begin()->first; for (const auto& uncondensedSlice : uncondensedSlices) { const auto& uncondensedSliceID = uncondensedSlice.first.first; CheckAndCondenseElement(uncondensedSliceID, uncondensedSlice.second, masterSliceKey, masterSlice, condensedSlices); } condensedSlices[masterSliceKey] = masterSlice; } auto values = nlohmann::json::array(); for (const auto& z : condensedSlices) { for (const auto& t : z.second) { const auto& minSliceID = z.first.first; const auto& maxSliceID = z.first.second; const auto& minTimePointID = t.first.first; const auto& maxTimePointID = t.first.second; auto value = nlohmann::json::object(); value["z"] = minSliceID; if (minSliceID != maxSliceID) value["zmax"] = maxSliceID; value["t"] = minTimePointID; if (minTimePointID != maxTimePointID) value["tmax"] = maxTimePointID; value["value"] = t.second; values.push_back(value); } } j = nlohmann::json{{"values", values}}; return true; } bool mitk::TemporoSpatialStringProperty::FromJSON(const nlohmann::json& j) { for (const auto& element : j["values"]) { auto value = element.value("value", ""); auto z = element.value("z", 0); auto zmax = element.value("zmax", z); auto t = element.value("t", 0); auto tmax = element.value("tmax", t); for (auto currentT = t; currentT <= tmax; ++currentT) { for (auto currentZ = z; currentZ <= zmax; ++currentZ) { this->SetValue(currentT, currentZ, value); } } } return true; } std::string mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(const mitk::BaseProperty *prop) { const auto *tsProp = dynamic_cast(prop); if (!tsProp) mitkThrow() << "Cannot serialize properties of types other than TemporoSpatialStringProperty."; nlohmann::json j; tsProp->ToJSON(j); return j.dump(); } mitk::BaseProperty::Pointer mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty(const std::string &value) { if (value.empty()) return nullptr; auto prop = mitk::TemporoSpatialStringProperty::New(); auto root = nlohmann::json::parse(value); prop->FromJSON(root); return prop.GetPointer(); } diff --git a/Modules/Multilabel/files.cmake b/Modules/Multilabel/files.cmake index 3253bf602b..cc5f29c1e5 100644 --- a/Modules/Multilabel/files.cmake +++ b/Modules/Multilabel/files.cmake @@ -1,22 +1,23 @@ set(CPP_FILES mitkLabel.cpp mitkLabelSetImage.cpp mitkLabelSetImageConverter.cpp mitkLabelSetImageSource.cpp mitkLabelSetImageHelper.cpp mitkLabelSetImageSurfaceStampFilter.cpp mitkLabelSetImageToSurfaceFilter.cpp mitkLabelSetImageToSurfaceThreadedFilter.cpp mitkLabelSetImageVtkMapper2D.cpp mitkMultilabelObjectFactory.cpp mitkMultiLabelIOHelper.cpp mitkMultiLabelEvents.cpp mitkMultiLabelPredicateHelper.cpp mitkDICOMSegmentationPropertyHelper.cpp mitkDICOMSegmentationConstants.cpp mitkSegmentationTaskList.cpp + mitkMultiLabelSegmentationVtkMapper3D.cpp ) set(RESOURCE_FILES ) diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index c53eaec19d..bfb1fdb3b3 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1719 +1,1722 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include #include #include #include #include #include #include #include #include #include namespace mitk { template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void ClearImageBuffer(mitk::Image* image) { if (image->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(image, ClearBufferProcessing, 4); } else { AccessByItk(image, ClearBufferProcessing); } } } const mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::UNLABELED_VALUE = 0; mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLabelValue(0), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLabelValue(other.m_ActiveLabelValue), m_LookupTable(other.m_LookupTable->Clone()), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { GroupIndexType i = 0; for (auto groupImage : other.m_LayerContainer) { this->AddLayer(groupImage->Clone(), other.GetConstLabelsByValue(other.GetLabelValuesByGroup(i))); i++; } m_Groups = other.m_Groups; // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero ClearImageBuffer(this); // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto [value, label] : m_LabelMap) { this->ReleaseLabel(label); } m_LabelMap.clear(); } unsigned int mitk::LabelSetImage::GetActiveLayer() const { if (m_LayerContainer.size() == 0) mitkThrow() << "Cannot return active layer index. No layer is available."; return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LayerContainer.size(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { if (!this->ExistGroup(indexToDelete)) mitkThrow() << "Cannot remove group. Group does not exist. Invalid group index: "<GetNumberOfLayers() == 1) { //last layer is about to be deleted newActiveIndex = 0; } else { //we have to add/subtract one more because we have not removed the layer yet, thus the group count is to 1 high. newActiveIndex = indexToDelete+1 < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 2; newActiveIndexBeforeDeletion = indexToDelete + 1 < GetNumberOfLayers() ? indexToDelete+1 : indexToDelete -1; } } if (activeIndex == indexToDelete) { // we are deleting the active layer, it should not be copied back into the vector m_activeLayerInvalid = true; //copy the image content of the upcoming new active layer; SetActiveLayer(newActiveIndexBeforeDeletion); } auto relevantLabels = m_GroupToLabelMap[indexToDelete]; { std::lock_guard guard(m_LabelNGroupMapsMutex); // remove labels of group for (auto labelValue : relevantLabels) { auto label = m_LabelMap[labelValue]; this->ReleaseLabel(label); m_LabelToGroupMap.erase(labelValue); m_LabelMap.erase(labelValue); this->InvokeEvent(LabelRemovedEvent(labelValue)); } // remove the group entries in the maps and the image. m_Groups.erase(m_Groups.begin() + indexToDelete); m_GroupToLabelMap.erase(m_GroupToLabelMap.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); } //update old indexes in m_GroupToLabelMap to new layer indexes for (auto& element : m_LabelToGroupMap) { if (element.second > indexToDelete) element.second = element.second -1; } //correct active layer index m_ActiveLayer = newActiveIndex; this->InvokeEvent(LabelsChangedEvent(relevantLabels)); this->InvokeEvent(GroupRemovedEvent(indexToDelete)); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const LabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::ConvertLabelVectorConst(const LabelVectorType& labels) { ConstLabelVectorType result(labels.begin(), labels.end()); return result; }; const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetAllLabelValues() const { LabelValueVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UNLABELED_VALUE }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(ConstLabelVector labels) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); ClearImageBuffer(newImage); return this->AddLayer(newImage, labels); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(mitk::Image* layerImage, ConstLabelVector labels) { GroupIndexType newGroupID = m_Groups.size(); if (nullptr == layerImage) mitkThrow() << "Cannot add group. Passed group image is nullptr."; bool equalGeometries = Equal( *(this->GetTimeGeometry()), *(layerImage->GetTimeGeometry()), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false); if (!equalGeometries) mitkThrow() << "Cannot add group. Passed group image has not the same geometry like segmentation."; if (layerImage->GetPixelType() != MakePixelType()) mitkThrow() << "Cannot add group. Passed group image has incorrect pixel type. Only LabelValueType is supported. Invalid pixel type: "<< layerImage->GetPixelType().GetTypeAsString(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); m_Groups.push_back(""); m_GroupToLabelMap.push_back({}); for (auto label : labels) { if (m_LabelMap.end() != m_LabelMap.find(label->GetValue())) { mitkThrow() << "Cannot add layer. Labels that should be added with layer use at least one label value that is already in use. Conflicted label value: " << label->GetValue(); } auto labelClone = label->Clone(); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); this->AddLabelToMap(labelClone->GetValue(), labelClone, newGroupID); this->RegisterLabel(labelClone); } this->Modified(); this->InvokeEvent(GroupAddedEvent(newGroupID)); return newGroupID; } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& labelSet) { if (m_LayerContainer.size() <= groupID) { mitkThrow() << "Trying to replace labels of non-existing group. Invalid group id: "< guard(m_LabelNGroupMapsMutex); oldLabels = this->m_GroupToLabelMap[groupID]; for (auto labelID : oldLabels) { this->RemoveLabelFromMap(labelID); this->InvokeEvent(LabelRemovedEvent(labelID)); } } this->InvokeEvent(LabelsChangedEvent(oldLabels)); this->InvokeEvent(GroupModifiedEvent(groupID)); //add new labels to group for (auto label : labelSet) { this->AddLabel(label->Clone(), groupID, true, false); } } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& labelSet) { return ReplaceGroupLabels(groupID, ConvertLabelVectorConst(labelSet)); } mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID]; } const mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer.at(groupID).GetPointer(); } const mitk::Image* mitk::LabelSetImage::GetGroupImageWorkaround(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; if (groupID == this->GetActiveLayer() && this->GetMTime()> m_LayerContainer[groupID]->GetMTime()) { //we have to transfer the content first into the group image if (4 == this->GetDimension()) { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (groupID)); } else { AccessByItk_1(this, ImageToLayerContainerProcessing, groupID); } } return m_LayerContainer[groupID].GetPointer(); } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::SetActiveLabel(LabelValueType label) { m_ActiveLabelValue = label; if (label != UNLABELED_VALUE) { auto groupID = this->GetGroupIndexOfLabel(label); if (groupID!=this->GetActiveLayer()) this->SetActiveLayer(groupID); } Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { ClearImageBuffer(this); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->InvokeEvent(LabelModifiedEvent(sourcePixelValue)); this->InvokeEvent(LabelModifiedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ sourcePixelValue, pixelValue })); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->InvokeEvent(LabelModifiedEvent(vectorOfSourcePixelValues[idx])); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->SetActiveLabel(pixelValue); this->InvokeEvent(LabelModifiedEvent(pixelValue)); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->InvokeEvent(LabelsChangedEvent(modifiedValues)); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { GroupIndexType groupID = 0; { std::lock_guard guard(m_LabelNGroupMapsMutex); if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) return; groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); this->RemoveLabelFromMap(pixelValue); if (m_ActiveLabelValue == pixelValue) { this->SetActiveLabel(0); } } this->InvokeEvent(LabelRemovedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ pixelValue })); this->InvokeEvent(GroupModifiedEvent(groupID)); } void mitk::LabelSetImage::RemoveLabelFromMap(LabelValueType pixelValue) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) mitkThrow()<<"Invalid state of instance. RemoveLabelFromMap was called for unknown label id. invalid label id: "<GetGroupIndexOfLabel(pixelValue); this->ReleaseLabel(m_LabelMap[pixelValue]); //now remove the label entry itself m_LabelMap.erase(pixelValue); m_LabelToGroupMap.erase(pixelValue); auto labelsInGroup = m_GroupToLabelMap[groupID]; labelsInGroup.erase(std::remove(labelsInGroup.begin(), labelsInGroup.end(), pixelValue), labelsInGroup.end()); m_GroupToLabelMap[groupID] = labelsInGroup; } void mitk::LabelSetImage::RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues) { for (const auto labelValue : vectorOfLabelPixelValues) { this->RemoveLabel(labelValue); } this->InvokeEvent(LabelsChangedEvent(vectorOfLabelPixelValues)); } void mitk::LabelSetImage::EraseLabel(LabelValueType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetGroupImage(groupID); if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->InvokeEvent(LabelModifiedEvent(pixelValue)); this->InvokeEvent(LabelsChangedEvent({ pixelValue })); Modified(); } void mitk::LabelSetImage::EraseLabels(const LabelValueVectorType& labelValues) { for (auto labelValue : labelValues) { this->EraseLabel(labelValue); } } mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::GetUnusedLabelValue() const { auto usedValues = this->GetUsedLabelValues(); return usedValues.back() + 1; } mitk::Label* mitk::LabelSetImage::AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone, bool correctLabelValue) { if (nullptr == label) mitkThrow() << "Invalid use of AddLabel. label is not valid."; mitk::Label::Pointer newLabel = label; { std::lock_guard guard(m_LabelNGroupMapsMutex); unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LayerContainer.size() >= max_size) return nullptr; if (addAsClone) newLabel = label->Clone(); auto pixelValue = newLabel->GetValue(); auto usedValues = this->GetUsedLabelValues(); auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); if (!usedValues.empty() && usedValues.end() != finding) { if (correctLabelValue) { pixelValue = this->GetUnusedLabelValue(); newLabel->SetValue(pixelValue); } else { mitkThrow() << "Cannot add label due to conflicting label value that already exists in the MultiLabelSegmentation. Conflicting label value: " << pixelValue; } } // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); this->AddLabelToMap(pixelValue, newLabel, groupID); this->RegisterLabel(newLabel); } this->InvokeEvent(LabelAddedEvent(newLabel->GetValue())); m_ActiveLabelValue = newLabel->GetValue(); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabelWithContent(Label* label, const Image* labelContent, GroupIndexType groupID, LabelValueType contentLabelValue, bool addAsClone, bool correctLabelValue) { if (nullptr == labelContent) mitkThrow() << "Invalid use of AddLabel. labelContent is not valid."; if (!Equal(*(this->GetTimeGeometry()), *(labelContent->GetTimeGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) mitkThrow() << "Invalid use of AddLabel. labelContent has not the same geometry like the segmentation."; auto newLabel = this->AddLabel(label, groupID, addAsClone, correctLabelValue); mitk::TransferLabelContent(labelContent, this->GetGroupImage(groupID), this->GetConstLabelsByValue(this->GetLabelValuesByGroup(groupID)), mitk::LabelSetImage::UNLABELED_VALUE, mitk::LabelSetImage::UNLABELED_VALUE, false, { {contentLabelValue, newLabel->GetValue()}}, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabel(const std::string& name, const mitk::Color& color, GroupIndexType groupID) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); return AddLabel(newLabel,groupID,false); } void mitk::LabelSetImage::RenameLabel(LabelValueType pixelValue, const std::string& name, const mitk::Color& color) { std::shared_lock guard(m_LabelNGroupMapsMutex); mitk::Label* label = GetLabel(pixelValue); if (nullptr == label) mitkThrow() << "Cannot rename label.Unknown label value provided. Unknown label value:" << pixelValue; label->SetName(name); label->SetColor(color); this->UpdateLookupTable(pixelValue); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } mitk::Label *mitk::LabelSetImage::GetActiveLabel() { if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } const mitk::Label* mitk::LabelSetImage::GetActiveLabel() const { if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, 4, pixelValue); } else { AccessByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, pixelValue); } } void mitk::LabelSetImage::SetLookupTable(mitk::LookupTable* lut) { m_LookupTable = lut; this->Modified(); } void mitk::LabelSetImage::UpdateLookupTable(PixelType pixelValue) { const mitk::Color& color = this->GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (GetLabel(pixelValue)->GetVisible()) rgba[3] = GetLabel(pixelValue)->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { if (layer >= m_Groups.size()) mitkThrow() << "Cannot get number of labels in group. Group is unknown. Invalid index:" << layer; return m_GroupToLabelMap[layer].size(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { return m_LabelMap.size(); } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (Exception& e) { mitkReThrow(e) << "Could not initialize by provided labeled image."; } catch (...) { mitkThrow() << "Could not initialize by provided labeled image due to unknown error."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { const auto originalSourceValue = sourceIter.Get(); const auto sourceValue = static_cast(originalSourceValue); if (originalSourceValue > mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains a pixel value that exceeds the label value range. Invalid pixel value:" << originalSourceValue; } targetIter.Set(sourceValue); if (LabelSetImage::UNLABELED_VALUE!=sourceValue && !this->ExistLabel(sourceValue)) { if (this->GetTotalNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains to many labels."; } std::stringstream name; name << "object-" << sourceValue; double rgba[4]; this->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->AddLabel(label,0,false); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); const auto activeLabel = this->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UNLABELED_VALUE) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, LabelValueType pixelValue) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; this->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); this->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(const itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } m_LayerContainer[layer]->Modified(); } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; if (!this->ExistGroup(groupID)) mitkThrow() << "Cannot add label. Defined group is unknown. Invalid group index: " << groupID; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = std::find(m_GroupToLabelMap[groupID].begin(), m_GroupToLabelMap[groupID].end(), labelValue); if (groupFinding == m_GroupToLabelMap[groupID].end()) { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::RegisterLabel(mitk::Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of RegisterLabel with a nullptr."; UpdateLookupTable(label->GetValue()); auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); m_LabelModEventGuardMap.emplace(label->GetValue(), ITKEventObserverGuard(label, itk::ModifiedEvent(), command)); } void mitk::LabelSetImage::ReleaseLabel(Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of ReleaseLabel with a nullptr."; m_LabelModEventGuardMap.erase(label->GetValue()); } void mitk::LabelSetImage::ApplyToLabels(const LabelValueVectorType& values, std::function&& lambda) { auto labels = this->GetLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); this->InvokeEvent(LabelsChangedEvent(values)); } void mitk::LabelSetImage::VisitLabels(const LabelValueVectorType& values, std::function&& lambda) const { auto labels = this->GetConstLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); } void mitk::LabelSetImage::OnLabelModified(const Object* sender, const itk::EventObject&) { auto label = dynamic_cast(sender); if (nullptr == label) mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; Superclass::Modified(); this->InvokeEvent(LabelModifiedEvent(label->GetValue())); } bool mitk::LabelSetImage::ExistLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); return m_LabelMap.end() != finding; } bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { return finding->second == groupIndex; } return false; } bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LayerContainer.size(); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } const mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UNLABELED_VALUE) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) { LabelVectorType result; for (const auto& labelValue : labelValues) { auto* label = this->GetLabel(labelValue); if (label != nullptr) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) const { ConstLabelVectorType result; for (const auto& labelValue : labelValues) { const auto* label = this->GetLabel(labelValue); if (label != nullptr) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; return m_GroupToLabelMap[index]; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByName(GroupIndexType index, const std::string_view name) const { LabelValueVectorType result; auto searchName = [&result, name](const Label* l) { if(l->GetName() == name) result.push_back(l->GetValue()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return result; } std::vector mitk::LabelSetImage::GetLabelClassNames() const { std::set names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetAllLabelValues(), searchName); return std::vector(names.begin(), names.end()); } std::vector mitk::LabelSetImage::GetLabelClassNamesByGroup(GroupIndexType index) const { std::set names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return std::vector(names.begin(), names.end()); } void mitk::LabelSetImage::SetAllLabelsVisible(bool visible) { - auto setVisibility = [this, visible](Label* l) + auto setVisibility = [visible,this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetAllLabelValues(), setVisibility); + this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible) { - auto setVisibility = [this, visible](Label* l) + auto setVisibility = [visible, this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setVisibility); + this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsVisibleByName(GroupIndexType group, const std::string_view name, bool visible) { - auto setVisibility = [this, visible](Label* l) + auto setVisibility = [visible, this](Label* l) { l->SetVisible(visible); this->UpdateLookupTable(l->GetValue()); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setVisibility); + this->m_LookupTable->Modified(); } void mitk::LabelSetImage::SetAllLabelsLocked(bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetAllLabelValues(), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByGroup(GroupIndexType group, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByName(GroupIndexType group, const std::string_view name, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setLock); } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // m_LookupTable; const mitk::LookupTable* lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable* rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tables not equal."; return returnValue; ; } // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } if (leftHandSide.GetTotalNumberOfLabels() != rightHandSide.GetTotalNumberOfLabels()) { MITK_INFO(verbose) << "Number of labels are not equal."; return false; } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetGroupImage(layerIndex), *rightHandSide.GetGroupImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // label data auto leftLabelsInGroup = leftHandSide.GetLabelValuesByGroup(layerIndex); auto rightLabelsInGroup = rightHandSide.GetLabelValuesByGroup(layerIndex); if (leftLabelsInGroup.size()!=rightLabelsInGroup.size()) { MITK_INFO(verbose) << "Number of layer labels is not equal. Invalid layer:" <; ConstLabelMapType ConvertLabelVectorToMap(const mitk::ConstLabelVector& labelV) { ConstLabelMapType result; for (auto label : labelV) { const auto value = label->GetValue(); auto finding = result.find(value); if (finding != result.end()) mitkThrow() << "Operation failed. Cannot convert label vector into label map, because at least one label value is not unique. Violating label value: " << value; result.insert(std::make_pair(value, label)); } return result; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const ConstLabelMapType& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabels(destinationLabels), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabels == other.m_DestinationLabels; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabels = other.m_DestinationLabels; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto labelFinding = this->m_DestinationLabels.find(existingDestinationValue); if (labelFinding==this->m_DestinationLabels.end() || !labelFinding->second->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: ConstLabelMapType m_DestinationLabels; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(ConvertLabelVectorToMap(destinationLabels), sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } if (!Equal(*(sourceImageAtTimeStep->GetGeometry()), *(destinationImageAtTimeStep->GetGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) { if (IsSubGeometry(*(sourceImageAtTimeStep->GetGeometry()), *(destinationImageAtTimeStep->GetGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) { //we have to pad the source image //because ImageToImageFilters always check for origin matching even if //the requested output region is fitting :( auto padFilter = mitk::PadImageFilter::New(); padFilter->SetInput(0, sourceImageAtTimeStep); padFilter->SetInput(1, destinationImageAtTimeStep); padFilter->SetPadConstant(Label::UNLABELED_VALUE); padFilter->SetBinaryFilter(false); padFilter->Update(); sourceImageAtTimeStep = padFilter->GetOutput(); } else { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; source image has neither the same geometry than destination image nor has the source image a sub geometry."; } } auto destLabelMap = ConvertLabelVectorToMap(destinationLabels); for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UNLABELED_VALUE!=newDestinationLabel && destLabelMap.end() == destLabelMap.find(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabels, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } auto destinationLabels = destinationImage->GetConstLabelsByValue(destinationImage->GetLabelValuesByGroup(destinationImage->GetActiveLayer())); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UNLABELED_VALUE != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, timeStep, LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp index 936e561891..7a4210ee50 100644 --- a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp +++ b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp @@ -1,643 +1,755 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImageVtkMapper2D.h" // MITK #include #include #include -#include -#include -#include -#include -#include #include #include -#include -#include -#include +#include // MITK Rendering -#include "vtkMitkLevelWindowFilter.h" -#include "vtkMitkThickSlicesFilter.h" #include "vtkNeverTranslucentTexture.h" // VTK #include -#include #include #include #include -#include #include -#include #include #include -#include -#include -//#include +#include -// ITK -#include -#include +namespace +{ + itk::ModifiedTimeType PropertyTimeStampIsNewer(const mitk::IPropertyProvider* provider, mitk::BaseRenderer* renderer, const std::string& propName, itk::ModifiedTimeType refMT) + { + const std::string context = renderer != nullptr ? renderer->GetName() : ""; + auto prop = provider->GetConstProperty(propName, context); + if (prop != nullptr) + { + return prop->GetTimeStamp() > refMT; + } + return false; + } +} mitk::LabelSetImageVtkMapper2D::LabelSetImageVtkMapper2D() { } mitk::LabelSetImageVtkMapper2D::~LabelSetImageVtkMapper2D() { } vtkProp *mitk::LabelSetImageVtkMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actors; } mitk::LabelSetImageVtkMapper2D::LocalStorage *mitk::LabelSetImageVtkMapper2D::GetLocalStorage( mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } -void mitk::LabelSetImageVtkMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) +void mitk::LabelSetImageVtkMapper2D::GenerateLookupTable(mitk::BaseRenderer* renderer) { - LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); - mitk::DataNode *node = this->GetDataNode(); - auto *image = dynamic_cast(node->GetData()); + LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); + mitk::DataNode* node = this->GetDataNode(); + auto* image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); - // check if there is a valid worldGeometry - const PlaneGeometry *worldGeometry = renderer->GetCurrentWorldPlaneGeometry(); - if ((worldGeometry == nullptr) || (!worldGeometry->IsValid()) || (!worldGeometry->HasReferenceGeometry())) - return; + localStorage->m_LabelLookupTable = image->GetLookupTable()->Clone(); + const auto labelValues = image->GetAllLabelValues(); - image->Update(); + std::string propertyName = "org.mitk.multilabel.labels.highlighted"; - int numberOfLayers = image->GetNumberOfLayers(); - int activeLayer = image->GetActiveLayer(); + mitk::IntVectorProperty::Pointer prop = dynamic_cast(node->GetNonConstProperty(propertyName)); + if (nullptr != prop) + { + const auto highlightedLabelValues = prop->GetValue(); - float opacity = 1.0f; - node->GetOpacity(opacity, renderer, "opacity"); + if (!highlightedLabelValues.empty()) + { + auto lookUpTable = localStorage->m_LabelLookupTable->GetVtkLookupTable(); + auto highlightEnd = highlightedLabelValues.cend(); + + double rgba[4]; + for (const auto& value : labelValues) + { + lookUpTable->GetTableValue(value, rgba); + if (highlightEnd == std::find(highlightedLabelValues.begin(), highlightedLabelValues.end(), value)) + { //make all none highlighted values more transparent + rgba[3] *= 0.3; + } + else + { + if (rgba[3] != 0) + { //if highlighted values are visible set them to opaque to pop out + rgba[3] = 1.; + } + else + { //if highlighted values are invisible the opacity is increased a bit + //to give a visual hint that the are highlighted but also invisible. + //e.g. needed to see a difference if you change the visibility of + //a highlighted label in the MultiLabelInspector + rgba[3] = 0.4; + } + } + lookUpTable->SetTableValue(value, rgba); + } + localStorage->m_LabelLookupTable->Modified(); + } + } +} - if (numberOfLayers != localStorage->m_NumberOfLayers) +namespace +{ + std::vector GetOutdatedGroups(const mitk::LabelSetImageVtkMapper2D::LocalStorage* ls, const mitk::LabelSetImage* seg) { - localStorage->m_NumberOfLayers = numberOfLayers; - localStorage->m_ReslicedImageVector.clear(); - localStorage->m_ReslicerVector.clear(); - localStorage->m_LayerTextureVector.clear(); - localStorage->m_LevelWindowFilterVector.clear(); - localStorage->m_LayerMapperVector.clear(); - localStorage->m_LayerActorVector.clear(); - - localStorage->m_Actors = vtkSmartPointer::New(); + const auto nrOfGroups = seg->GetNumberOfLayers(); + std::vector result; - for (int lidx = 0; lidx < numberOfLayers; ++lidx) + for (mitk::LabelSetImage::GroupIndexType groupID = 0; groupID < nrOfGroups; ++groupID) { - localStorage->m_ReslicedImageVector.push_back(vtkSmartPointer::New()); - localStorage->m_ReslicerVector.push_back(mitk::ExtractSliceFilter::New()); - localStorage->m_LayerTextureVector.push_back(vtkSmartPointer::New()); - localStorage->m_LevelWindowFilterVector.push_back(vtkSmartPointer::New()); - localStorage->m_LayerMapperVector.push_back(vtkSmartPointer::New()); - localStorage->m_LayerActorVector.push_back(vtkSmartPointer::New()); + const auto groupImage = seg->GetGroupImage(groupID); + if (groupImage->GetMTime() > ls->m_LastDataUpdateTime + || groupImage->GetPipelineMTime() > ls->m_LastDataUpdateTime + || ls->m_GroupImageIDs.size() <= groupID + || groupImage != ls->m_GroupImageIDs[groupID]) + { + result.push_back(groupID); + } + } + return result; + } +} - // do not repeat the texture (the image) - localStorage->m_LayerTextureVector[lidx]->RepeatOff(); +void mitk::LabelSetImageVtkMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) +{ + LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); + mitk::DataNode *node = this->GetDataNode(); + auto *segmentation = dynamic_cast(node->GetData()); + assert(segmentation && segmentation->IsInitialized()); - // set corresponding mappers for the actors - localStorage->m_LayerActorVector[lidx]->SetMapper(localStorage->m_LayerMapperVector[lidx]); + bool isLookupModified = localStorage->m_LabelLookupTable.IsNull() || + (localStorage->m_LabelLookupTable->GetMTime() < segmentation->GetLookupTable()->GetMTime()) || + PropertyTimeStampIsNewer(node, renderer, "org.mitk.multilabel.labels.highlighted", localStorage->m_LabelLookupTable->GetMTime()) || + PropertyTimeStampIsNewer(node, renderer, "opacity", localStorage->m_LabelLookupTable->GetMTime()); - localStorage->m_Actors->AddPart(localStorage->m_LayerActorVector[lidx]); - } + if (isLookupModified) + { + this->GenerateLookupTable(renderer); + } - localStorage->m_Actors->AddPart(localStorage->m_OutlineShadowActor); - localStorage->m_Actors->AddPart(localStorage->m_OutlineActor); + auto outdatedGroups = GetOutdatedGroups(localStorage, segmentation); + + bool isGeometryModified = (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || + (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()); + + bool rendererGeometryIsValid = true; + + // check if there is a valid worldGeometry + if (isGeometryModified) + { + const PlaneGeometry* worldGeometry = renderer->GetCurrentWorldPlaneGeometry(); + rendererGeometryIsValid = worldGeometry != nullptr + && worldGeometry->IsValid() + && worldGeometry->HasReferenceGeometry(); + + isGeometryModified = rendererGeometryIsValid + && localStorage->m_WorldPlane.IsNotNull() && !Equal(*worldGeometry, *(localStorage->m_WorldPlane.GetPointer())); + + localStorage->m_WorldPlane = rendererGeometryIsValid ? worldGeometry->Clone() : nullptr; } - // early out if there is no intersection of the current rendering geometry - // and the geometry of the image that is to be rendered. - if (!RenderingGeometryIntersectsImage(worldGeometry, image->GetSlicedGeometry())) + bool hasValidContent = rendererGeometryIsValid && RenderingGeometryIntersectsImage(localStorage->m_WorldPlane, segmentation->GetSlicedGeometry()); + + if (!hasValidContent && localStorage->m_HasValidContent) { // set image to nullptr, to clear the texture in 3D, because // the latest image is used there if the plane is out of the geometry // see bug-13275 - for (int lidx = 0; lidx < numberOfLayers; ++lidx) + for (unsigned int lidx = 0; lidx < localStorage->m_NumberOfLayers; ++lidx) { localStorage->m_ReslicedImageVector[lidx] = nullptr; localStorage->m_LayerMapperVector[lidx]->SetInputData(localStorage->m_EmptyPolyData); localStorage->m_OutlineActor->SetVisibility(false); localStorage->m_OutlineShadowActor->SetVisibility(false); } + localStorage->m_LastDataUpdateTime.Modified(); + } + + if (isGeometryModified || (hasValidContent && !localStorage->m_HasValidContent)) + { + //if geometry is outdated or we have valid content again + // -> all groups need regeneration + outdatedGroups.resize(segmentation->GetNumberOfLayers()); + std::iota(outdatedGroups.begin(), outdatedGroups.end(), 0); + } + + localStorage->m_HasValidContent = hasValidContent; + + if (!hasValidContent) + { + // early out if there is no intersection of the current rendering geometry + // and the geometry of the image that is to be rendered. return; } - for (int lidx = 0; lidx < numberOfLayers; ++lidx) + if (!outdatedGroups.empty()) { - const auto layerImage = image->GetGroupImage(lidx); + this->GenerateImageSlice(renderer, outdatedGroups); + } - localStorage->m_ReslicerVector[lidx]->SetInput(layerImage); - localStorage->m_ReslicerVector[lidx]->SetWorldGeometry(worldGeometry); - localStorage->m_ReslicerVector[lidx]->SetTimeStep(this->GetTimestep()); + float opacity = 1.0f; + node->GetOpacity(opacity, renderer, "opacity"); + + if (isLookupModified) + { + //if lookup table is modified all groups need a new color mapping + outdatedGroups.resize(segmentation->GetNumberOfLayers()); + std::iota(outdatedGroups.begin(), outdatedGroups.end(), 0); + } + + for (const auto groupID: outdatedGroups) + { + localStorage->m_LayerImageMapToColors[groupID]->SetLookupTable(localStorage->m_LabelLookupTable->GetVtkLookupTable()); + localStorage->m_LayerImageMapToColors[groupID]->SetInputData(localStorage->m_ReslicedImageVector[groupID]); + localStorage->m_LayerImageMapToColors[groupID]->Update(); + + // check for texture interpolation property + bool textureInterpolation = false; + node->GetBoolProperty("texture interpolation", textureInterpolation, renderer); + + // set the interpolation modus according to the property + localStorage->m_LayerTextureVector[groupID]->SetInterpolate(textureInterpolation); + + localStorage->m_LayerTextureVector[groupID]->SetInputConnection( + localStorage->m_LayerImageMapToColors[groupID]->GetOutputPort()); + this->TransformActor(renderer); + + // set the plane as input for the mapper + localStorage->m_LayerMapperVector[groupID]->SetInputConnection(localStorage->m_Plane->GetOutputPort()); + + // set the texture for the actor + localStorage->m_LayerActorVector[groupID]->SetTexture(localStorage->m_LayerTextureVector[groupID]); + localStorage->m_LayerActorVector[groupID]->GetProperty()->SetOpacity(opacity); + } + + auto activeLayer = segmentation->GetActiveLayer(); + bool activeGroupIsOutdated = std::find(outdatedGroups.begin(), outdatedGroups.end(), activeLayer) != outdatedGroups.end(); + + if (activeGroupIsOutdated + || PropertyTimeStampIsNewer(node, renderer, "opacity", localStorage->m_LastActiveLabelUpdateTime.GetMTime()) + || PropertyTimeStampIsNewer(node, renderer, "labelset.contour.active", localStorage->m_LastActiveLabelUpdateTime.GetMTime()) + || PropertyTimeStampIsNewer(node, renderer, "labelset.contour.width", localStorage->m_LastActiveLabelUpdateTime.GetMTime()) + ) + { + this->GenerateActiveLabelOutline(renderer); + } +} + +void mitk::LabelSetImageVtkMapper2D::GenerateImageSlice(mitk::BaseRenderer* renderer, const std::vector& outdatedGroupIDs) +{ + LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); + mitk::DataNode* node = this->GetDataNode(); + auto* segmentation = dynamic_cast(node->GetData()); + assert(segmentation && segmentation->IsInitialized()); + + segmentation->Update(); + + const auto numberOfLayers = segmentation->GetNumberOfLayers(); + + if (numberOfLayers != localStorage->m_NumberOfLayers) + { + if (numberOfLayers > localStorage->m_NumberOfLayers) + { + for (unsigned int lidx = localStorage->m_NumberOfLayers; lidx < numberOfLayers; ++lidx) + { + localStorage->m_GroupImageIDs.push_back(nullptr); + localStorage->m_ReslicedImageVector.push_back(vtkSmartPointer::New()); + localStorage->m_ReslicerVector.push_back(mitk::ExtractSliceFilter::New()); + localStorage->m_LayerTextureVector.push_back(vtkSmartPointer::New()); + localStorage->m_LayerMapperVector.push_back(vtkSmartPointer::New()); + localStorage->m_LayerActorVector.push_back(vtkSmartPointer::New()); + localStorage->m_LayerImageMapToColors.push_back(vtkSmartPointer::New()); + + // do not repeat the texture (the image) + localStorage->m_LayerTextureVector[lidx]->RepeatOff(); + // set corresponding mappers for the actors + localStorage->m_LayerActorVector[lidx]->SetMapper(localStorage->m_LayerMapperVector[lidx]); + } + } + else + { + localStorage->m_GroupImageIDs.resize(numberOfLayers); + localStorage->m_ReslicedImageVector.resize(numberOfLayers); + localStorage->m_ReslicerVector.resize(numberOfLayers); + localStorage->m_LayerTextureVector.resize(numberOfLayers); + localStorage->m_LayerMapperVector.resize(numberOfLayers); + localStorage->m_LayerActorVector.resize(numberOfLayers); + localStorage->m_LayerImageMapToColors.resize(numberOfLayers); + } + localStorage->m_NumberOfLayers = numberOfLayers; + + localStorage->m_Actors = vtkSmartPointer::New(); + + for (unsigned int lidx = 0; lidx < numberOfLayers; ++lidx) + { + localStorage->m_Actors->AddPart(localStorage->m_LayerActorVector[lidx]); + } + localStorage->m_Actors->AddPart(localStorage->m_OutlineShadowActor); + localStorage->m_Actors->AddPart(localStorage->m_OutlineActor); + } + + for (const auto groupID : outdatedGroupIDs) + { + const auto groupImage = segmentation->GetGroupImage(groupID); + localStorage->m_GroupImageIDs[groupID] = groupImage; + + localStorage->m_ReslicerVector[groupID]->SetInput(groupImage); + localStorage->m_ReslicerVector[groupID]->SetWorldGeometry(localStorage->m_WorldPlane); + localStorage->m_ReslicerVector[groupID]->SetTimeStep(this->GetTimestep()); // set the transformation of the image to adapt reslice axis - localStorage->m_ReslicerVector[lidx]->SetResliceTransformByGeometry( - layerImage->GetTimeGeometry()->GetGeometryForTimeStep(this->GetTimestep())); + localStorage->m_ReslicerVector[groupID]->SetResliceTransformByGeometry( + groupImage->GetTimeGeometry()->GetGeometryForTimeStep(this->GetTimestep())); // is the geometry of the slice based on the image image or the worldgeometry? bool inPlaneResampleExtentByGeometry = false; node->GetBoolProperty("in plane resample extent by geometry", inPlaneResampleExtentByGeometry, renderer); - localStorage->m_ReslicerVector[lidx]->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); - localStorage->m_ReslicerVector[lidx]->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); - localStorage->m_ReslicerVector[lidx]->SetVtkOutputRequest(true); + localStorage->m_ReslicerVector[groupID]->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); + localStorage->m_ReslicerVector[groupID]->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); + localStorage->m_ReslicerVector[groupID]->SetVtkOutputRequest(true); // this is needed when thick mode was enabled before. These variables have to be reset to default values - localStorage->m_ReslicerVector[lidx]->SetOutputDimensionality(2); - localStorage->m_ReslicerVector[lidx]->SetOutputSpacingZDirection(1.0); - localStorage->m_ReslicerVector[lidx]->SetOutputExtentZDirection(0, 0); + localStorage->m_ReslicerVector[groupID]->SetOutputDimensionality(2); + localStorage->m_ReslicerVector[groupID]->SetOutputSpacingZDirection(1.0); + localStorage->m_ReslicerVector[groupID]->SetOutputExtentZDirection(0, 0); // Bounds information for reslicing (only required if reference geometry is present) // this used for generating a vtkPLaneSource with the right size double sliceBounds[6]; sliceBounds[0] = 0.0; sliceBounds[1] = 0.0; sliceBounds[2] = 0.0; sliceBounds[3] = 0.0; sliceBounds[4] = 0.0; sliceBounds[5] = 0.0; - localStorage->m_ReslicerVector[lidx]->GetClippedPlaneBounds(sliceBounds); + localStorage->m_ReslicerVector[groupID]->GetClippedPlaneBounds(sliceBounds); // setup the textured plane this->GeneratePlane(renderer, sliceBounds); // get the spacing of the slice - localStorage->m_mmPerPixel = localStorage->m_ReslicerVector[lidx]->GetOutputSpacing(); - localStorage->m_ReslicerVector[lidx]->Modified(); + localStorage->m_mmPerPixel = localStorage->m_ReslicerVector[groupID]->GetOutputSpacing(); + localStorage->m_ReslicerVector[groupID]->Modified(); // start the pipeline with updating the largest possible, needed if the geometry of the image has changed - localStorage->m_ReslicerVector[lidx]->UpdateLargestPossibleRegion(); - localStorage->m_ReslicedImageVector[lidx] = localStorage->m_ReslicerVector[lidx]->GetVtkOutput(); - - const auto *planeGeometry = dynamic_cast(worldGeometry); - - double textureClippingBounds[6]; - for (auto &textureClippingBound : textureClippingBounds) - { - textureClippingBound = 0.0; - } - - // Calculate the actual bounds of the transformed plane clipped by the - // dataset bounding box; this is required for drawing the texture at the - // correct position during 3D mapping. - mitk::PlaneClipping::CalculateClippedPlaneBounds(layerImage->GetGeometry(), planeGeometry, textureClippingBounds); - - textureClippingBounds[0] = static_cast(textureClippingBounds[0] / localStorage->m_mmPerPixel[0] + 0.5); - textureClippingBounds[1] = static_cast(textureClippingBounds[1] / localStorage->m_mmPerPixel[0] + 0.5); - textureClippingBounds[2] = static_cast(textureClippingBounds[2] / localStorage->m_mmPerPixel[1] + 0.5); - textureClippingBounds[3] = static_cast(textureClippingBounds[3] / localStorage->m_mmPerPixel[1] + 0.5); - - // clipping bounds for cutting the imageLayer - localStorage->m_LevelWindowFilterVector[lidx]->SetClippingBounds(textureClippingBounds); - - localStorage->m_LevelWindowFilterVector[lidx]->SetLookupTable( - image->GetLookupTable()->GetVtkLookupTable()); - - // do not use a VTK lookup table (we do that ourselves in m_LevelWindowFilter) - localStorage->m_LayerTextureVector[lidx]->SetColorModeToDirectScalars(); - - // connect the imageLayer with the levelwindow filter - localStorage->m_LevelWindowFilterVector[lidx]->SetInputData(localStorage->m_ReslicedImageVector[lidx]); - // connect the texture with the output of the levelwindow filter - - // check for texture interpolation property - bool textureInterpolation = false; - node->GetBoolProperty("texture interpolation", textureInterpolation, renderer); - - // set the interpolation modus according to the property - localStorage->m_LayerTextureVector[lidx]->SetInterpolate(textureInterpolation); - - localStorage->m_LayerTextureVector[lidx]->SetInputConnection( - localStorage->m_LevelWindowFilterVector[lidx]->GetOutputPort()); + localStorage->m_ReslicerVector[groupID]->UpdateLargestPossibleRegion(); + localStorage->m_ReslicedImageVector[groupID] = localStorage->m_ReslicerVector[groupID]->GetVtkOutput(); + } + localStorage->m_LastDataUpdateTime.Modified(); +} - this->TransformActor(renderer); +void mitk::LabelSetImageVtkMapper2D::GenerateActiveLabelOutline(mitk::BaseRenderer* renderer) +{ + LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); + mitk::DataNode* node = this->GetDataNode(); + auto* image = dynamic_cast(node->GetData()); - // set the plane as input for the mapper - localStorage->m_LayerMapperVector[lidx]->SetInputConnection(localStorage->m_Plane->GetOutputPort()); + int activeLayer = image->GetActiveLayer(); - // set the texture for the actor - localStorage->m_LayerActorVector[lidx]->SetTexture(localStorage->m_LayerTextureVector[lidx]); - localStorage->m_LayerActorVector[lidx]->GetProperty()->SetOpacity(opacity); - } + float opacity = 1.0f; + node->GetOpacity(opacity, renderer, "opacity"); mitk::Label* activeLabel = image->GetActiveLabel(); - if (nullptr != activeLabel) + bool contourActive = false; + node->GetBoolProperty("labelset.contour.active", contourActive, renderer); + if (nullptr != activeLabel && contourActive && activeLabel->GetVisible()) { - bool contourActive = false; - node->GetBoolProperty("labelset.contour.active", contourActive, renderer); - if (contourActive && activeLabel->GetVisible()) //contour rendering - { - //generate contours/outlines - localStorage->m_OutlinePolyData = - this->CreateOutlinePolyData(renderer, localStorage->m_ReslicedImageVector[activeLayer], activeLabel->GetValue()); - localStorage->m_OutlineActor->SetVisibility(true); - localStorage->m_OutlineShadowActor->SetVisibility(true); - const mitk::Color& color = activeLabel->GetColor(); - localStorage->m_OutlineActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); - localStorage->m_OutlineShadowActor->GetProperty()->SetColor(0, 0, 0); - - float contourWidth(2.0); - node->GetFloatProperty("labelset.contour.width", contourWidth, renderer); - localStorage->m_OutlineActor->GetProperty()->SetLineWidth(contourWidth); - localStorage->m_OutlineShadowActor->GetProperty()->SetLineWidth(contourWidth * 1.5); - - localStorage->m_OutlineActor->GetProperty()->SetOpacity(opacity); - localStorage->m_OutlineShadowActor->GetProperty()->SetOpacity(opacity); - - localStorage->m_OutlineMapper->SetInputData(localStorage->m_OutlinePolyData); - return; - } + //generate contours/outlines + localStorage->m_OutlinePolyData = + this->CreateOutlinePolyData(renderer, localStorage->m_ReslicedImageVector[activeLayer], activeLabel->GetValue()); + localStorage->m_OutlineActor->SetVisibility(true); + localStorage->m_OutlineShadowActor->SetVisibility(true); + const mitk::Color& color = activeLabel->GetColor(); + localStorage->m_OutlineActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); + localStorage->m_OutlineShadowActor->GetProperty()->SetColor(0, 0, 0); + + float contourWidth(2.0); + node->GetFloatProperty("labelset.contour.width", contourWidth, renderer); + localStorage->m_OutlineActor->GetProperty()->SetLineWidth(contourWidth); + localStorage->m_OutlineShadowActor->GetProperty()->SetLineWidth(contourWidth * 1.5); + + localStorage->m_OutlineActor->GetProperty()->SetOpacity(opacity); + localStorage->m_OutlineShadowActor->GetProperty()->SetOpacity(opacity); + + localStorage->m_OutlineMapper->SetInputData(localStorage->m_OutlinePolyData); + } + else + { + localStorage->m_OutlineActor->SetVisibility(false); + localStorage->m_OutlineShadowActor->SetVisibility(false); } - localStorage->m_OutlineActor->SetVisibility(false); - localStorage->m_OutlineShadowActor->SetVisibility(false); + localStorage->m_LastActiveLabelUpdateTime.Modified(); } + bool mitk::LabelSetImageVtkMapper2D::RenderingGeometryIntersectsImage(const PlaneGeometry *renderingGeometry, - SlicedGeometry3D *imageGeometry) + const BaseGeometry *imageGeometry) const { // if either one of the two geometries is nullptr we return true // for safety reasons if (renderingGeometry == nullptr || imageGeometry == nullptr) return true; // get the distance for the first cornerpoint ScalarType initialDistance = renderingGeometry->SignedDistance(imageGeometry->GetCornerPoint(0)); for (int i = 1; i < 8; i++) { mitk::Point3D cornerPoint = imageGeometry->GetCornerPoint(i); // get the distance to the other cornerpoints ScalarType distance = renderingGeometry->SignedDistance(cornerPoint); // if it has not the same signing as the distance of the first point if (initialDistance * distance < 0) { // we have an intersection and return true return true; } } // all distances have the same sign, no intersection and we return false return false; } vtkSmartPointer mitk::LabelSetImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer *renderer, vtkImageData *image, int pixelValue) { LocalStorage *localStorage = this->GetLocalStorage(renderer); // get the min and max index values of each direction int *extent = image->GetExtent(); int xMin = extent[0]; int xMax = extent[1]; int yMin = extent[2]; int yMax = extent[3]; int *dims = image->GetDimensions(); // dimensions of the image int line = dims[0]; // how many pixels per line? int x = xMin; // pixel index x int y = yMin; // pixel index y // get the depth for each contour float depth = this->CalculateLayerDepth(renderer); vtkSmartPointer points = vtkSmartPointer::New(); // the points to draw vtkSmartPointer lines = vtkSmartPointer::New(); // the lines to connect the points // We take the pointer to the first pixel of the image auto *currentPixel = static_cast(image->GetScalarPointer()); while (y <= yMax) { // if the current pixel value is set to something if ((currentPixel) && (*currentPixel == pixelValue)) { // check in which direction a line is necessary // a line is added if the neighbor of the current pixel has the value 0 // and if the pixel is located at the edge of the image // if vvvvv not the first line vvvvv if (y > yMin && *(currentPixel - line) != pixelValue) { // x direction - bottom edge of the pixel // add the 2 points vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); // add the line between both points lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the last line vvvvv if (y < yMax && *(currentPixel + line) != pixelValue) { // x direction - top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the first pixel vvvvv if ((x > xMin || y > yMin) && *(currentPixel - 1) != pixelValue) { // y direction - left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the last pixel vvvvv if ((y < yMax || (x < xMax)) && *(currentPixel + 1) != pixelValue) { // y direction - right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } /* now consider pixels at the edge of the image */ // if vvvvv left edge of image vvvvv if (x == xMin) { // draw left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv right edge of image vvvvv if (x == xMax) { // draw right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv bottom edge of image vvvvv if (y == yMin) { // draw bottom edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv top edge of image vvvvv if (y == yMax) { // draw top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } } // end if currentpixel is set x++; if (x > xMax) { // reached end of line x = xMin; y++; } // Increase the pointer-position to the next pixel. // This is safe, as the while-loop and the x-reset logic above makes // sure we do not exceed the bounds of the image currentPixel++; } // end of while // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); return polyData; } -void mitk::LabelSetImageVtkMapper2D::ApplyColor(mitk::BaseRenderer *renderer, const mitk::Color &color) -{ - LocalStorage *localStorage = this->GetLocalStorage(renderer); - localStorage->m_OutlineActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); - localStorage->m_OutlineShadowActor->GetProperty()->SetColor(0, 0, 0); -} - -void mitk::LabelSetImageVtkMapper2D::ApplyOpacity(mitk::BaseRenderer *renderer, int layer) -{ - LocalStorage *localStorage = this->GetLocalStorage(renderer); - float opacity = 1.0f; - this->GetDataNode()->GetOpacity(opacity, renderer, "opacity"); - localStorage->m_LayerActorVector[layer]->GetProperty()->SetOpacity(opacity); - localStorage->m_OutlineActor->GetProperty()->SetOpacity(opacity); - localStorage->m_OutlineShadowActor->GetProperty()->SetOpacity(opacity); -} - -void mitk::LabelSetImageVtkMapper2D::ApplyLookuptable(mitk::BaseRenderer *renderer, int layer) -{ - LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); - auto *input = dynamic_cast(this->GetDataNode()->GetData()); - localStorage->m_LevelWindowFilterVector[layer]->SetLookupTable( - input->GetLookupTable()->GetVtkLookupTable()); -} - void mitk::LabelSetImageVtkMapper2D::Update(mitk::BaseRenderer *renderer) { bool visible = true; const DataNode *node = this->GetDataNode(); node->GetVisibility(visible, renderer, "visible"); if (!visible) return; auto *image = dynamic_cast(node->GetData()); if (image == nullptr || image->IsInitialized() == false) return; // Calculate time step of the image data for the specified renderer (integer value) this->CalculateTimeStep(renderer); // Check if time step is valid const TimeGeometry *dataTimeGeometry = image->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || (!dataTimeGeometry->IsValidTimeStep(this->GetTimestep()))) { return; } image->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // check if something important has changed and we need to re-render - if ((localStorage->m_LastDataUpdateTime < image->GetMTime()) || + if (localStorage->m_LabelLookupTable.IsNull() || + (localStorage->m_LabelLookupTable->GetMTime() < image->GetLookupTable()->GetMTime()) || + (localStorage->m_LastDataUpdateTime < image->GetMTime()) || (localStorage->m_LastDataUpdateTime < image->GetPipelineMTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || - (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime())) + (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || + (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList()->GetMTime()) || + (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || + (localStorage->m_LastPropertyUpdateTime < image->GetPropertyList()->GetMTime())) { this->GenerateDataForRenderer(renderer); - localStorage->m_LastDataUpdateTime.Modified(); + localStorage->m_LastPropertyUpdateTime.Modified(); } else if ((localStorage->m_LastPropertyUpdateTime < node->GetPropertyList()->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < image->GetPropertyList()->GetMTime())) { this->GenerateDataForRenderer(renderer); localStorage->m_LastPropertyUpdateTime.Modified(); } } // set the two points defining the textured plane according to the dimension and spacing void mitk::LabelSetImageVtkMapper2D::GeneratePlane(mitk::BaseRenderer *renderer, double planeBounds[6]) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); float depth = this->CalculateLayerDepth(renderer); // Set the origin to (xMin; yMin; depth) of the plane. This is necessary for obtaining the correct // plane size in crosshair rotation and swivel mode. localStorage->m_Plane->SetOrigin(planeBounds[0], planeBounds[2], depth); // These two points define the axes of the plane in combination with the origin. // Point 1 is the x-axis and point 2 the y-axis. // Each plane is transformed according to the view (axial, coronal and sagittal) afterwards. localStorage->m_Plane->SetPoint1(planeBounds[1], planeBounds[2], depth); // P1: (xMax, yMin, depth) localStorage->m_Plane->SetPoint2(planeBounds[0], planeBounds[3], depth); // P2: (xMin, yMax, depth) } float mitk::LabelSetImageVtkMapper2D::CalculateLayerDepth(mitk::BaseRenderer *renderer) { // get the clipping range to check how deep into z direction we can render images double maxRange = renderer->GetVtkRenderer()->GetActiveCamera()->GetClippingRange()[1]; // Due to a VTK bug, we cannot use the whole clipping range. /100 is empirically determined float depth = -maxRange * 0.01; // divide by 100 int layer = 0; GetDataNode()->GetIntProperty("layer", layer, renderer); // add the layer property for each image to render images with a higher layer on top of the others depth += layer * 10; //*10: keep some room for each image (e.g. for ODFs in between) if (depth > 0.0f) { depth = 0.0f; MITK_WARN << "Layer value exceeds clipping range. Set to minimum instead."; } return depth; } void mitk::LabelSetImageVtkMapper2D::TransformActor(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // get the transformation matrix of the reslicer in order to render the slice as axial, coronal or sagittal vtkSmartPointer trans = vtkSmartPointer::New(); vtkSmartPointer matrix = localStorage->m_ReslicerVector[0]->GetResliceAxes(); // same for all layers trans->SetMatrix(matrix); - for (int lidx = 0; lidx < localStorage->m_NumberOfLayers; ++lidx) + for (unsigned int lidx = 0; lidx < localStorage->m_NumberOfLayers; ++lidx) { // transform the plane/contour (the actual actor) to the corresponding view (axial, coronal or sagittal) localStorage->m_LayerActorVector[lidx]->SetUserTransform(trans); // transform the origin to center based coordinates, because MITK is center based. localStorage->m_LayerActorVector[lidx]->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); } // same for outline actor localStorage->m_OutlineActor->SetUserTransform(trans); localStorage->m_OutlineActor->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); // same for outline shadow actor localStorage->m_OutlineShadowActor->SetUserTransform(trans); localStorage->m_OutlineShadowActor->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); } void mitk::LabelSetImageVtkMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { // add/replace the following properties node->SetProperty("opacity", FloatProperty::New(1.0f), renderer); node->SetProperty("binary", BoolProperty::New(false), renderer); - mitk::RenderingModeProperty::Pointer renderingModeProperty = - mitk::RenderingModeProperty::New(RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); - node->SetProperty("Image Rendering.Mode", renderingModeProperty, renderer); - - mitk::LevelWindow levelwindow(32767.5, 65535); - mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(levelwindow); - - levWinProp->SetLevelWindow(levelwindow); - node->SetProperty("levelwindow", levWinProp, renderer); - node->SetProperty("labelset.contour.active", BoolProperty::New(true), renderer); node->SetProperty("labelset.contour.width", FloatProperty::New(2.0), renderer); Superclass::SetDefaultProperties(node, renderer, overwrite); } mitk::LabelSetImageVtkMapper2D::LocalStorage::~LocalStorage() { } mitk::LabelSetImageVtkMapper2D::LocalStorage::LocalStorage() { // Do as much actions as possible in here to avoid double executions. m_Plane = vtkSmartPointer::New(); m_Actors = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_EmptyPolyData = vtkSmartPointer::New(); m_OutlineActor = vtkSmartPointer::New(); m_OutlineMapper = vtkSmartPointer::New(); m_OutlineShadowActor = vtkSmartPointer::New(); + m_HasValidContent = false; m_NumberOfLayers = 0; m_mmPerPixel = nullptr; m_OutlineActor->SetMapper(m_OutlineMapper); m_OutlineShadowActor->SetMapper(m_OutlineMapper); m_OutlineActor->SetVisibility(false); m_OutlineShadowActor->SetVisibility(false); } diff --git a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.h b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.h index a5363284fa..c213b7fff5 100644 --- a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.h +++ b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.h @@ -1,241 +1,231 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLabelSetImageVtkMapper2D_h #define mitkLabelSetImageVtkMapper2D_h // MITK #include "MitkMultilabelExports.h" #include "mitkCommon.h" // MITK Rendering #include "mitkBaseRenderer.h" #include "mitkExtractSliceFilter.h" #include "mitkLabelSetImage.h" #include "mitkVtkMapper.h" // VTK #include class vtkActor; class vtkPolyDataMapper; class vtkPlaneSource; class vtkImageData; class vtkLookupTable; class vtkImageReslice; class vtkPoints; class vtkMitkThickSlicesFilter; class vtkPolyData; -class vtkMitkLevelWindowFilter; class vtkNeverTranslucentTexture; +class vtkImageMapToColors; namespace mitk { /** \brief Mapper to resample and display 2D slices of a 3D labelset image. * * Properties that can be set for labelset images and influence this mapper are: * * - \b "labelset.contour.active": (BoolProperty) whether to show only the active label as a contour or not * - \b "labelset.contour.width": (FloatProperty) line width of the contour * The default properties are: * - \b "labelset.contour.active", mitk::BoolProperty::New( true ), renderer, overwrite ) * - \b "labelset.contour.width", mitk::FloatProperty::New( 2.0 ), renderer, overwrite ) * \ingroup Mapper */ class MITKMULTILABEL_EXPORT LabelSetImageVtkMapper2D : public VtkMapper { public: /** Standard class typedefs. */ mitkClassMacro(LabelSetImageVtkMapper2D, VtkMapper); /** Method for creation through the object factory. */ itkNewMacro(Self); /** \brief Get the Image to map */ const mitk::Image *GetInput(void); /** \brief Checks whether this mapper needs to update itself and generate * data. */ void Update(mitk::BaseRenderer *renderer) override; //### methods of MITK-VTK rendering pipeline vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; //### end of methods of MITK-VTK rendering pipeline /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ /** * To render axial, coronal, and sagittal, the mapper is called three times. * For performance reasons, the corresponding data for each view is saved in the * internal helper class LocalStorage. This allows rendering n views with just * 1 mitkMapper using n vtkMapper. * */ class MITKMULTILABEL_EXPORT LocalStorage : public mitk::Mapper::BaseLocalStorage { public: vtkSmartPointer m_Actors; + /** Vector containing the pointer of the currently used group images. + * IMPORTANT: This member must not be used to access any data. + * Its purpose is to allow checking if the order of the groups has changed + * in order to adapt the pipe line accordingly*/ + std::vector m_GroupImageIDs; + std::vector> m_LayerActorVector; std::vector> m_LayerMapperVector; std::vector> m_ReslicedImageVector; + std::vector> m_LayerImageMapToColors; std::vector> m_LayerTextureVector; vtkSmartPointer m_EmptyPolyData; vtkSmartPointer m_Plane; std::vector m_ReslicerVector; vtkSmartPointer m_OutlinePolyData; /** \brief An actor for the outline */ vtkSmartPointer m_OutlineActor; /** \brief An actor for the outline shadow*/ vtkSmartPointer m_OutlineShadowActor; /** \brief A mapper for the outline */ vtkSmartPointer m_OutlineMapper; /** \brief Timestamp of last update of stored data. */ itk::TimeStamp m_LastDataUpdateTime; - /** \brief Timestamp of last update of a property. */ itk::TimeStamp m_LastPropertyUpdateTime; + /** \brief Timestamp of last update of a property. */ + itk::TimeStamp m_LastActiveLabelUpdateTime; /** \brief mmPerPixel relation between pixel and mm. (World spacing).*/ mitk::ScalarType *m_mmPerPixel; - int m_NumberOfLayers; + /** look up table for label colors. */ + mitk::LookupTable::Pointer m_LabelLookupTable; + + mitk::PlaneGeometry::Pointer m_WorldPlane; + bool m_HasValidContent; - /** \brief This filter is used to apply the level window to Grayvalue and RBG(A) images. */ - // vtkSmartPointer m_LevelWindowFilter; - std::vector> m_LevelWindowFilterVector; + unsigned int m_NumberOfLayers; /** \brief Default constructor of the local storage. */ LocalStorage(); - /** \brief Default deconstructor of the local storage. */ + /** \brief Default destructor of the local storage. */ ~LocalStorage() override; }; /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ mitk::LocalStorageHandler m_LSH; /** \brief Get the LocalStorage corresponding to the current renderer. */ LocalStorage *GetLocalStorage(mitk::BaseRenderer *renderer); /** \brief Set the default properties for general image rendering. */ static void SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer = nullptr, bool overwrite = false); /** \brief This method switches between different rendering modes (e.g. use a lookup table or a transfer function). * Detailed documentation about the modes can be found here: \link mitk::RenderingModeProperty \endlink */ void ApplyRenderingMode(mitk::BaseRenderer *renderer); protected: /** \brief Transforms the actor to the actual position in 3D. * \param renderer The current renderer corresponding to the render window. */ void TransformActor(mitk::BaseRenderer *renderer); /** \brief Generates a plane according to the size of the resliced image in milimeters. * * In VTK a vtkPlaneSource is defined through three points. The origin and two * points defining the axes of the plane (see VTK documentation). The origin is * set to (xMin; yMin; Z), where xMin and yMin are the minimal bounds of the * resliced image in space. Z is relevant for blending and the layer property. * The center of the plane (C) is also the center of the view plane (cf. the image above). * * \note For the standard MITK view with three 2D render windows showing three * different slices, three such planes are generated. All these planes are generated * in the XY-plane (even if they depict a YZ-slice of the volume). * */ void GeneratePlane(mitk::BaseRenderer *renderer, double planeBounds[6]); /** \brief Generates a vtkPolyData object containing the outline of a given binary slice. \param renderer Pointer to the renderer containing the needed information \param image \param pixelValue \note This code is based on code from the iil library. */ vtkSmartPointer CreateOutlinePolyData(mitk::BaseRenderer *renderer, vtkImageData *image, int pixelValue = 1); /** Default constructor */ LabelSetImageVtkMapper2D(); /** Default deconstructor */ ~LabelSetImageVtkMapper2D() override; /** \brief Does the actual resampling, without rendering the image yet. * All the data is generated inside this method. The vtkProp (or Actor) * is filled with content (i.e. the resliced image). * * After generation, a 4x4 transformation matrix(t) of the current slice is obtained * from the vtkResliceImage object via GetReslicesAxis(). This matrix is * applied to each textured plane (actor->SetUserTransform(t)) to transform everything * to the actual 3D position (cf. the following image). * * \image html cameraPositioning3D.png * */ void GenerateDataForRenderer(mitk::BaseRenderer *renderer) override; + void GenerateImageSlice(mitk::BaseRenderer* renderer, const std::vector& outdatedGroupIDs); + + void GenerateActiveLabelOutline(mitk::BaseRenderer* renderer); + + /** \brief Generates the look up table that should be used. + */ + void GenerateLookupTable(mitk::BaseRenderer* renderer); + /** \brief This method uses the vtkCamera clipping range and the layer property * to calcualte the depth of the object (e.g. image or contour). The depth is used * to keep the correct order for the final VTK rendering.*/ float CalculateLayerDepth(mitk::BaseRenderer *renderer); - /** \brief This method applies (or modifies) the lookuptable for all types of images. - * \warning To use the lookup table, the property 'Lookup Table' must be set and a 'Image Rendering.Mode' - * which uses the lookup table must be set. - */ - void ApplyLookuptable(mitk::BaseRenderer *renderer, int layer); - - /** \brief This method applies a color transfer function. - * Internally, a vtkColorTransferFunction is used. This is usefull for coloring continous - * images (e.g. float) - * \warning To use the color transfer function, the property 'Image Rendering.Transfer Function' must be set and a - * 'Image Rendering.Mode' which uses the color transfer function must be set. - */ - void ApplyColorTransferFunction(mitk::BaseRenderer *renderer); - - /** - * @brief ApplyLevelWindow Apply the level window for the given renderer. - * \warning To use the level window, the property 'LevelWindow' must be set and a 'Image Rendering.Mode' which uses - * the level window must be set. - * @param renderer Level window for which renderer? - */ - void ApplyLevelWindow(mitk::BaseRenderer *renderer); - - /** \brief Set the color of the image/polydata */ - void ApplyColor(mitk::BaseRenderer *renderer, const mitk::Color &color); - - /** \brief Set the opacity of the actor. */ - void ApplyOpacity(mitk::BaseRenderer *renderer, int layer); - /** * \brief Calculates whether the given rendering geometry intersects the * given SlicedGeometry3D. * * This method checks if the given Geometry2D intersects the given * SlicedGeometry3D. It calculates the distance of the Geometry2D to all * 8 cornerpoints of the SlicedGeometry3D. If all distances have the same * sign (all positive or all negative) there is no intersection. * If the distances have different sign, there is an intersection. **/ - bool RenderingGeometryIntersectsImage(const PlaneGeometry *renderingGeometry, SlicedGeometry3D *imageGeometry); + bool RenderingGeometryIntersectsImage(const PlaneGeometry *renderingGeometry, const BaseGeometry* imageGeometry) const; }; } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.cpp b/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.cpp new file mode 100644 index 0000000000..27a5d47030 --- /dev/null +++ b/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.cpp @@ -0,0 +1,350 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkMultiLabelSegmentationVtkMapper3D.h" + +// MITK +#include +#include +#include + +// MITK Rendering + +// VTK +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ + itk::ModifiedTimeType PropertyTimeStampIsNewer(const mitk::IPropertyProvider* provider, mitk::BaseRenderer* renderer, const std::string& propName, itk::ModifiedTimeType refMT) + { + const std::string context = renderer != nullptr ? renderer->GetName() : ""; + auto prop = provider->GetConstProperty(propName, context); + if (prop != nullptr) + { + return prop->GetTimeStamp() > refMT; + } + return false; + } +} + +mitk::MultiLabelSegmentationVtkMapper3D::MultiLabelSegmentationVtkMapper3D() +{ +} + +mitk::MultiLabelSegmentationVtkMapper3D::~MultiLabelSegmentationVtkMapper3D() +{ +} + +vtkProp *mitk::MultiLabelSegmentationVtkMapper3D::GetVtkProp(mitk::BaseRenderer *renderer) +{ + // return the actor corresponding to the renderer + return m_LSH.GetLocalStorage(renderer)->m_Actors; +} + +mitk::MultiLabelSegmentationVtkMapper3D::LocalStorage *mitk::MultiLabelSegmentationVtkMapper3D::GetLocalStorage( + mitk::BaseRenderer *renderer) +{ + return m_LSH.GetLocalStorage(renderer); +} + +void mitk::MultiLabelSegmentationVtkMapper3D::GenerateLookupTable(mitk::BaseRenderer* renderer) +{ + LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); + mitk::DataNode* node = this->GetDataNode(); + auto* image = dynamic_cast(node->GetData()); + assert(image && image->IsInitialized()); + + localStorage->m_LabelLookupTable = image->GetLookupTable()->Clone(); + auto lookUpTable = localStorage->m_LabelLookupTable->GetVtkLookupTable(); + + const auto labelValues = image->GetAllLabelValues(); + + std::string propertyName = "org.mitk.multilabel.labels.highlighted"; + + mitk::IntVectorProperty::Pointer prop = dynamic_cast(node->GetNonConstProperty(propertyName)); + if (nullptr != prop) + { + const auto highlightedLabelValues = prop->GetValue(); + + if (!highlightedLabelValues.empty()) + { + auto highlightEnd = highlightedLabelValues.cend(); + + double rgba[4]; + for (const auto& value : labelValues) + { + lookUpTable->GetTableValue(value, rgba); + if (highlightEnd == std::find(highlightedLabelValues.begin(), highlightedLabelValues.end(), value)) + { //make all none highlighted values more transparent + rgba[3] *= 0.05; + } + else + { + if (rgba[3] != 0) + { //if highlighted values are visible set them to opaque to pop out + rgba[3] = 1.; + } + else + { //if highlighted values are invisible the opacity is increased a bit + //to give a visual hint that the are highlighted but also invisible. + //e.g. needed to see a difference if you change the visibility of + //a highlighted label in the MultiLabelInspector + rgba[3] = 0.2; + } + } + lookUpTable->SetTableValue(value, rgba); + } + localStorage->m_LabelLookupTable->Modified(); // need to call modified, since LookupTableProperty seems to be unchanged so no widget-update is + // executed + } + } + + const auto nrOfGroups = image->GetNumberOfLayers(); + for (unsigned int groupID = 0; groupID < nrOfGroups; ++groupID) + { + localStorage->m_TransferFunctions[groupID] = vtkSmartPointer::New(); + localStorage->m_OpacityTransferFunctions[groupID] = vtkSmartPointer::New(); + + localStorage->m_TransferFunctions[groupID]->AddRGBPoint(0, 0., 0., 1.); + localStorage->m_OpacityTransferFunctions[groupID]->AddPoint(0, 0.); + + for (const auto& value : image->GetLabelValuesByGroup(groupID)) + { + double* color = lookUpTable->GetTableValue(value); + localStorage->m_TransferFunctions[groupID]->AddRGBPoint(value, color[0], color[1], color[2]); + + localStorage->m_OpacityTransferFunctions[groupID]->AddPoint(value, color[3]); + } + } +} + +namespace +{ + std::vector GetOutdatedGroups(const mitk::MultiLabelSegmentationVtkMapper3D::LocalStorage* ls, const mitk::LabelSetImage* seg) + { + const auto nrOfGroups = seg->GetNumberOfLayers(); + std::vector result; + + for (mitk::LabelSetImage::GroupIndexType groupID = 0; groupID < nrOfGroups; ++groupID) + { + const auto groupImage = seg->GetGroupImage(groupID); + if (groupImage->GetMTime() > ls->m_LastDataUpdateTime + || groupImage->GetPipelineMTime() > ls->m_LastDataUpdateTime + || ls->m_GroupImageIDs.size() <= groupID + || groupImage != ls->m_GroupImageIDs[groupID]) + { + result.push_back(groupID); + } + } + return result; + } +} + +void mitk::MultiLabelSegmentationVtkMapper3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) +{ + LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); + mitk::DataNode *node = this->GetDataNode(); + auto *image = dynamic_cast(node->GetData()); + assert(image && image->IsInitialized()); + + bool isLookupModified = localStorage->m_LabelLookupTable.IsNull() || + (localStorage->m_LabelLookupTable->GetMTime() < image->GetLookupTable()->GetMTime()) || + PropertyTimeStampIsNewer(node, renderer, "org.mitk.multilabel.labels.highlighted", localStorage->m_LabelLookupTable->GetMTime()) || + PropertyTimeStampIsNewer(node, renderer, "opacity", localStorage->m_LabelLookupTable->GetMTime()); + + auto outdatedGroups = GetOutdatedGroups(localStorage, image); + + bool isGeometryModified = (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || + (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()); + + if (isGeometryModified) + { + //if geometry is outdated all groups need regeneration + outdatedGroups.resize(image->GetNumberOfLayers()); + std::iota(outdatedGroups.begin(), outdatedGroups.end(), 0); + } + + if (!outdatedGroups.empty()) + { + auto hasValidContent = this->GenerateVolumeMapping(renderer, outdatedGroups); + if (!hasValidContent) return; + } + + if (isLookupModified) + { + this->GenerateLookupTable(renderer); + } + + if (isLookupModified) + { + //if lookup table is modified all groups need a new color mapping + outdatedGroups.resize(image->GetNumberOfLayers()); + std::iota(outdatedGroups.begin(), outdatedGroups.end(), 0); + } + + for (const auto groupID : outdatedGroups) + { + localStorage->m_LayerVolumes[groupID]->GetProperty()->SetColor(localStorage->m_TransferFunctions[groupID]); + localStorage->m_LayerVolumes[groupID]->GetProperty()->SetScalarOpacity(localStorage->m_OpacityTransferFunctions[groupID]); + localStorage->m_LayerVolumes[groupID]->Update(); + } +} + +bool mitk::MultiLabelSegmentationVtkMapper3D::GenerateVolumeMapping(mitk::BaseRenderer* renderer, const std::vector& outdatedGroupIDs) +{ + LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); + mitk::DataNode* node = this->GetDataNode(); + auto* image = dynamic_cast(node->GetData()); + assert(image && image->IsInitialized()); + + image->Update(); + + const auto numberOfGroups = image->GetNumberOfLayers(); + + if (numberOfGroups != localStorage->m_NumberOfGroups) + { + if (numberOfGroups > localStorage->m_NumberOfGroups) + { + for (unsigned int groupID = localStorage->m_NumberOfGroups; groupID < numberOfGroups; ++groupID) + { + localStorage->m_GroupImageIDs.push_back(nullptr); + localStorage->m_LayerImages.push_back(vtkSmartPointer::New()); + localStorage->m_LayerVolumeMappers.push_back(vtkSmartPointer::New()); + localStorage->m_LayerVolumes.push_back(vtkSmartPointer::New()); + + localStorage->m_TransferFunctions.push_back(vtkSmartPointer::New()); + localStorage->m_OpacityTransferFunctions.push_back(vtkSmartPointer::New()); + } + } + else + { + localStorage->m_GroupImageIDs.resize(numberOfGroups); + localStorage->m_LayerImages.resize(numberOfGroups); + localStorage->m_LayerVolumeMappers.resize(numberOfGroups); + localStorage->m_LayerVolumes.resize(numberOfGroups); + + localStorage->m_TransferFunctions.resize(numberOfGroups); + localStorage->m_OpacityTransferFunctions.resize(numberOfGroups); + } + + localStorage->m_NumberOfGroups = numberOfGroups; + + localStorage->m_Actors = vtkSmartPointer::New(); + + for (unsigned int groupID = 0; groupID < numberOfGroups; ++groupID) + { + localStorage->m_Actors->AddPart(localStorage->m_LayerVolumes[groupID]); + } + } + + for (const auto groupID : outdatedGroupIDs) + { + const auto groupImage = image->GetGroupImage(groupID); + localStorage->m_GroupImageIDs[groupID] = groupImage; + + localStorage->m_LayerImages[groupID] = groupImage->GetVtkImageData(this->GetTimestep()); + + //need to recreate the volumeMapper because otherwise label data was still rendered even + //if a label was removed. There must be a cleaner way to do it. Exchanging the whole mapper + //is a ugly workaround for now. + localStorage->m_LayerVolumeMappers[groupID] = vtkSmartPointer::New(); + + localStorage->m_LayerVolumeMappers[groupID]->SetInputData(localStorage->m_LayerImages[groupID]); + + localStorage->m_LayerVolumes[groupID]->GetProperty()->ShadeOn(); + localStorage->m_LayerVolumes[groupID]->GetProperty()->SetDiffuse(1.0); + localStorage->m_LayerVolumes[groupID]->GetProperty()->SetAmbient(0.4); + localStorage->m_LayerVolumes[groupID]->GetProperty()->SetSpecular(0.2); + localStorage->m_LayerVolumes[groupID]->GetProperty()->SetInterpolationTypeToNearest(); + + localStorage->m_LayerVolumes[groupID]->SetMapper(localStorage->m_LayerVolumeMappers[groupID]); + } + localStorage->m_LastDataUpdateTime.Modified(); + return true; +} + +void mitk::MultiLabelSegmentationVtkMapper3D::Update(mitk::BaseRenderer *renderer) +{ + bool visible = true; + bool has3Dvisualize = true; + const DataNode *node = this->GetDataNode(); + node->GetVisibility(visible, renderer, "visible"); + node->GetBoolProperty("multilabel.3D.visualize", has3Dvisualize, renderer); + if (!visible || !has3Dvisualize) + return; + + auto *image = dynamic_cast(node->GetData()); + + if (image == nullptr || image->IsInitialized() == false) + return; + + // Calculate time step of the image data for the specified renderer (integer value) + this->CalculateTimeStep(renderer); + + // Check if time step is valid + const TimeGeometry *dataTimeGeometry = image->GetTimeGeometry(); + if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || + (!dataTimeGeometry->IsValidTimeStep(this->GetTimestep()))) + { + return; + } + + image->UpdateOutputInformation(); + LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); + + // check if something important has changed and we need to re-render + + if (localStorage->m_LabelLookupTable.IsNull() || + (localStorage->m_LabelLookupTable->GetMTime() < image->GetLookupTable()->GetMTime()) || + (localStorage->m_LastDataUpdateTime < image->GetMTime()) || + (localStorage->m_LastDataUpdateTime < image->GetPipelineMTime()) || + (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || + (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || + (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList()->GetMTime()) || + (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || + (localStorage->m_LastPropertyUpdateTime < image->GetPropertyList()->GetMTime())) + { + this->GenerateDataForRenderer(renderer); + localStorage->m_LastPropertyUpdateTime.Modified(); + } +} + +void mitk::MultiLabelSegmentationVtkMapper3D::SetDefaultProperties(mitk::DataNode *node, + mitk::BaseRenderer *renderer, + bool overwrite) +{ + Superclass::SetDefaultProperties(node, renderer, overwrite); + + // add/replace the following properties + node->SetProperty("multilabel.3D.visualize", BoolProperty::New(false), renderer); +} + +mitk::MultiLabelSegmentationVtkMapper3D::LocalStorage::~LocalStorage() +{ +} + +mitk::MultiLabelSegmentationVtkMapper3D::LocalStorage::LocalStorage() +{ + // Do as much actions as possible in here to avoid double executions. + m_Actors = vtkSmartPointer::New(); + + m_NumberOfGroups = 0; +} diff --git a/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.h b/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.h new file mode 100644 index 0000000000..043096f2a6 --- /dev/null +++ b/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.h @@ -0,0 +1,149 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkMultiLabelSegmentationVtkMapper3D_h +#define mitkMultiLabelSegmentationVtkMapper3D_h + +// MITK +#include "MitkMultilabelExports.h" +#include "mitkCommon.h" + +// MITK Rendering +#include "mitkBaseRenderer.h" +#include "mitkExtractSliceFilter.h" +#include "mitkLabelSetImage.h" +#include "mitkVtkMapper.h" + +// VTK +#include + +class vtkPolyDataMapper; +class vtkImageData; +class vtkLookupTable; +class vtkVolumeProperty; +class vtkVolume; +class vtkSmartVolumeMapper; + +namespace mitk +{ + + /** \brief Mapper to resample and display 2D slices of a 3D labelset image. + * + * Properties that can be set for labelset images and influence this mapper are: + * + * - \b "labelset.contour.active": (BoolProperty) whether to show only the active label as a contour or not + * - \b "labelset.contour.width": (FloatProperty) line width of the contour + + * The default properties are: + + * - \b "labelset.contour.active", mitk::BoolProperty::New( true ), renderer, overwrite ) + * - \b "labelset.contour.width", mitk::FloatProperty::New( 2.0 ), renderer, overwrite ) + + * \ingroup Mapper + */ + class MITKMULTILABEL_EXPORT MultiLabelSegmentationVtkMapper3D : public VtkMapper + { + public: + /** Standard class typedefs. */ + mitkClassMacro(MultiLabelSegmentationVtkMapper3D, VtkMapper); + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** \brief Checks whether this mapper needs to update itself and generate + * data. */ + void Update(mitk::BaseRenderer *renderer) override; + + //### methods of MITK-VTK rendering pipeline + vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; + //### end of methods of MITK-VTK rendering pipeline + + /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ + /** + * To render axial, coronal, and sagittal, the mapper is called three times. + * For performance reasons, the corresponding data for each view is saved in the + * internal helper class LocalStorage. This allows rendering n views with just + * 1 mitkMapper using n vtkMapper. + * */ + class MITKMULTILABEL_EXPORT LocalStorage : public mitk::Mapper::BaseLocalStorage + { + public: + vtkSmartPointer m_Actors; + + std::vector> m_LayerVolumeMappers; + std::vector> m_LayerImages; + std::vector> m_LayerVolumes; + + std::vector > m_TransferFunctions; + std::vector > m_OpacityTransferFunctions; + + /** Vector containing the pointer of the currently used group images. + * IMPORTANT: This member must not be used to access any data. + * Its purpose is to allow checking if the order of the groups has changed + * in order to adapt the pipe line accordingly*/ + std::vector m_GroupImageIDs; + + /** \brief Timestamp of last update of stored data. */ + itk::TimeStamp m_LastDataUpdateTime; + /** \brief Timestamp of last update of a property. */ + itk::TimeStamp m_LastPropertyUpdateTime; + + /** look up table for label colors. */ + mitk::LookupTable::Pointer m_LabelLookupTable; + + unsigned int m_NumberOfGroups; + + /** \brief Default constructor of the local storage. */ + LocalStorage(); + /** \brief Default destructor of the local storage. */ + ~LocalStorage() override; + }; + + /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ + mitk::LocalStorageHandler m_LSH; + + /** \brief Get the LocalStorage corresponding to the current renderer. */ + LocalStorage *GetLocalStorage(mitk::BaseRenderer *renderer); + + /** \brief Set the default properties for general image rendering. */ + static void SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer = nullptr, bool overwrite = false); + + protected: + /** Default constructor */ + MultiLabelSegmentationVtkMapper3D(); + /** Default deconstructor */ + ~MultiLabelSegmentationVtkMapper3D() override; + + /** \brief Does the actual resampling, without rendering the image yet. + * All the data is generated inside this method. The vtkProp (or Actor) + * is filled with content (i.e. the resliced image). + * + * After generation, a 4x4 transformation matrix(t) of the current slice is obtained + * from the vtkResliceImage object via GetReslicesAxis(). This matrix is + * applied to each textured plane (actor->SetUserTransform(t)) to transform everything + * to the actual 3D position (cf. the following image). + * + * \image html cameraPositioning3D.png + * + */ + void GenerateDataForRenderer(mitk::BaseRenderer *renderer) override; + + bool GenerateVolumeMapping(mitk::BaseRenderer* renderer, const std::vector& outdatedGroupIDs); + + /** \brief Generates the look up table that should be used. + */ + void GenerateLookupTable(mitk::BaseRenderer* renderer); + }; + +} // namespace mitk + +#endif diff --git a/Modules/Multilabel/mitkMultilabelObjectFactory.cpp b/Modules/Multilabel/mitkMultilabelObjectFactory.cpp index 03be75a0e8..6e0e75742a 100644 --- a/Modules/Multilabel/mitkMultilabelObjectFactory.cpp +++ b/Modules/Multilabel/mitkMultilabelObjectFactory.cpp @@ -1,124 +1,134 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMultilabelObjectFactory.h" #include "mitkBaseRenderer.h" #include "mitkCoreObjectFactory.h" #include "mitkDataNode.h" #include "mitkProperties.h" #include #include #include +#include #include mitk::MultilabelObjectFactory::MultilabelObjectFactory() : CoreObjectFactoryBase() { static bool alreadyDone = false; if (!alreadyDone) { MITK_DEBUG << "MultilabelObjectFactory c'tor" << std::endl; CreateFileExtensionsMap(); alreadyDone = true; } } mitk::MultilabelObjectFactory::~MultilabelObjectFactory() { } mitk::Mapper::Pointer mitk::MultilabelObjectFactory::CreateMapper(mitk::DataNode *node, MapperSlotId id) { mitk::Mapper::Pointer newMapper = nullptr; mitk::BaseData *data = node->GetData(); if (id == mitk::BaseRenderer::Standard2D) { if ((dynamic_cast(data) != nullptr)) { newMapper = mitk::LabelSetImageVtkMapper2D::New(); newMapper->SetDataNode(node); } } + else if (id == mitk::BaseRenderer::Standard3D) + { + if ((dynamic_cast(data) != nullptr)) + { + newMapper = mitk::MultiLabelSegmentationVtkMapper3D::New(); + newMapper->SetDataNode(node); + } + } return newMapper; } void mitk::MultilabelObjectFactory::SetDefaultProperties(mitk::DataNode *node) { if (node == nullptr) return; if (node->GetData() == nullptr) return; if (dynamic_cast(node->GetData()) != nullptr) { mitk::LabelSetImageVtkMapper2D::SetDefaultProperties(node); + mitk::MultiLabelSegmentationVtkMapper3D::SetDefaultProperties(node); auto propertyFilters = CoreServices::GetPropertyFilters(); if (propertyFilters != nullptr) { PropertyFilter labelSetImageFilter; labelSetImageFilter.AddEntry("binaryimage.hoveringannotationcolor", PropertyFilter::Blacklist); labelSetImageFilter.AddEntry("binaryimage.hoveringcolor", PropertyFilter::Blacklist); labelSetImageFilter.AddEntry("binaryimage.selectedannotationcolor", PropertyFilter::Blacklist); labelSetImageFilter.AddEntry("binaryimage.selectedcolor", PropertyFilter::Blacklist); labelSetImageFilter.AddEntry("outline binary shadow color", PropertyFilter::Blacklist); propertyFilters->AddFilter(labelSetImageFilter, "LabelSetImage"); } } } std::string mitk::MultilabelObjectFactory::GetFileExtensions() { std::string fileExtension; this->CreateFileExtensions({}, fileExtension); return fileExtension.c_str(); } mitk::CoreObjectFactoryBase::MultimapType mitk::MultilabelObjectFactory::GetFileExtensionsMap() { return {}; } mitk::CoreObjectFactoryBase::MultimapType mitk::MultilabelObjectFactory::GetSaveFileExtensionsMap() { return {}; } void mitk::MultilabelObjectFactory::CreateFileExtensionsMap() { } std::string mitk::MultilabelObjectFactory::GetSaveFileExtensions() { std::string fileExtension; this->CreateFileExtensions({}, fileExtension); return fileExtension.c_str(); } struct RegisterMultilabelObjectFactory { RegisterMultilabelObjectFactory() : m_Factory(mitk::MultilabelObjectFactory::New()) { mitk::CoreObjectFactory::GetInstance()->RegisterExtraFactory(m_Factory); } ~RegisterMultilabelObjectFactory() { mitk::CoreObjectFactory::GetInstance()->UnRegisterExtraFactory(m_Factory); } mitk::MultilabelObjectFactory::Pointer m_Factory; }; static RegisterMultilabelObjectFactory registerMultilabelObjectFactory; diff --git a/Modules/QtWidgets/files.cmake b/Modules/QtWidgets/files.cmake index 67c6335c3e..dca87db1cb 100644 --- a/Modules/QtWidgets/files.cmake +++ b/Modules/QtWidgets/files.cmake @@ -1,186 +1,189 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES QmitkAbstractDataStorageModel.cpp QmitkAbstractMultiWidget.cpp QmitkAbstractNodeSelectionWidget.cpp QmitkApplicationCursor.cpp + QmitkAutomatedLayoutWidget.cpp QmitkDataStorageComboBox.cpp QmitkDataStorageDefaultListModel.cpp QmitkDataStorageHistoryModel.cpp QmitkDataStorageListModel.cpp QmitkDataStorageTableModel.cpp QmitkDataStorageSimpleTreeModel.cpp QmitkDataStorageTreeModel.cpp QmitkDataStorageTreeModelInternalItem.cpp QmitkDnDDataNodeWidget.cpp QmitkFileReaderOptionsDialog.cpp QmitkFileReaderWriterOptionsWidget.cpp QmitkFileWriterOptionsDialog.cpp QmitkInteractionSchemeToolBar.cpp QmitkIOUtil.cpp QmitkLevelWindowPresetDefinitionDialog.cpp QmitkLevelWindowRangeChangeDialog.cpp QmitkLevelWindowWidgetContextMenu.cpp QmitkLevelWindowWidget.cpp QmitkLineEditLevelWindowWidget.cpp QmitkMemoryUsageIndicatorView.cpp QmitkMimeTypes.cpp QmitkMultiNodeSelectionWidget.cpp QmitkMultiWidgetConfigurationToolBar.cpp QmitkMultiWidgetLayoutManager.cpp QmitkMultiWidgetLayoutSelectionWidget.cpp QmitkNodeDescriptor.cpp QmitkNodeSelectionButton.cpp QmitkNodeSelectionConstants.cpp QmitkNodeSelectionDialog.cpp QmitkNodeSelectionListItemWidget.cpp QmitkNodeSelectionPreferenceHelper.cpp QmitkNodeDescriptor.cpp QmitkColoredNodeDescriptor.cpp QmitkNodeDescriptorManager.cpp QmitkProgressBar.cpp QmitkPropertiesTableEditor.cpp QmitkPropertiesTableModel.cpp QmitkPropertyDelegate.cpp QmitkRegisterClasses.cpp QmitkRenderingManager.cpp QmitkRenderWindowDataStorageTreeModel.cpp QmitkRenderingManagerFactory.cpp QmitkRenderWindow.cpp QmitkRenderWindowMenu.cpp QmitkRenderWindowUtilityWidget.cpp QmitkRenderWindowWidget.cpp QmitkRenderWindowContextDataStorageInspector.cpp mitkRenderWindowLayerController.cpp mitkRenderWindowLayerUtilities.cpp mitkRenderWindowViewDirectionController.cpp QmitkServiceListWidget.cpp QmitkSingleNodeSelectionWidget.cpp QmitkSliceNavigationWidget.cpp QmitkSliderLevelWindowWidget.cpp QmitkStdMultiWidget.cpp QmitkStepperAdapter.cpp QmitkMxNMultiWidget.cpp QmitkDataStorageComboBoxWithSelectNone.cpp QmitkDataStorageFilterProxyModel.cpp QmitkPropertyItem.cpp QmitkPropertyItemDelegate.cpp QmitkPropertyItemModel.cpp QmitkStyleManager.cpp QmitkAbstractDataStorageInspector.cpp QmitkDataStorageFavoriteNodesInspector.cpp QmitkDataStorageListInspector.cpp QmitkDataStorageTreeInspector.cpp QmitkDataStorageSelectionHistoryInspector.cpp QmitkModelViewSelectionConnector.cpp mitkIDataStorageInspectorProvider.cpp mitkQtWidgetsActivator.cpp mitkDataStorageInspectorGenerator.cpp QmitkOverlayWidget.cpp QmitkSimpleTextOverlayWidget.cpp QmitkButtonOverlayWidget.cpp QmitkNodeDetailsDialog.cpp QmitkRenderWindowDataNodeTableModel.cpp QmitkSynchronizedNodeSelectionWidget.cpp QmitkSynchronizedWidgetConnector.cpp ) set(MOC_H_FILES include/QmitkAbstractDataStorageModel.h include/QmitkAbstractMultiWidget.h include/QmitkAbstractNodeSelectionWidget.h + include/QmitkAutomatedLayoutWidget.h include/QmitkDataStorageComboBox.h include/QmitkDataStorageTableModel.h include/QmitkDataStorageTreeModel.h include/QmitkDataStorageSimpleTreeModel.h include/QmitkDataStorageDefaultListModel.h include/QmitkDnDDataNodeWidget.h include/QmitkFileReaderOptionsDialog.h include/QmitkFileReaderWriterOptionsWidget.h include/QmitkFileWriterOptionsDialog.h include/QmitkInteractionSchemeToolBar.h include/QmitkLevelWindowPresetDefinitionDialog.h include/QmitkLevelWindowRangeChangeDialog.h include/QmitkLevelWindowWidgetContextMenu.h include/QmitkLevelWindowWidget.h include/QmitkLineEditLevelWindowWidget.h include/QmitkMemoryUsageIndicatorView.h include/QmitkMultiNodeSelectionWidget.h include/QmitkMultiWidgetConfigurationToolBar.h include/QmitkMultiWidgetLayoutManager.h include/QmitkMultiWidgetLayoutSelectionWidget.h include/QmitkNodeDescriptor.h include/QmitkNodeSelectionButton.h include/QmitkNodeSelectionDialog.h include/QmitkNodeSelectionListItemWidget.h include/QmitkColoredNodeDescriptor.h include/QmitkNodeDescriptorManager.h include/QmitkProgressBar.h include/QmitkPropertiesTableEditor.h include/QmitkPropertyDelegate.h include/QmitkRenderingManager.h include/QmitkRenderWindow.h include/QmitkRenderWindowDataStorageTreeModel.h include/QmitkRenderWindowMenu.h include/QmitkRenderWindowUtilityWidget.h include/QmitkRenderWindowWidget.h include/QmitkRenderWindowContextDataStorageInspector.h include/mitkRenderWindowLayerController.h include/mitkRenderWindowLayerUtilities.h include/mitkRenderWindowViewDirectionController.h include/QmitkServiceListWidget.h include/QmitkSingleNodeSelectionWidget.h include/QmitkSliceNavigationWidget.h include/QmitkSliderLevelWindowWidget.h include/QmitkStdMultiWidget.h include/QmitkMxNMultiWidget.h include/QmitkStepperAdapter.h include/QmitkDataStorageComboBoxWithSelectNone.h include/QmitkPropertyItemDelegate.h include/QmitkPropertyItemModel.h include/QmitkAbstractDataStorageInspector.h include/QmitkDataStorageFavoriteNodesInspector.h include/QmitkDataStorageListInspector.h include/QmitkDataStorageTreeInspector.h include/QmitkDataStorageHistoryModel.h include/QmitkDataStorageSelectionHistoryInspector.h include/QmitkModelViewSelectionConnector.h include/QmitkOverlayWidget.h include/QmitkSimpleTextOverlayWidget.h include/QmitkButtonOverlayWidget.h include/QmitkNodeDetailsDialog.h include/QmitkRenderWindowDataNodeTableModel.h include/QmitkSynchronizedNodeSelectionWidget.h include/QmitkSynchronizedWidgetConnector.h ) set(UI_FILES + src/QmitkAutomatedLayoutWidget.ui src/QmitkFileReaderOptionsDialog.ui src/QmitkFileWriterOptionsDialog.ui src/QmitkLevelWindowPresetDefinition.ui src/QmitkLevelWindowWidget.ui src/QmitkLevelWindowRangeChange.ui src/QmitkMemoryUsageIndicator.ui src/QmitkMultiNodeSelectionWidget.ui src/QmitkMultiWidgetLayoutSelectionWidget.ui src/QmitkNodeSelectionDialog.ui src/QmitkNodeSelectionListItemWidget.ui src/QmitkRenderWindowContextDataStorageInspector.ui src/QmitkServiceListWidgetControls.ui src/QmitkSingleNodeSelectionWidget.ui src/QmitkSliceNavigationWidget.ui src/QmitkDataStorageListInspector.ui src/QmitkDataStorageTreeInspector.ui src/QmitkDataStorageSelectionHistoryInspector.ui src/QmitkSynchronizedNodeSelectionWidget.ui src/QmitkNodeDetailsDialog.ui ) set(RESOURCE_FILES mxnLayout_twoRowsEachDirection.json ) set(QRC_FILES resource/Qmitk.qrc ) diff --git a/Modules/QtWidgets/include/QmitkAutomatedLayoutWidget.h b/Modules/QtWidgets/include/QmitkAutomatedLayoutWidget.h new file mode 100644 index 0000000000..40564ee207 --- /dev/null +++ b/Modules/QtWidgets/include/QmitkAutomatedLayoutWidget.h @@ -0,0 +1,50 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkAutomatedLayoutDialog_h +#define QmitkAutomatedLayoutDialog_h + +#include + +#include + +#include + +namespace Ui +{ + class QmitkAutomatedLayoutWidget; +} + +class MITKQTWIDGETS_EXPORT QmitkAutomatedLayoutWidget : public QWidget +{ + Q_OBJECT + +private Q_SLOTS: + void OnSetLayoutClicked(); + void OnSelectionDialogClosed(); + +Q_SIGNALS: + void SetDataBasedLayout(const QList& nodes); + + +public: + explicit QmitkAutomatedLayoutWidget(QWidget* parent = nullptr); + void SetDataStorage(mitk::DataStorage::Pointer dataStorage); + +private: + + Ui::QmitkAutomatedLayoutWidget* m_Controls; + mitk::DataStorage::Pointer m_DataStorage; +}; + + +#endif diff --git a/Modules/QtWidgets/include/QmitkMultiNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkMultiNodeSelectionWidget.h index 54c9f87f0f..e4ad19807b 100644 --- a/Modules/QtWidgets/include/QmitkMultiNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkMultiNodeSelectionWidget.h @@ -1,82 +1,85 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkMultiNodeSelectionWidget_h #define QmitkMultiNodeSelectionWidget_h #include #include #include #include #include #include #include class QmitkAbstractDataStorageModel; /** * @class QmitkMultiNodeSelectionWidget * @brief Widget that allows to perform and represents a multiple node selection. */ class MITKQTWIDGETS_EXPORT QmitkMultiNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: explicit QmitkMultiNodeSelectionWidget(QWidget* parent = nullptr); using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; /** * @brief Helper function that is used to check the given selection for consistency. * Returning an empty string assumes that everything is alright and the selection * is valid. If the string is not empty, the content of the string will be used * as error message in the overlay to indicate the problem. */ using SelectionCheckFunctionType = std::function; /** * @brief A selection check function can be set. If set the widget uses this function to * check the made/set selection. If the selection is valid, everything is fine. * If selection is indicated as invalid, it will not be communicated by the widget * (no signal emission). */ void SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction); /** Returns if the current internal selection is violating the current check function, if set.*/ bool CurrentSelectionViolatesCheckFunction() const; +Q_SIGNALS: + void DialogClosed(); + public Q_SLOTS: void OnEditSelection(); protected Q_SLOTS: void OnClearSelection(const mitk::DataNode* node); protected: void changeEvent(QEvent *event) override; void UpdateInfo() override; void OnInternalSelectionChanged() override; bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const override; QmitkSimpleTextOverlayWidget* m_Overlay; SelectionCheckFunctionType m_CheckFunction; mutable std::string m_CheckResponse; Ui_QmitkMultiNodeSelectionWidget m_Controls; }; #endif diff --git a/Modules/QtWidgets/include/QmitkMultiWidgetConfigurationToolBar.h b/Modules/QtWidgets/include/QmitkMultiWidgetConfigurationToolBar.h index 18bb853e67..e5d36d8a7e 100644 --- a/Modules/QtWidgets/include/QmitkMultiWidgetConfigurationToolBar.h +++ b/Modules/QtWidgets/include/QmitkMultiWidgetConfigurationToolBar.h @@ -1,70 +1,73 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkMultiWidgetConfigurationToolBar_h #define QmitkMultiWidgetConfigurationToolBar_h -#include "MitkQtWidgetsExports.h" +#include #include +#include #include // qt #include class QmitkAbstractMultiWidget; class QmitkMultiWidgetLayoutSelectionWidget; /** * @brief * * */ class MITKQTWIDGETS_EXPORT QmitkMultiWidgetConfigurationToolBar : public QToolBar { Q_OBJECT public: QmitkMultiWidgetConfigurationToolBar(QmitkAbstractMultiWidget* multiWidget); ~QmitkMultiWidgetConfigurationToolBar() override; + void SetDataStorage(mitk::DataStorage::Pointer dataStorage); Q_SIGNALS: void LayoutSet(int row, int column); void SaveLayout(std::ostream* outStream); void LoadLayout(const nlohmann::json* jsonData); + void SetDataBasedLayout(const QList& nodes); void Synchronized(bool synchronized); void InteractionSchemeChanged(mitk::InteractionSchemeSwitcher::InteractionScheme scheme); protected Q_SLOTS: void OnSetLayout(); void OnSynchronize(); void OnInteractionSchemeChanged(); private: void InitializeToolBar();; void AddButtons(); QmitkAbstractMultiWidget* m_MultiWidget; QAction* m_SynchronizeAction; QAction* m_InteractionSchemeChangeAction; QmitkMultiWidgetLayoutSelectionWidget* m_LayoutSelectionPopup; }; #endif diff --git a/Modules/QtWidgets/include/QmitkMultiWidgetLayoutSelectionWidget.h b/Modules/QtWidgets/include/QmitkMultiWidgetLayoutSelectionWidget.h index 14d9fed068..fa38feb2d2 100644 --- a/Modules/QtWidgets/include/QmitkMultiWidgetLayoutSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkMultiWidgetLayoutSelectionWidget.h @@ -1,65 +1,74 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkMultiWidgetLayoutSelectionWidget_h #define QmitkMultiWidgetLayoutSelectionWidget_h #include "MitkQtWidgetsExports.h" -#include "ui_QmitkMultiWidgetLayoutSelectionWidget.h" +#include #include // qt #include "QWidget" +namespace Ui +{ + class QmitkMultiWidgetLayoutSelectionWidget; +} + /** * @brief * * */ class MITKQTWIDGETS_EXPORT QmitkMultiWidgetLayoutSelectionWidget : public QWidget { Q_OBJECT public: QmitkMultiWidgetLayoutSelectionWidget(QWidget* parent = nullptr); + void SetDataStorage(mitk::DataStorage::Pointer dataStorage); Q_SIGNALS: void LayoutSet(int row, int column); + void SetDataBasedLayout(const QList& nodes); // needs to be connected via Qt::DirectConnection (usually default), to ensure the stream pointers validity void SaveLayout(std::ostream* outStream); void LoadLayout(const nlohmann::json* jsonData); private Q_SLOTS: void OnTableItemSelectionChanged(); void OnSetLayoutButtonClicked(); + void OnDataBasedLayoutButtonClicked(); void OnSaveLayoutButtonClicked(); void OnLoadLayoutButtonClicked(); void OnLayoutPresetSelected(int index); private: void Init(); - Ui::QmitkMultiWidgetLayoutSelectionWidget ui; + Ui::QmitkMultiWidgetLayoutSelectionWidget* ui; std::map m_PresetMap; + QmitkAutomatedLayoutWidget* m_AutomatedDataLayoutWidget; }; #endif diff --git a/Modules/QtWidgets/include/QmitkMxNMultiWidget.h b/Modules/QtWidgets/include/QmitkMxNMultiWidget.h index 92d5e7a7b5..233d5771e6 100644 --- a/Modules/QtWidgets/include/QmitkMxNMultiWidget.h +++ b/Modules/QtWidgets/include/QmitkMxNMultiWidget.h @@ -1,131 +1,133 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkMxNMultiWidget_h #define QmitkMxNMultiWidget_h #include "MitkQtWidgetsExports.h" // qt widgets module #include "QmitkAbstractMultiWidget.h" #include #include #include class QSplitter; /** * @brief The 'QmitkMxNMultiWidget' is a 'QmitkAbstractMultiWidget' that is used to display multiple render windows at once. * Render windows can dynamically be added and removed to change the layout of the multi widget. This * is done by using the 'SetLayout'-function to define a layout. This will automatically add or remove * the appropriate number of render window widgets. */ class MITKQTWIDGETS_EXPORT QmitkMxNMultiWidget : public QmitkAbstractMultiWidget { Q_OBJECT public: QmitkMxNMultiWidget(QWidget* parent = nullptr, Qt::WindowFlags f = {}, const QString& multiWidgetName = "mxn"); ~QmitkMxNMultiWidget(); void InitializeMultiWidget() override; void Synchronize(bool synchronized) override; QmitkRenderWindow* GetRenderWindow(const QString& widgetName) const override; QmitkRenderWindow* GetRenderWindow(const mitk::AnatomicalPlane& orientation) const override; void SetActiveRenderWindowWidget(RenderWindowWidgetPointer activeRenderWindowWidget) override; /** * @brief Initialize the active render windows of the MxNMultiWidget to the given geometry. * * @param geometry The geometry to be used to initialize / update the * active render window's time and slice navigation controller. * @param resetCamera If true, the camera and crosshair will be reset to the default view (centered, no zoom). * If false, the current crosshair position and the camera zoom will be stored and reset * after the reference geometry has been updated. */ void InitializeViews(const mitk::TimeGeometry* geometry, bool resetCamera) override; /** * @brief Forward the given time geometry to all base renderers, so that they can store it as their * interaction reference geometry. * This will update the alignment status of the reference geometry for each base renderer. * For more details, see 'BaseRenderer::SetInteractionReferenceGeometry'. * Overridem from 'QmitkAbstractMultiWidget'. */ void SetInteractionReferenceGeometry(const mitk::TimeGeometry* referenceGeometry) override; /** * @brief Returns true if the render windows are coupled; false if not. * * For the MxNMultiWidget the render windows are typically decoupled. */ bool HasCoupledRenderWindows() const override; void SetSelectedPosition(const mitk::Point3D& newPosition, const QString& widgetName) override; const mitk::Point3D GetSelectedPosition(const QString& widgetName) const override; void SetCrosshairVisibility(bool visible) override; bool GetCrosshairVisibility() const override; void SetCrosshairGap(unsigned int gapSize) override; void ResetCrosshair() override; void SetWidgetPlaneMode(int userMode) override; mitk::SliceNavigationController* GetTimeNavigationController(); void EnableCrosshair(); void DisableCrosshair(); public Q_SLOTS: // mouse events void wheelEvent(QWheelEvent* e) override; void mousePressEvent(QMouseEvent* e) override; void moveEvent(QMoveEvent* e) override; void LoadLayout(const nlohmann::json* jsonData); void SaveLayout(std::ostream* outStream); + void SetDataBasedLayout(const QmitkAbstractNodeSelectionWidget::NodeList& nodes); Q_SIGNALS: void WheelMoved(QWheelEvent *); void Moved(); void UpdateUtilityWidgetViewPlanes(); void LayoutChanged(); private: void SetLayoutImpl() override; void SetInteractionSchemeImpl() override { } QmitkAbstractMultiWidget::RenderWindowWidgetPointer CreateRenderWindowWidget(); + QmitkAbstractMultiWidget::RenderWindowWidgetPointer GetWindowFromIndex(size_t index); void SetInitialSelection(); void ToggleSynchronization(QmitkSynchronizedNodeSelectionWidget* synchronizedWidget); static nlohmann::json BuildJSONFromLayout(const QSplitter* splitter); QSplitter* BuildLayoutFromJSON(const nlohmann::json* jsonData, unsigned int* windowCounter, QSplitter* parentSplitter = nullptr); std::unique_ptr m_SynchronizedWidgetConnector; bool m_CrosshairVisibility; }; #endif diff --git a/Modules/QtWidgets/include/QmitkRenderWindowUtilityWidget.h b/Modules/QtWidgets/include/QmitkRenderWindowUtilityWidget.h index 8df33517f5..3ba92caf6f 100644 --- a/Modules/QtWidgets/include/QmitkRenderWindowUtilityWidget.h +++ b/Modules/QtWidgets/include/QmitkRenderWindowUtilityWidget.h @@ -1,77 +1,79 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkRenderWindowUtilityWidget_h #define QmitkRenderWindowUtilityWidget_h #include "MitkQtWidgetsExports.h" // qt widgets module #include #include #include #include #include // qt #include #include #include #include namespace mitk { class DataStorage; } class QmitkRenderWindow; class MITKQTWIDGETS_EXPORT QmitkRenderWindowUtilityWidget : public QWidget { Q_OBJECT public: QmitkRenderWindowUtilityWidget( QWidget* parent = nullptr, QmitkRenderWindow* renderWindow = nullptr, mitk::DataStorage* dataStorage = nullptr ); ~QmitkRenderWindowUtilityWidget() override; void ToggleSynchronization(bool synchronized); void SetGeometry(const itk::EventObject& event); public Q_SLOTS: void UpdateViewPlaneSelection(); Q_SIGNALS: void SynchronizationToggled(QmitkSynchronizedNodeSelectionWidget* synchronizedWidget); + void SetDataSelection(const QList& newSelection); private: mitk::BaseRenderer* m_BaseRenderer; QmitkSynchronizedNodeSelectionWidget* m_NodeSelectionWidget; + QPushButton* m_SynchPushButton; QmitkSliceNavigationWidget* m_SliceNavigationWidget; QmitkStepperAdapter* m_StepperAdapter; std::unique_ptr m_RenderWindowLayerController; std::unique_ptr m_RenderWindowViewDirectionController; QComboBox* m_ViewDirectionSelector; void ChangeViewDirection(const QString& viewDirection); }; #endif diff --git a/Modules/QtWidgets/include/QmitkRenderWindowWidget.h b/Modules/QtWidgets/include/QmitkRenderWindowWidget.h index 98f734fec0..b9b803e64f 100644 --- a/Modules/QtWidgets/include/QmitkRenderWindowWidget.h +++ b/Modules/QtWidgets/include/QmitkRenderWindowWidget.h @@ -1,123 +1,125 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkRenderWindowWidget_h #define QmitkRenderWindowWidget_h #include "MitkQtWidgetsExports.h" // qt widgets module #include // mitk core #include #include #include +#include // qt #include #include #include class vtkCornerAnnotation; /** * @brief The 'QmitkRenderWindowWidget' is a QFrame that holds a render window * and some associates properties, e.g. decorations. * Decorations are corner annotation (text and color), frame color or background color * and can be set using this class. * The 'QmitkRenderWindowWidget' is used inside a 'QmitkAbstractMultiWidget', where a map contains * several render window widgets to create the multi widget display. * This class uses a CrosshairManager, which allows to use plane geometries as crosshair. */ class MITKQTWIDGETS_EXPORT QmitkRenderWindowWidget : public QFrame { Q_OBJECT public: QmitkRenderWindowWidget( QWidget* parent = nullptr, const QString& widgetName = "", mitk::DataStorage* dataStorage = nullptr); ~QmitkRenderWindowWidget() override; void SetDataStorage(mitk::DataStorage* dataStorage); const QString& GetWidgetName() const { return m_WidgetName; }; QmitkRenderWindow* GetRenderWindow() const { return m_RenderWindow; }; mitk::SliceNavigationController* GetSliceNavigationController() const; void RequestUpdate(); void ForceImmediateUpdate(); void AddUtilityWidget(QWidget* utilityWidget); + QmitkRenderWindowUtilityWidget* GetUtilityWidget(); void SetGradientBackgroundColors(const mitk::Color& upper, const mitk::Color& lower); void ShowGradientBackground(bool enable); std::pair GetGradientBackgroundColors() const { return m_GradientBackgroundColors; }; bool IsGradientBackgroundOn() const; void SetDecorationColor(const mitk::Color& color); mitk::Color GetDecorationColor() const { return m_DecorationColor; }; void ShowColoredRectangle(bool show); bool IsColoredRectangleVisible() const; void ShowCornerAnnotation(bool show); bool IsCornerAnnotationVisible() const; void SetCornerAnnotationText(const std::string& cornerAnnotation); std::string GetCornerAnnotationText() const; bool IsRenderWindowMenuActivated() const; void SetCrosshairVisibility(bool visible); bool GetCrosshairVisibility(); void SetCrosshairGap(unsigned int gapSize); void EnableCrosshair(); void DisableCrosshair(); void SetCrosshairPosition(const mitk::Point3D& newPosition); mitk::Point3D GetCrosshairPosition() const; void SetGeometry(const itk::EventObject& event); void SetGeometrySlice(const itk::EventObject& event); public Q_SLOTS: void OnResetGeometry(); private: void InitializeGUI(); void InitializeDecorations(); void ResetGeometry(const mitk::TimeGeometry* referenceGeometry); QString m_WidgetName; QVBoxLayout* m_Layout; mitk::DataStorage* m_DataStorage; QmitkRenderWindow* m_RenderWindow; mitk::CrosshairManager::Pointer m_CrosshairManager; std::pair m_GradientBackgroundColors; mitk::Color m_DecorationColor; vtkSmartPointer m_CornerAnnotation; }; #endif diff --git a/Modules/QtWidgets/include/QmitkSingleNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkSingleNodeSelectionWidget.h index 44130cb7e6..72c61fbad6 100644 --- a/Modules/QtWidgets/include/QmitkSingleNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkSingleNodeSelectionWidget.h @@ -1,97 +1,98 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSingleNodeSelectionWidget_h #define QmitkSingleNodeSelectionWidget_h #include #include #include #include #include #include #include class QmitkAbstractDataStorageModel; /** * @class QmitkSingleNodeSelectionWidget * @brief Widget that represents a node selection of (max) one node. It acts like a button. Clicking on it * allows to change the selection. * * @remark This class provides a public function 'SetAutoSelectNewNodes' that can be used to enable * the auto selection mode (default is false). * The user of this class calling this function has to make sure that the base-class Q_SIGNAL * 'CurrentSelectionChanged', which will be emitted by this function, is already * connected to a receiving slot, if the initial valid auto selection should not get lost. */ class MITKQTWIDGETS_EXPORT QmitkSingleNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: explicit QmitkSingleNodeSelectionWidget(QWidget* parent = nullptr); mitk::DataNode::Pointer GetSelectedNode() const; bool GetAutoSelectNewNodes() const; using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; public Q_SLOTS: void SetCurrentSelectedNode(mitk::DataNode* selectedNode); /** * Sets the auto selection mode (default is false). * If auto select is true and the following conditions are fulfilled, the widget will * select a node automatically from the data storage: * - a data storage is set * - data storage contains at least one node that matches the given predicate * - no selection is set * * @remark Enabling the auto selection mode by calling 'SetAutoSelectNewNodes(true)' * will directly emit a 'QmitkSingleNodeSelectionWidget::CurrentSelectionChanged' Q_SIGNAL * if a valid auto selection was made. * If this initial emission should not get lost, auto selection mode needs to be enabled after this * selection widget has been connected via the 'QmitkSingleNodeSelectionWidget::CurrentSelectionChanged' * Q_SIGNAL to a receiving function. */ void SetAutoSelectNewNodes(bool autoSelect); protected Q_SLOTS: virtual void OnClearSelection(); protected: void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; bool eventFilter(QObject *obj, QEvent *ev) override; void EditSelection(); void UpdateInfo() override; void OnDataStorageChanged() override; void OnNodeAddedToStorage(const mitk::DataNode* node) override; + void OnNodePredicateChanged() override; void AutoSelectNodes(); /** Helper function that gets a suitable auto selected node from the datastorage that fits to the predicate settings. @param ignoreNodes You may pass a list of nodes that must not be chosen as auto selected node. */ mitk::DataNode::Pointer DetermineAutoSelectNode(const NodeList& ignoreNodes = {}); /** See documentation of SetAutoSelectNewNodes for details*/ bool m_AutoSelectNodes; Ui_QmitkSingleNodeSelectionWidget m_Controls; }; #endif diff --git a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h index 104403f81f..2a418227ec 100644 --- a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h @@ -1,103 +1,105 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSynchronizedNodeSelectionWidget_h #define QmitkSynchronizedNodeSelectionWidget_h #include #include "ui_QmitkSynchronizedNodeSelectionWidget.h" // mitk core #include // qt widgets module #include #include /* * @brief The 'QmitkSynchronizedNodeSelectionWidget' implements the 'QmitkAbstractNodeSelectionWidget' * by providing a table view, using a 'QmitkRenderWindowDataNodeTableModel' and extending it * with base renderer-specific functionality. * * Given a base renderer, the selection widget is able to display and access render window specific properties * of the selected nodes, making it possible to switch between a "synchronized" and "desynchronized" selection * state. * The widget can be used to decide if all data nodes of the data storage should be selected or * only an individually selected set of nodes, defined by a 'QmitkNodeSelectionDialog'. * If individual nodes are selected / removed from the selection, the widget can inform other * 'QmitkSynchronizedNodeSelectionWidget' about the current selection, if desired. * Additionally the widget allows to reinitialize the corresponding base renderer with a specific * data node geometry. */ class MITKQTWIDGETS_EXPORT QmitkSynchronizedNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: QmitkSynchronizedNodeSelectionWidget(QWidget* parent); ~QmitkSynchronizedNodeSelectionWidget(); using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; void SetBaseRenderer(mitk::BaseRenderer* baseRenderer); void SetSelectAll(bool selectAll); bool GetSelectAll() const; void SelectAll(); void SetSynchronized(bool synchronize); bool IsSynchronized() const; Q_SIGNALS: void SelectionModeChanged(bool selectAll); void DeregisterSynchronization(); +public Q_SLOTS: + void SetSelection(const NodeList& newSelection); + private Q_SLOTS: - void OnModelUpdated(); void OnSelectionModeChanged(bool selectAll); void OnEditSelection(); void OnTableClicked(const QModelIndex& index); protected: void SetUpConnections(); void Initialize(); void UpdateInfo() override; void OnDataStorageChanged() override; void OnNodePredicateChanged() override; void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; void OnInternalSelectionChanged() override; bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const override; void OnNodeAddedToStorage(const mitk::DataNode* node) override; void OnNodeModified(const itk::Object* caller, const itk::EventObject& event) override; private: void ReviseSynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); void ReviseDesynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); void ReinitNode(const mitk::DataNode* dataNode); void RemoveFromInternalSelection(mitk::DataNode* dataNode); bool IsParentNodeSelected(const mitk::DataNode* dataNode) const; void DeselectNode(mitk::DataNode* dataNode); Ui::QmitkSynchronizedNodeSelectionWidget m_Controls; mitk::WeakPointer m_BaseRenderer; std::unique_ptr m_StorageModel; }; #endif diff --git a/Modules/QtWidgets/src/QmitkAutomatedLayoutWidget.cpp b/Modules/QtWidgets/src/QmitkAutomatedLayoutWidget.cpp new file mode 100644 index 0000000000..5404d060f1 --- /dev/null +++ b/Modules/QtWidgets/src/QmitkAutomatedLayoutWidget.cpp @@ -0,0 +1,62 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include +#include +#include +#include +#include + +#include + +QmitkAutomatedLayoutWidget::QmitkAutomatedLayoutWidget(QWidget* parent) + : QWidget(parent) + , m_Controls(new Ui::QmitkAutomatedLayoutWidget) +{ + m_Controls->setupUi(this); + + auto isImage = mitk::TNodePredicateDataType::New(); + auto isNotHelper = mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")); + auto isNotHidden = mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object")); + auto isValidImage = mitk::NodePredicateAnd::New(isImage, isNotHelper, isNotHidden); + m_Controls->dataSelector->SetNodePredicate(isValidImage); + m_Controls->dataSelector->SetInvalidInfo(QStringLiteral("Please select images for the layout")); + m_Controls->dataSelector->SetPopUpTitel(QStringLiteral("Select input images")); + m_Controls->dataSelector->SetPopUpHint(QStringLiteral("Select as many images as you want to compare")); + + m_Controls->setLayoutButton->setEnabled(false); + + connect(m_Controls->dataSelector, &QmitkMultiNodeSelectionWidget::DialogClosed, this, &QmitkAutomatedLayoutWidget::OnSelectionDialogClosed); + connect(m_Controls->setLayoutButton, &QPushButton::clicked, this, &QmitkAutomatedLayoutWidget::OnSetLayoutClicked); +} + +void QmitkAutomatedLayoutWidget::SetDataStorage(mitk::DataStorage::Pointer dataStorage) +{ + // smart pointer to data storage needs to be saved here because dataSelector makes it a regular pointer + m_DataStorage = dataStorage; + m_Controls->dataSelector->SetDataStorage(m_DataStorage); +} + +void QmitkAutomatedLayoutWidget::OnSetLayoutClicked() +{ + auto selectedNodes = m_Controls->dataSelector->GetSelectedNodes(); + emit SetDataBasedLayout(selectedNodes); + this->hide(); +} + +void QmitkAutomatedLayoutWidget::OnSelectionDialogClosed() +{ + bool setLayoutEnabled = m_Controls->dataSelector->GetSelectedNodes().size() > 0; + m_Controls->setLayoutButton->setEnabled(setLayoutEnabled); + this->show(); +} diff --git a/Modules/QtWidgets/src/QmitkAutomatedLayoutWidget.ui b/Modules/QtWidgets/src/QmitkAutomatedLayoutWidget.ui new file mode 100644 index 0000000000..13be69219d --- /dev/null +++ b/Modules/QtWidgets/src/QmitkAutomatedLayoutWidget.ui @@ -0,0 +1,39 @@ + + + QmitkAutomatedLayoutWidget + + + + 0 + 0 + 400 + 300 + + + + Widget + + + + + + + + + Set Layout + + + + + + + + QmitkMultiNodeSelectionWidget + QWidget +
QmitkMultiNodeSelectionWidget.h
+ 1 +
+
+ + +
diff --git a/Modules/QtWidgets/src/QmitkMultiNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkMultiNodeSelectionWidget.cpp index 5bbf899831..7ed9f6eafc 100644 --- a/Modules/QtWidgets/src/QmitkMultiNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMultiNodeSelectionWidget.cpp @@ -1,164 +1,165 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiNodeSelectionWidget.h" #include #include #include #include QmitkMultiNodeSelectionWidget::QmitkMultiNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) { m_Controls.setupUi(this); m_Overlay = new QmitkSimpleTextOverlayWidget(m_Controls.list); m_Overlay->setVisible(false); m_CheckFunction = [](const NodeList &) { return ""; }; this->OnInternalSelectionChanged(); this->UpdateInfo(); connect(m_Controls.btnChange, SIGNAL(clicked(bool)), this, SLOT(OnEditSelection())); } void QmitkMultiNodeSelectionWidget::SetSelectionCheckFunction(const SelectionCheckFunctionType &checkFunction) { m_CheckFunction = checkFunction; auto newEmission = this->CompileEmitSelection(); auto newCheckResponse = m_CheckFunction(newEmission); if (newCheckResponse.empty() && !m_CheckResponse.empty()) { this->EmitSelection(newEmission); } m_CheckResponse = newCheckResponse; this->UpdateInfo(); } void QmitkMultiNodeSelectionWidget::OnEditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this, m_PopUpTitel, m_PopUpHint); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(this->CompileEmitSelection()); dialog->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); dialog->SetSelectionCheckFunction(m_CheckFunction); m_Controls.btnChange->setChecked(true); if (dialog->exec()) { this->HandleChangeOfInternalSelection(dialog->GetSelectedNodes()); } m_Controls.btnChange->setChecked(false); + emit DialogClosed(); delete dialog; } void QmitkMultiNodeSelectionWidget::UpdateInfo() { if (!m_Controls.list->count()) { if (m_IsOptional) { if (this->isEnabled()) { m_Overlay->SetOverlayText(QStringLiteral("") + m_EmptyInfo + QStringLiteral("")); } else { m_Overlay->SetOverlayText(QStringLiteral("") + m_EmptyInfo + QStringLiteral("")); } } else { if (this->isEnabled()) { m_Overlay->SetOverlayText(QStringLiteral("") + m_InvalidInfo + QStringLiteral("")); } else { m_Overlay->SetOverlayText(QStringLiteral("") + m_InvalidInfo + QStringLiteral("")); } } } else { if (!m_CheckResponse.empty()) { m_Overlay->SetOverlayText(QString::fromStdString(m_CheckResponse)); } } m_Overlay->setVisible(m_Controls.list->count() == 0 || !m_CheckResponse.empty()); for (auto i = 0; i < m_Controls.list->count(); ++i) { auto item = m_Controls.list->item(i); auto widget = qobject_cast(m_Controls.list->itemWidget(item)); widget->SetClearAllowed(m_IsOptional || m_Controls.list->count() > 1); } } void QmitkMultiNodeSelectionWidget::OnInternalSelectionChanged() { m_Controls.list->clear(); auto currentSelection = this->GetCurrentInternalSelection(); for (auto& node : currentSelection) { if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { QListWidgetItem *newItem = new QListWidgetItem; newItem->setSizeHint(QSize(0, 40)); QmitkNodeSelectionListItemWidget* widget = new QmitkNodeSelectionListItemWidget; widget->SetSelectedNode(node); widget->SetClearAllowed(m_IsOptional || currentSelection.size() > 1); connect(widget, &QmitkNodeSelectionListItemWidget::ClearSelection, this, &QmitkMultiNodeSelectionWidget::OnClearSelection); newItem->setData(Qt::UserRole, QVariant::fromValue(node)); m_Controls.list->addItem(newItem); m_Controls.list->setItemWidget(newItem, widget); } } } void QmitkMultiNodeSelectionWidget::OnClearSelection(const mitk::DataNode* node) { this->RemoveNodeFromSelection(node); } void QmitkMultiNodeSelectionWidget::changeEvent(QEvent *event) { if (event->type() == QEvent::EnabledChange) { this->UpdateInfo(); } QmitkAbstractNodeSelectionWidget::changeEvent(event); } bool QmitkMultiNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& emissionCandidates) const { m_CheckResponse = m_CheckFunction(emissionCandidates); return m_CheckResponse.empty(); } bool QmitkMultiNodeSelectionWidget::CurrentSelectionViolatesCheckFunction() const { return !m_CheckResponse.empty(); } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp index 1987f7653f..2446476a64 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetConfigurationToolBar.cpp @@ -1,111 +1,119 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiWidgetConfigurationToolBar.h" // mitk qt widgets module #include "QmitkAbstractMultiWidget.h" #include "QmitkMultiWidgetLayoutSelectionWidget.h" QmitkMultiWidgetConfigurationToolBar::QmitkMultiWidgetConfigurationToolBar(QmitkAbstractMultiWidget* multiWidget) : QToolBar(multiWidget) , m_MultiWidget(multiWidget) { 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); + connect(m_LayoutSelectionPopup, &QmitkMultiWidgetLayoutSelectionWidget::SetDataBasedLayout, this, &QmitkMultiWidgetConfigurationToolBar::SetDataBasedLayout); connect(m_LayoutSelectionPopup, &QmitkMultiWidgetLayoutSelectionWidget::SaveLayout, this, &QmitkMultiWidgetConfigurationToolBar::SaveLayout); connect(m_LayoutSelectionPopup, &QmitkMultiWidgetLayoutSelectionWidget::LoadLayout, this, &QmitkMultiWidgetConfigurationToolBar::LoadLayout); } 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/mwDesynchronized.png"), tr("Synchronize render windows"), this); m_SynchronizeAction->setCheckable(true); m_SynchronizeAction->setChecked(false); connect(m_SynchronizeAction, &QAction::triggered, this, &QmitkMultiWidgetConfigurationToolBar::OnSynchronize); QToolBar::addAction(m_SynchronizeAction); m_InteractionSchemeChangeAction = new QAction(QIcon(":/Qmitk/mwMITK.png"), tr("Change to PACS interaction"), this); m_InteractionSchemeChangeAction->setCheckable(true); m_InteractionSchemeChangeAction->setChecked(false); connect(m_InteractionSchemeChangeAction, &QAction::triggered, this, &QmitkMultiWidgetConfigurationToolBar::OnInteractionSchemeChanged); QToolBar::addAction(m_InteractionSchemeChangeAction); } +void QmitkMultiWidgetConfigurationToolBar::SetDataStorage(mitk::DataStorage::Pointer dataStorage) +{ + if (m_LayoutSelectionPopup == nullptr) + return; + m_LayoutSelectionPopup->SetDataStorage(dataStorage); +} + void QmitkMultiWidgetConfigurationToolBar::OnSetLayout() { if (nullptr != m_MultiWidget) { m_LayoutSelectionPopup->setWindowFlags(Qt::Popup); 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); } void QmitkMultiWidgetConfigurationToolBar::OnInteractionSchemeChanged() { bool PACSInteractionScheme = m_InteractionSchemeChangeAction->isChecked(); if (PACSInteractionScheme) { m_InteractionSchemeChangeAction->setIcon(QIcon(":/Qmitk/mwPACS.png")); m_InteractionSchemeChangeAction->setText(tr("Change to MITK interaction")); emit InteractionSchemeChanged(mitk::InteractionSchemeSwitcher::PACSStandard); } else { m_InteractionSchemeChangeAction->setIcon(QIcon(":/Qmitk/mwMITK.png")); m_InteractionSchemeChangeAction->setText(tr("Change to PACS interaction")); emit InteractionSchemeChanged(mitk::InteractionSchemeSwitcher::MITKStandard); } m_InteractionSchemeChangeAction->setChecked(PACSInteractionScheme); } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp index d7c6dc070f..da35c00133 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.cpp @@ -1,138 +1,160 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#include "QmitkMultiWidgetLayoutSelectionWidget.h" +#include +#include #include #include #include #include #include QmitkMultiWidgetLayoutSelectionWidget::QmitkMultiWidgetLayoutSelectionWidget(QWidget* parent/* = 0*/) : QWidget(parent) + , ui(new Ui::QmitkMultiWidgetLayoutSelectionWidget) { Init(); } void QmitkMultiWidgetLayoutSelectionWidget::Init() { - ui.setupUi(this); + ui->setupUi(this); auto stylesheet = "QTableWidget::item{background-color: white;}\nQTableWidget::item:selected{background-color: #1C97EA;}"; - ui.tableWidget->setStyleSheet(stylesheet); + ui->tableWidget->setStyleSheet(stylesheet); - connect(ui.tableWidget, &QTableWidget::itemSelectionChanged, this, &QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged); - connect(ui.setLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSetLayoutButtonClicked); - connect(ui.loadLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnLoadLayoutButtonClicked); - connect(ui.saveLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSaveLayoutButtonClicked); - connect(ui.selectDefaultLayoutComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &QmitkMultiWidgetLayoutSelectionWidget::OnLayoutPresetSelected); + m_AutomatedDataLayoutWidget = new QmitkAutomatedLayoutWidget(this); + connect(m_AutomatedDataLayoutWidget, &QmitkAutomatedLayoutWidget::SetDataBasedLayout, this, &QmitkMultiWidgetLayoutSelectionWidget::SetDataBasedLayout); + m_AutomatedDataLayoutWidget->hide(); - ui.selectDefaultLayoutComboBox->addItem("Select a layout preset"); + connect(ui->tableWidget, &QTableWidget::itemSelectionChanged, this, &QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged); + connect(ui->setLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSetLayoutButtonClicked); + connect(ui->dataBasedLayoutButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnDataBasedLayoutButtonClicked); + connect(ui->loadLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnLoadLayoutButtonClicked); + connect(ui->saveLayoutPushButton, &QPushButton::clicked, this, &QmitkMultiWidgetLayoutSelectionWidget::OnSaveLayoutButtonClicked); + connect(ui->selectDefaultLayoutComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &QmitkMultiWidgetLayoutSelectionWidget::OnLayoutPresetSelected); + + ui->selectDefaultLayoutComboBox->addItem("Select a layout preset"); auto presetResources = us::GetModuleContext()->GetModule()->FindResources("/", "mxnLayout_*.json", false); for (const auto& resource : presetResources) { us::ModuleResourceStream jsonStream(resource); auto data = nlohmann::json::parse(jsonStream); auto resourceName = data["name"].get(); - ui.selectDefaultLayoutComboBox->addItem(QString::fromStdString(resourceName)); - m_PresetMap[ui.selectDefaultLayoutComboBox->count() - 1] = data; + ui->selectDefaultLayoutComboBox->addItem(QString::fromStdString(resourceName)); + m_PresetMap[ui->selectDefaultLayoutComboBox->count() - 1] = data; } } +void QmitkMultiWidgetLayoutSelectionWidget::SetDataStorage(mitk::DataStorage::Pointer dataStorage) +{ + if (m_AutomatedDataLayoutWidget == nullptr) + return; + m_AutomatedDataLayoutWidget->SetDataStorage(dataStorage); +} + void QmitkMultiWidgetLayoutSelectionWidget::OnTableItemSelectionChanged() { - QItemSelectionModel* selectionModel = ui.tableWidget->selectionModel(); + 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()); + 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(); + QModelIndexList indices = ui->tableWidget->selectionModel()->selectedIndexes(); if (indices.size() > 0) { // find largest row and column for (const auto& modelIndex : std::as_const(indices)) { if (modelIndex.row() > row) { row = modelIndex.row(); } if (modelIndex.column() > column) { column = modelIndex.column(); } } close(); emit LayoutSet(row+1, column+1); } - ui.selectDefaultLayoutComboBox->setCurrentIndex(0); + ui->selectDefaultLayoutComboBox->setCurrentIndex(0); +} + +void QmitkMultiWidgetLayoutSelectionWidget::OnDataBasedLayoutButtonClicked() +{ + this->hide(); + m_AutomatedDataLayoutWidget->setWindowFlags(Qt::Popup); + m_AutomatedDataLayoutWidget->move(this->pos().x() - m_AutomatedDataLayoutWidget->width() + this->width(), this->pos().y()); + m_AutomatedDataLayoutWidget->show(); } void QmitkMultiWidgetLayoutSelectionWidget::OnSaveLayoutButtonClicked() { QString filename = QFileDialog::getSaveFileName(nullptr, "Select where to save the current layout", "", "MITK Window Layout (*.json)"); if (filename.isEmpty()) return; QString fileExt(".json"); if (!filename.endsWith(fileExt)) filename += fileExt; auto outStream = std::ofstream(filename.toStdString()); emit SaveLayout(&outStream); } void QmitkMultiWidgetLayoutSelectionWidget::OnLoadLayoutButtonClicked() { QString filename = QFileDialog::getOpenFileName(nullptr, "Load a layout file", "", "MITK Window Layouts (*.json)"); if (filename.isEmpty()) return; - ui.selectDefaultLayoutComboBox->setCurrentIndex(0); + ui->selectDefaultLayoutComboBox->setCurrentIndex(0); std::ifstream f(filename.toStdString()); auto jsonData = nlohmann::json::parse(f); emit LoadLayout(&jsonData); } void QmitkMultiWidgetLayoutSelectionWidget::OnLayoutPresetSelected(int index) { if (index == 0) { // First entry is only for description return; } auto jsonData = m_PresetMap[index]; close(); emit LoadLayout(&jsonData); } diff --git a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui index 391d5b8cb2..01af707ab0 100644 --- a/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui +++ b/Modules/QtWidgets/src/QmitkMultiWidgetLayoutSelectionWidget.ui @@ -1,123 +1,149 @@ QmitkMultiWidgetLayoutSelectionWidget 0 0 224 - 290 + 324 + + + 0 + 0 + + + + + 200 + 150 + + + + + 200 + 150 + + QAbstractItemView::NoEditTriggers false false 3 4 false - + 50 - + 50 false - + 50 - + 50 Set multi widget layout - - - - 0 - 0 - + + + Qt::Vertical - + - 0 - 0 + 20 + 10 - - 1 - - - 0 + + + + + + Data-based layout + + + + - Qt::Horizontal + Qt::Vertical - + + + 20 + 10 + + + false Save layout Load layout diff --git a/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp b/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp index 55c15a1c4e..ef255e9858 100644 --- a/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp @@ -1,600 +1,650 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMxNMultiWidget.h" // mitk core #include #include #include #include #include #include // mitk qt widget #include #include // qt #include #include #include #include QmitkMxNMultiWidget::QmitkMxNMultiWidget(QWidget* parent, Qt::WindowFlags f/* = 0*/, const QString& multiWidgetName/* = "mxn"*/) : QmitkAbstractMultiWidget(parent, f, multiWidgetName) , m_SynchronizedWidgetConnector(std::make_unique()) , m_CrosshairVisibility(false) { } QmitkMxNMultiWidget::~QmitkMxNMultiWidget() { } void QmitkMxNMultiWidget::InitializeMultiWidget() { SetLayout(1, 1); SetDisplayActionEventHandler(std::make_unique()); auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } this->SetInitialSelection(); } void QmitkMxNMultiWidget::Synchronize(bool synchronized) { if (synchronized) { SetDisplayActionEventHandler(std::make_unique()); } else { SetDisplayActionEventHandler(std::make_unique()); } auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } } QmitkRenderWindow* QmitkMxNMultiWidget::GetRenderWindow(const QString& widgetName) const { if ("axial" == widgetName || "sagittal" == widgetName || "coronal" == widgetName || "3d" == widgetName) { return GetActiveRenderWindowWidget()->GetRenderWindow(); } return QmitkAbstractMultiWidget::GetRenderWindow(widgetName); } QmitkRenderWindow* QmitkMxNMultiWidget::GetRenderWindow(const mitk::AnatomicalPlane& /*orientation*/) const { // currently no mapping between plane orientation and render windows // simply return the currently active render window return GetActiveRenderWindowWidget()->GetRenderWindow(); } void QmitkMxNMultiWidget::SetActiveRenderWindowWidget(RenderWindowWidgetPointer activeRenderWindowWidget) { auto currentActiveRenderWindowWidget = GetActiveRenderWindowWidget(); if (currentActiveRenderWindowWidget == activeRenderWindowWidget) { return; } // reset the decoration color of the previously active render window widget if (nullptr != currentActiveRenderWindowWidget) { auto decorationColor = currentActiveRenderWindowWidget->GetDecorationColor(); QColor hexColor(decorationColor[0] * 255, decorationColor[1] * 255, decorationColor[2] * 255); currentActiveRenderWindowWidget->setStyleSheet("QmitkRenderWindowWidget { border: 2px solid " + hexColor.name(QColor::HexRgb) + "; }"); } // set the new decoration color of the currently active render window widget if (nullptr != activeRenderWindowWidget) { activeRenderWindowWidget->setStyleSheet("QmitkRenderWindowWidget { border: 2px solid #FF6464; }"); } QmitkAbstractMultiWidget::SetActiveRenderWindowWidget(activeRenderWindowWidget); } void QmitkMxNMultiWidget::InitializeViews(const mitk::TimeGeometry* geometry, bool resetCamera) { auto* renderingManager = mitk::RenderingManager::GetInstance(); mitk::Point3D currentPosition = mitk::Point3D(); unsigned int imageTimeStep = 0; if (!resetCamera) { // store the current position to set it again later, if the camera should not be reset currentPosition = this->GetSelectedPosition(""); // store the current time step to set it again later, if the camera should not be reset const mitk::TimePointType currentTimePoint = renderingManager->GetTimeNavigationController()->GetSelectedTimePoint(); if (geometry->IsValidTimePoint(currentTimePoint)) { imageTimeStep = geometry->TimePointToTimeStep(currentTimePoint); } } // initialize active render window renderingManager->InitializeView( this->GetActiveRenderWindowWidget()->GetRenderWindow()->GetVtkRenderWindow(), geometry, resetCamera); if (!resetCamera) { this->SetSelectedPosition(currentPosition, ""); renderingManager->GetTimeNavigationController()->GetStepper()->SetPos(imageTimeStep); } } void QmitkMxNMultiWidget::SetInteractionReferenceGeometry(const mitk::TimeGeometry* referenceGeometry) { // Set the interaction reference referenceGeometry for all render windows. auto allRenderWindows = this->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { auto* baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow()); baseRenderer->SetInteractionReferenceGeometry(referenceGeometry); } } bool QmitkMxNMultiWidget::HasCoupledRenderWindows() const { return false; } void QmitkMxNMultiWidget::SetSelectedPosition(const mitk::Point3D& newPosition, const QString& widgetName) { RenderWindowWidgetPointer renderWindowWidget; if (widgetName.isNull() || widgetName.isEmpty()) { renderWindowWidget = GetActiveRenderWindowWidget(); } else { renderWindowWidget = GetRenderWindowWidget(widgetName); } if (nullptr != renderWindowWidget) { renderWindowWidget->GetSliceNavigationController()->SelectSliceByPoint(newPosition); return; } MITK_ERROR << "Position can not be set for an unknown render window widget."; } const mitk::Point3D QmitkMxNMultiWidget::GetSelectedPosition(const QString& widgetName) const { RenderWindowWidgetPointer renderWindowWidget; if (widgetName.isNull() || widgetName.isEmpty()) { renderWindowWidget = GetActiveRenderWindowWidget(); } else { renderWindowWidget = GetRenderWindowWidget(widgetName); } if (nullptr != renderWindowWidget) { return renderWindowWidget->GetCrosshairPosition(); } MITK_ERROR << "Crosshair position can not be retrieved."; return mitk::Point3D(0.0); } void QmitkMxNMultiWidget::SetCrosshairVisibility(bool visible) { // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return; } auto renderWindowWidget = this->GetRenderWindowWidget(renderWindow); renderWindowWidget->SetCrosshairVisibility(visible); } bool QmitkMxNMultiWidget::GetCrosshairVisibility() const { // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return false; } auto renderWindowWidget = this->GetRenderWindowWidget(renderWindow); return renderWindowWidget->GetCrosshairVisibility(); } void QmitkMxNMultiWidget::SetCrosshairGap(unsigned int gapSize) { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { renderWindowWidget.second->SetCrosshairGap(gapSize); } } void QmitkMxNMultiWidget::ResetCrosshair() { auto dataStorage = GetDataStorage(); if (nullptr == dataStorage) { return; } // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return; } mitk::RenderingManager::GetInstance()->InitializeViewByBoundingObjects(renderWindow->GetVtkRenderWindow(), dataStorage); SetWidgetPlaneMode(mitk::InteractionSchemeSwitcher::MITKStandard); } void QmitkMxNMultiWidget::SetWidgetPlaneMode(int userMode) { MITK_DEBUG << "Changing crosshair mode to " << userMode; switch (userMode) { case 0: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKStandard); break; case 1: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationUncoupled); break; case 2: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationCoupled); break; case 3: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKSwivel); break; } } void QmitkMxNMultiWidget::EnableCrosshair() { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { renderWindowWidget.second->EnableCrosshair(); } } void QmitkMxNMultiWidget::DisableCrosshair() { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { renderWindowWidget.second->DisableCrosshair(); } } ////////////////////////////////////////////////////////////////////////// // PUBLIC SLOTS // MOUSE EVENTS ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidget::wheelEvent(QWheelEvent* e) { emit WheelMoved(e); } void QmitkMxNMultiWidget::mousePressEvent(QMouseEvent*) { // nothing here, but necessary for mouse interactions (.xml-configuration files) } void QmitkMxNMultiWidget::moveEvent(QMoveEvent* e) { QWidget::moveEvent(e); // it is necessary to readjust the position of the overlays as the MultiWidget has moved // unfortunately it's not done by QmitkRenderWindow::moveEvent -> must be done here emit Moved(); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidget::SetLayoutImpl() { int requiredRenderWindowWidgets = GetRowCount() * GetColumnCount(); int existingRenderWindowWidgets = GetRenderWindowWidgets().size(); int difference = requiredRenderWindowWidgets - existingRenderWindowWidgets; while (0 < difference) { // more render window widgets needed CreateRenderWindowWidget(); --difference; } while (0 > difference) { // less render window widgets needed RemoveRenderWindowWidget(); ++difference; } auto firstRenderWindowWidget = GetFirstRenderWindowWidget(); if (nullptr != firstRenderWindowWidget) { SetActiveRenderWindowWidget(firstRenderWindowWidget); } GetMultiWidgetLayoutManager()->SetLayoutDesign(QmitkMultiWidgetLayoutManager::LayoutDesign::DEFAULT); } QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkMxNMultiWidget::CreateRenderWindowWidget() { // create the render window widget and connect signal / slot QString renderWindowWidgetName = GetNameFromIndex(GetNumberOfRenderWindowWidgets()); RenderWindowWidgetPointer renderWindowWidget = std::make_shared(this, renderWindowWidgetName, GetDataStorage()); renderWindowWidget->SetCornerAnnotationText(renderWindowWidgetName.toStdString()); AddRenderWindowWidget(renderWindowWidgetName, renderWindowWidget); auto renderWindow = renderWindowWidget->GetRenderWindow(); QmitkRenderWindowUtilityWidget* utilityWidget = new QmitkRenderWindowUtilityWidget(this, renderWindow, GetDataStorage()); renderWindowWidget->AddUtilityWidget(utilityWidget); connect(utilityWidget, &QmitkRenderWindowUtilityWidget::SynchronizationToggled, this, &QmitkMxNMultiWidget::ToggleSynchronization); connect(this, &QmitkMxNMultiWidget::UpdateUtilityWidgetViewPlanes, utilityWidget, &QmitkRenderWindowUtilityWidget::UpdateViewPlaneSelection); // needs to be done after 'QmitkRenderWindowUtilityWidget::ToggleSynchronization' has been connected // initially synchronize the node selection widget utilityWidget->ToggleSynchronization(true); auto layoutManager = GetMultiWidgetLayoutManager(); connect(renderWindow, &QmitkRenderWindow::LayoutDesignChanged, layoutManager, &QmitkMultiWidgetLayoutManager::SetLayoutDesign); connect(renderWindow, &QmitkRenderWindow::ResetView, this, &QmitkMxNMultiWidget::ResetCrosshair); connect(renderWindow, &QmitkRenderWindow::CrosshairVisibilityChanged, this, &QmitkMxNMultiWidget::SetCrosshairVisibility); connect(renderWindow, &QmitkRenderWindow::CrosshairRotationModeChanged, this, &QmitkMxNMultiWidget::SetWidgetPlaneMode); return renderWindowWidget; } +QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkMxNMultiWidget::GetWindowFromIndex(size_t index) +{ + if (index >= GetRenderWindowWidgets().size()) + { + return nullptr; + } + + auto renderWindowName = this->GetNameFromIndex(index); + auto renderWindowWidgets = GetRenderWindowWidgets(); + auto it = renderWindowWidgets.find(renderWindowName); + if (it != renderWindowWidgets.end()) + { + return it->second; + } + else + { + MITK_ERROR << "Could not find render window " << renderWindowName.toStdString() << ", although it should be there."; + return nullptr; + } +} + void QmitkMxNMultiWidget::LoadLayout(const nlohmann::json* jsonData) { if ((*jsonData).is_null()) { QMessageBox::warning(this, "Load layout", "Could not read window layout"); return; } unsigned int windowCounter = 0; try { auto version = jsonData->at("version").get(); if (version != "1.0") { QMessageBox::warning(this, "Load layout", "Unknown layout version, could not load"); return; } delete this->layout(); auto content = BuildLayoutFromJSON(jsonData, &windowCounter); auto hBoxLayout = new QHBoxLayout(this); this->setLayout(hBoxLayout); hBoxLayout->addWidget(content); emit UpdateUtilityWidgetViewPlanes(); } catch (nlohmann::json::out_of_range& e) { MITK_ERROR << "Error in loading window layout from JSON: " << e.what(); return; } while (GetNumberOfRenderWindowWidgets() > windowCounter) { RemoveRenderWindowWidget(); } EnableCrosshair(); emit LayoutChanged(); } void QmitkMxNMultiWidget::SaveLayout(std::ostream* outStream) { if (outStream == nullptr) { return; } auto layout = this->layout(); if (layout == nullptr) return; // There should only ever be one item: a splitter auto widget = layout->itemAt(0)->widget(); auto splitter = dynamic_cast(widget); if (!splitter) { MITK_ERROR << "Tried to save unexpected layout format. Make sure the layout of this instance contains a single QSplitter."; return; } auto layoutJSON = BuildJSONFromLayout(splitter); layoutJSON["version"] = "1.0"; layoutJSON["name"] = "Custom Layout"; *outStream << std::setw(4) << layoutJSON << std::endl; } nlohmann::json QmitkMxNMultiWidget::BuildJSONFromLayout(const QSplitter* splitter) { nlohmann::json resultJSON; resultJSON["isWindow"] = false; resultJSON["vertical"] = (splitter->orientation() == Qt::Vertical) ? true : false; auto sizes = splitter->sizes(); auto content = nlohmann::json::array(); auto countSplitter = splitter->count(); for (int i = 0; i < countSplitter; ++i) { auto widget = splitter->widget(i); nlohmann::json widgetJSON; if (auto widgetSplitter = dynamic_cast(widget); widgetSplitter) { widgetJSON = BuildJSONFromLayout(widgetSplitter); } else if (auto widgetWindow = dynamic_cast(widget); widgetWindow) { widgetJSON["isWindow"] = true; widgetJSON["viewDirection"] = widgetWindow->GetSliceNavigationController()->GetViewDirectionAsString(); } widgetJSON["size"] = sizes[i]; content.push_back(widgetJSON); } resultJSON["content"] = content; return resultJSON; } QSplitter* QmitkMxNMultiWidget::BuildLayoutFromJSON(const nlohmann::json* jsonData, unsigned int* windowCounter, QSplitter* parentSplitter) { bool vertical = jsonData->at("vertical").get(); auto orientation = vertical ? Qt::Vertical : Qt::Horizontal; auto split = new QSplitter(orientation, parentSplitter); QList sizes; for (auto object : jsonData->at("content")) { bool isWindow = object["isWindow"].get(); int size = object["size"].get(); sizes.append(size); if (isWindow) { auto viewDirection = object["viewDirection"].get(); mitk::AnatomicalPlane viewPlane = mitk::AnatomicalPlane::Sagittal; if (viewDirection == "Axial") { viewPlane = mitk::AnatomicalPlane::Axial; } else if (viewDirection == "Coronal") { viewPlane = mitk::AnatomicalPlane::Coronal; } else if (viewDirection == "Original") { viewPlane = mitk::AnatomicalPlane::Original; } else if (viewDirection == "Sagittal") { viewPlane = mitk::AnatomicalPlane::Sagittal; } - QmitkAbstractMultiWidget::RenderWindowWidgetPointer window = nullptr; - QString renderWindowName; - QmitkAbstractMultiWidget::RenderWindowWidgetMap::iterator it; - // repurpose existing render windows as far as they already exist - if (*windowCounter < GetRenderWindowWidgets().size()) - { - renderWindowName = this->GetNameFromIndex(*windowCounter); - auto renderWindowWidgets = GetRenderWindowWidgets(); - it = renderWindowWidgets.find(renderWindowName); - if (it != renderWindowWidgets.end()) - { - window = it->second; - } - else - { - MITK_ERROR << "Could not find render window " << renderWindowName.toStdString() << ", although it should be there."; - } - } - + auto window = GetWindowFromIndex(*windowCounter); if (window == nullptr) { window = CreateRenderWindowWidget(); } window->GetSliceNavigationController()->SetDefaultViewDirection(viewPlane); window->GetSliceNavigationController()->Update(); split->addWidget(window.get()); window->show(); (*windowCounter)++; } else { auto subSplitter = BuildLayoutFromJSON(&object, windowCounter, split); split->addWidget(subSplitter); } } split->setSizes(sizes); return split; } +void QmitkMxNMultiWidget::SetDataBasedLayout(const QmitkAbstractNodeSelectionWidget::NodeList& nodes) +{ + auto vSplit = new QSplitter(Qt::Vertical); + + unsigned int windowCounter = 0; + for (auto node : nodes) + { + auto hSplit = new QSplitter(Qt::Horizontal); + for (auto viewPlane : { mitk::AnatomicalPlane::Axial, mitk::AnatomicalPlane::Coronal, mitk::AnatomicalPlane::Sagittal }) + { + // repurpose existing render windows as far as they already exist + auto window = GetWindowFromIndex(windowCounter++); + if (window == nullptr) + { + window = CreateRenderWindowWidget(); + } + + auto utilityWidget = window->GetUtilityWidget(); + utilityWidget->ToggleSynchronization(false); + utilityWidget->SetDataSelection(QList({ node })); + window->GetSliceNavigationController()->SetDefaultViewDirection(viewPlane); + window->GetSliceNavigationController()->Update(); + auto baseRenderer = mitk::BaseRenderer::GetInstance(window->GetRenderWindow()->GetVtkRenderWindow()); + mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), node->GetData()->GetTimeGeometry()); + hSplit->addWidget(window.get()); + window->show(); + } + auto sizes = QList({1, 1, 1}); + hSplit->setSizes(sizes); + vSplit->addWidget(hSplit); + } + + delete this->layout(); + auto hBoxLayout = new QHBoxLayout(this); + this->setLayout(hBoxLayout); + hBoxLayout->addWidget(vSplit); + emit UpdateUtilityWidgetViewPlanes(); + + while (GetNumberOfRenderWindowWidgets() > windowCounter) + { + RemoveRenderWindowWidget(); + } + + EnableCrosshair(); + emit LayoutChanged(); +} + void QmitkMxNMultiWidget::SetInitialSelection() { auto dataStorage = this->GetDataStorage(); if (nullptr == dataStorage) { return; } mitk::NodePredicateAnd::Pointer noHelperObjects = mitk::NodePredicateAnd::New(); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); auto allNodes = dataStorage->GetSubset(noHelperObjects); QmitkSynchronizedNodeSelectionWidget::NodeList currentSelection; for (auto& node : *allNodes) { currentSelection.append(node); } m_SynchronizedWidgetConnector->ChangeSelection(currentSelection); } void QmitkMxNMultiWidget::ToggleSynchronization(QmitkSynchronizedNodeSelectionWidget* synchronizedWidget) { bool synchronized = synchronizedWidget->IsSynchronized(); if (synchronized) { m_SynchronizedWidgetConnector->ConnectWidget(synchronizedWidget); m_SynchronizedWidgetConnector->SynchronizeWidget(synchronizedWidget); } else { m_SynchronizedWidgetConnector->DisconnectWidget(synchronizedWidget); } } diff --git a/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp b/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp index 4934c397d3..47057d7442 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp @@ -1,185 +1,190 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkRenderWindowUtilityWidget.h" #include // mitk core #include #include #include #include // mitk qt widgets #include #include // itk #include QmitkRenderWindowUtilityWidget::QmitkRenderWindowUtilityWidget( QWidget* parent/* = nullptr */, QmitkRenderWindow* renderWindow/* = nullptr */, mitk::DataStorage* dataStorage/* = nullptr */) : m_NodeSelectionWidget(nullptr) , m_SliceNavigationWidget(nullptr) , m_StepperAdapter(nullptr) , m_ViewDirectionSelector(nullptr) { this->setParent(parent); auto layout = new QHBoxLayout(this); layout->setContentsMargins({}); mitk::NodePredicateAnd::Pointer noHelperObjects = mitk::NodePredicateAnd::New(); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_BaseRenderer = mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow()); m_NodeSelectionWidget = new QmitkSynchronizedNodeSelectionWidget(parent); m_NodeSelectionWidget->SetBaseRenderer(m_BaseRenderer); m_NodeSelectionWidget->SetDataStorage(dataStorage); m_NodeSelectionWidget->SetNodePredicate(noHelperObjects); + connect(this, &QmitkRenderWindowUtilityWidget::SetDataSelection, m_NodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelection); auto menuBar = new QMenuBar(this); menuBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); menuBar->setNativeMenuBar(false); auto dataMenu = menuBar->addMenu("Data"); QWidgetAction* dataAction = new QWidgetAction(dataMenu); dataAction->setDefaultWidget(m_NodeSelectionWidget); dataMenu->addAction(dataAction); layout->addWidget(menuBar); - auto* synchPushButton = new QPushButton(this); + m_SynchPushButton = new QPushButton(this); auto* synchIcon = new QIcon(); auto synchronizeSvg = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto desynchronizeSvg = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); synchIcon->addPixmap(synchronizeSvg.pixmap(64), QIcon::Normal, QIcon::On); synchIcon->addPixmap(desynchronizeSvg.pixmap(64), QIcon::Normal, QIcon::Off); - synchPushButton->setIcon(*synchIcon); - synchPushButton->setToolTip("Synchronize / desynchronize data management"); - synchPushButton->setCheckable(true); - synchPushButton->setChecked(true); - connect(synchPushButton, &QPushButton::clicked, + m_SynchPushButton->setIcon(*synchIcon); + m_SynchPushButton->setToolTip("Synchronize / desynchronize data management"); + m_SynchPushButton->setCheckable(true); + m_SynchPushButton->setChecked(true); + connect(m_SynchPushButton, &QPushButton::clicked, this, &QmitkRenderWindowUtilityWidget::ToggleSynchronization); - layout->addWidget(synchPushButton); + layout->addWidget(m_SynchPushButton); auto* sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); m_SliceNavigationWidget = new QmitkSliceNavigationWidget(this); m_StepperAdapter = new QmitkStepperAdapter(m_SliceNavigationWidget, sliceNavigationController->GetStepper()); layout->addWidget(m_SliceNavigationWidget); mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer{ m_BaseRenderer }; m_RenderWindowViewDirectionController = std::make_unique(); m_RenderWindowViewDirectionController->SetControlledRenderer(controlledRenderer); m_RenderWindowViewDirectionController->SetDataStorage(dataStorage); m_ViewDirectionSelector = new QComboBox(this); QStringList viewDirections{ "axial", "coronal", "sagittal"}; m_ViewDirectionSelector->insertItems(0, viewDirections); m_ViewDirectionSelector->setMinimumContentsLength(12); connect(m_ViewDirectionSelector, &QComboBox::currentTextChanged, this, &QmitkRenderWindowUtilityWidget::ChangeViewDirection); UpdateViewPlaneSelection(); layout->addWidget(m_ViewDirectionSelector); // finally add observer, after all relevant objects have been created / initialized sliceNavigationController->ConnectGeometrySendEvent(this); } QmitkRenderWindowUtilityWidget::~QmitkRenderWindowUtilityWidget() { } void QmitkRenderWindowUtilityWidget::ToggleSynchronization(bool synchronized) { + if (m_SynchPushButton->isChecked() != synchronized) + { + m_SynchPushButton->setChecked(synchronized); + } m_NodeSelectionWidget->SetSynchronized(synchronized); emit SynchronizationToggled(m_NodeSelectionWidget); } void QmitkRenderWindowUtilityWidget::SetGeometry(const itk::EventObject& event) { if (!mitk::SliceNavigationController::GeometrySendEvent(nullptr, 0).CheckEvent(&event)) { return; } const auto* sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); auto viewDirection = sliceNavigationController->GetViewDirection(); unsigned int axis = 0; switch (viewDirection) { case mitk::AnatomicalPlane::Original: return; case mitk::AnatomicalPlane::Axial: { axis = 2; break; } case mitk::AnatomicalPlane::Coronal: { axis = 1; break; } case mitk::AnatomicalPlane::Sagittal: { axis = 0; break; } } const auto* inputTimeGeometry = sliceNavigationController->GetInputWorldTimeGeometry(); const mitk::BaseGeometry* rendererGeometry = m_BaseRenderer->GetCurrentWorldGeometry(); mitk::TimeStepType timeStep = sliceNavigationController->GetStepper()->GetPos(); mitk::BaseGeometry::ConstPointer geometry = inputTimeGeometry->GetGeometryForTimeStep(timeStep); if (geometry == nullptr) return; mitk::AffineTransform3D::MatrixType matrix = geometry->GetIndexToWorldTransform()->GetMatrix(); matrix.GetVnlMatrix().normalize_columns(); mitk::AffineTransform3D::MatrixType::InternalMatrixType inverseMatrix = matrix.GetInverse(); int dominantAxis = itk::Function::Max3(inverseMatrix[0][axis], inverseMatrix[1][axis], inverseMatrix[2][axis]); bool referenceGeometryAxisInverted = inverseMatrix[dominantAxis][axis] < 0; bool rendererZAxisInverted = rendererGeometry->GetAxisVector(2)[axis] < 0; m_SliceNavigationWidget->SetInverseDirection(referenceGeometryAxisInverted != rendererZAxisInverted); } void QmitkRenderWindowUtilityWidget::ChangeViewDirection(const QString& viewDirection) { m_RenderWindowViewDirectionController->SetViewDirectionOfRenderer(viewDirection.toStdString()); } void QmitkRenderWindowUtilityWidget::UpdateViewPlaneSelection() { const auto sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); const auto viewDirection = sliceNavigationController->GetDefaultViewDirection(); switch (viewDirection) { case mitk::AnatomicalPlane::Axial: m_ViewDirectionSelector->setCurrentIndex(0); break; case mitk::AnatomicalPlane::Coronal: m_ViewDirectionSelector->setCurrentIndex(1); break; case mitk::AnatomicalPlane::Sagittal: m_ViewDirectionSelector->setCurrentIndex(2); break; default: break; } } diff --git a/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp b/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp index 8c7d54c727..730adb3ea7 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp @@ -1,322 +1,333 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkRenderWindowWidget.h" // vtk #include #include QmitkRenderWindowWidget::QmitkRenderWindowWidget(QWidget* parent/* = nullptr*/, const QString& widgetName/* = ""*/, mitk::DataStorage* dataStorage/* = nullptr*/) : QFrame(parent) , m_WidgetName(widgetName) , m_DataStorage(dataStorage) , m_RenderWindow(nullptr) , m_CrosshairManager(nullptr) { this->InitializeGUI(); } QmitkRenderWindowWidget::~QmitkRenderWindowWidget() { auto sliceNavigationController = this->GetSliceNavigationController(); if (nullptr != sliceNavigationController) { sliceNavigationController->SetCrosshairEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkRenderWindowWidget::SetCrosshairPosition)); } this->DisableCrosshair(); } void QmitkRenderWindowWidget::SetDataStorage(mitk::DataStorage* dataStorage) { if (dataStorage == m_DataStorage) { return; } m_DataStorage = dataStorage; if (nullptr != m_RenderWindow) { mitk::BaseRenderer::GetInstance(m_RenderWindow->renderWindow())->SetDataStorage(dataStorage); } } mitk::SliceNavigationController* QmitkRenderWindowWidget::GetSliceNavigationController() const { return m_RenderWindow->GetSliceNavigationController(); } void QmitkRenderWindowWidget::RequestUpdate() { mitk::RenderingManager::GetInstance()->RequestUpdate(m_RenderWindow->renderWindow()); } void QmitkRenderWindowWidget::ForceImmediateUpdate() { mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(m_RenderWindow->renderWindow()); } void QmitkRenderWindowWidget::AddUtilityWidget(QWidget* utilityWidget) { m_Layout->insertWidget(0, utilityWidget); } +QmitkRenderWindowUtilityWidget* QmitkRenderWindowWidget::GetUtilityWidget() +{ + auto layoutItem = m_Layout->itemAt(0)->widget(); + auto utilityWidget = dynamic_cast(layoutItem); + if (utilityWidget != nullptr) + { + return utilityWidget; + } + return nullptr; +} + void QmitkRenderWindowWidget::SetGradientBackgroundColors(const mitk::Color& upper, const mitk::Color& lower) { vtkRenderer* vtkRenderer = m_RenderWindow->GetRenderer()->GetVtkRenderer(); if (nullptr == vtkRenderer) { return; } m_GradientBackgroundColors.first = upper; m_GradientBackgroundColors.second = lower; vtkRenderer->SetBackground(lower[0], lower[1], lower[2]); vtkRenderer->SetBackground2(upper[0], upper[1], upper[2]); ShowGradientBackground(true); } void QmitkRenderWindowWidget::ShowGradientBackground(bool show) { m_RenderWindow->GetRenderer()->GetVtkRenderer()->SetGradientBackground(show); } bool QmitkRenderWindowWidget::IsGradientBackgroundOn() const { return m_RenderWindow->GetRenderer()->GetVtkRenderer()->GetGradientBackground(); } void QmitkRenderWindowWidget::SetDecorationColor(const mitk::Color& color) { m_DecorationColor = color; m_CornerAnnotation->GetTextProperty()->SetColor(m_DecorationColor[0], m_DecorationColor[1], m_DecorationColor[2]); QColor hexColor(m_DecorationColor[0] * 255, m_DecorationColor[1] * 255, m_DecorationColor[2] * 255); setStyleSheet("QmitkRenderWindowWidget { border: 2px solid " + hexColor.name(QColor::HexRgb) + "; }"); } void QmitkRenderWindowWidget::ShowColoredRectangle(bool show) { if (show) { setFrameStyle(QFrame::Box | QFrame::Plain); } else { setFrameStyle(NoFrame); } } bool QmitkRenderWindowWidget::IsColoredRectangleVisible() const { return frameStyle() > 0; } void QmitkRenderWindowWidget::ShowCornerAnnotation(bool show) { m_CornerAnnotation->SetVisibility(show); } bool QmitkRenderWindowWidget::IsCornerAnnotationVisible() const { return m_CornerAnnotation->GetVisibility() > 0; } void QmitkRenderWindowWidget::SetCornerAnnotationText(const std::string& cornerAnnotation) { m_CornerAnnotation->SetText(0, cornerAnnotation.c_str()); } std::string QmitkRenderWindowWidget::GetCornerAnnotationText() const { return std::string(m_CornerAnnotation->GetText(0)); } bool QmitkRenderWindowWidget::IsRenderWindowMenuActivated() const { return m_RenderWindow->GetActivateMenuWidgetFlag(); } void QmitkRenderWindowWidget::SetCrosshairVisibility(bool visible) { m_CrosshairManager->SetCrosshairVisibility(visible, m_RenderWindow->GetRenderer()); this->RequestUpdate(); } bool QmitkRenderWindowWidget::GetCrosshairVisibility() { return m_CrosshairManager->GetCrosshairVisibility(m_RenderWindow->GetRenderer()); } void QmitkRenderWindowWidget::SetCrosshairGap(unsigned int gapSize) { m_CrosshairManager->SetCrosshairGap(gapSize); } void QmitkRenderWindowWidget::EnableCrosshair() { m_CrosshairManager->AddCrosshairNodeToDataStorage(m_DataStorage); } void QmitkRenderWindowWidget::DisableCrosshair() { m_CrosshairManager->RemoveCrosshairNodeFromDataStorage(m_DataStorage); } void QmitkRenderWindowWidget::InitializeGUI() { m_Layout = new QVBoxLayout(this); m_Layout->setContentsMargins({}); setLayout(m_Layout); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setContentsMargins(0, 0, 0, 0); if (nullptr == m_DataStorage) { return; } mitk::RenderingManager::GetInstance()->SetDataStorage(m_DataStorage); // create render window for this render window widget m_RenderWindow = new QmitkRenderWindow(this, m_WidgetName, nullptr); m_RenderWindow->SetLayoutIndex(mitk::AnatomicalPlane::Sagittal); connect(m_RenderWindow, &QmitkRenderWindow::ResetGeometry, this, &QmitkRenderWindowWidget::OnResetGeometry); auto* sliceNavigationController = this->GetSliceNavigationController(); sliceNavigationController->SetDefaultViewDirection(mitk::AnatomicalPlane::Sagittal); m_Layout->addWidget(m_RenderWindow); // set colors and corner annotation InitializeDecorations(); // use crosshair manager m_CrosshairManager = mitk::CrosshairManager::New(m_RenderWindow->GetRenderer()); sliceNavigationController->SetCrosshairEvent.AddListener( mitk::MessageDelegate1( this, &QmitkRenderWindowWidget::SetCrosshairPosition)); // finally add observer, after all relevant objects have been created / initialized sliceNavigationController->ConnectGeometrySendEvent(this); sliceNavigationController->ConnectGeometrySliceEvent(this); mitk::TimeGeometry::ConstPointer timeGeometry = m_DataStorage->ComputeBoundingGeometry3D(m_DataStorage->GetAll()); mitk::RenderingManager::GetInstance()->InitializeView(m_RenderWindow->GetVtkRenderWindow(), timeGeometry); } void QmitkRenderWindowWidget::InitializeDecorations() { vtkRenderer* vtkRenderer = m_RenderWindow->GetRenderer()->GetVtkRenderer(); if (nullptr == vtkRenderer) { return; } // initialize background color gradients float black[3] = { 0.0f, 0.0f, 0.0f }; SetGradientBackgroundColors(black, black); // initialize annotation text and decoration color setFrameStyle(QFrame::Box | QFrame::Plain); m_CornerAnnotation = vtkSmartPointer::New(); m_CornerAnnotation->SetText(0, "Sagittal"); m_CornerAnnotation->SetMaximumFontSize(12); if (0 == vtkRenderer->HasViewProp(m_CornerAnnotation)) { vtkRenderer->AddViewProp(m_CornerAnnotation); } float white[3] = { 1.0f, 1.0f, 1.0f }; SetDecorationColor(mitk::Color(white)); } void QmitkRenderWindowWidget::SetCrosshairPosition(const mitk::Point3D& newPosition) { m_CrosshairManager->SetCrosshairPosition(newPosition); this->RequestUpdate(); } mitk::Point3D QmitkRenderWindowWidget::GetCrosshairPosition() const { return m_CrosshairManager->GetCrosshairPosition(); } void QmitkRenderWindowWidget::SetGeometry(const itk::EventObject& event) { if (!mitk::SliceNavigationController::GeometrySendEvent(nullptr, 0).CheckEvent(&event)) { return; } const auto* planeGeometry = this->GetSliceNavigationController()->GetCurrentPlaneGeometry(); if (nullptr == planeGeometry) { mitkThrow() << "No valid plane geometry set. Render window is in an invalid state."; } return SetCrosshairPosition(planeGeometry->GetCenter()); } void QmitkRenderWindowWidget::SetGeometrySlice(const itk::EventObject& event) { if (!mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0).CheckEvent(&event)) { return; } const auto* sliceNavigationController = this->GetSliceNavigationController(); m_CrosshairManager->UpdateCrosshairPosition(sliceNavigationController); } void QmitkRenderWindowWidget::OnResetGeometry() { const auto* baseRenderer = mitk::BaseRenderer::GetInstance(m_RenderWindow->GetVtkRenderWindow()); const auto* interactionReferenceGeometry = baseRenderer->GetInteractionReferenceGeometry(); this->ResetGeometry(interactionReferenceGeometry); m_RenderWindow->ShowOverlayMessage(false); } void QmitkRenderWindowWidget::ResetGeometry(const mitk::TimeGeometry* referenceGeometry) { if (nullptr == referenceGeometry) { return; } mitk::TimeStepType imageTimeStep = 0; // store the current position to set it again later, if the camera should not be reset mitk::Point3D currentPosition = this->GetCrosshairPosition(); // store the current time step to set it again later, if the camera should not be reset auto* renderingManager = mitk::RenderingManager::GetInstance(); const mitk::TimePointType currentTimePoint = renderingManager->GetTimeNavigationController()->GetSelectedTimePoint(); if (referenceGeometry->IsValidTimePoint(currentTimePoint)) { imageTimeStep = referenceGeometry->TimePointToTimeStep(currentTimePoint); } const auto* baseRenderer = mitk::BaseRenderer::GetInstance(m_RenderWindow->renderWindow()); renderingManager->InitializeView(baseRenderer->GetRenderWindow(), referenceGeometry, false); // reset position and time step this->GetSliceNavigationController()->SelectSliceByPoint(currentPosition); renderingManager->GetTimeNavigationController()->GetStepper()->SetPos(imageTimeStep); } diff --git a/Modules/QtWidgets/src/QmitkSingleNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkSingleNodeSelectionWidget.cpp index fc30ea2679..d4c462c133 100644 --- a/Modules/QtWidgets/src/QmitkSingleNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkSingleNodeSelectionWidget.cpp @@ -1,240 +1,245 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSingleNodeSelectionWidget.h" #include #include #include #include #include #include QmitkSingleNodeSelectionWidget::QmitkSingleNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) , m_AutoSelectNodes(false) { m_Controls.setupUi(this); m_Controls.btnSelect->installEventFilter(this); m_Controls.btnSelect->setVisible(true); m_Controls.btnClear->setVisible(false); m_Controls.btnClear->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/times.svg"))); this->UpdateInfo(); connect(m_Controls.btnClear, SIGNAL(clicked(bool)), this, SLOT(OnClearSelection())); } void QmitkSingleNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { if (newInternalSelection.empty()) { if (m_AutoSelectNodes) { auto autoSelectedNode = this->DetermineAutoSelectNode(oldInternalSelection); if (autoSelectedNode.IsNotNull()) { newInternalSelection.append(autoSelectedNode); } } } else if (newInternalSelection.size()>1) { //this widget only allows one internal selected node. newInternalSelection = { newInternalSelection.front() }; } } void QmitkSingleNodeSelectionWidget::OnClearSelection() { if (m_IsOptional) { this->SetCurrentSelection({}); } this->UpdateInfo(); } mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::GetSelectedNode() const { mitk::DataNode::Pointer result; auto selection = GetCurrentInternalSelection(); if (!selection.empty()) { result = selection.front(); } return result; } bool QmitkSingleNodeSelectionWidget::eventFilter(QObject *obj, QEvent *ev) { if (obj == m_Controls.btnSelect) { if (ev->type() == QEvent::MouseButtonRelease) { auto mouseEv = dynamic_cast(ev); if (!mouseEv) { return false; } if (mouseEv->button() == Qt::LeftButton) { if (this->isEnabled()) { this->EditSelection(); return true; } } else { auto selection = this->CompileEmitSelection(); if (!selection.empty()) { QmitkNodeDetailsDialog infoDialog(selection, this); infoDialog.exec(); return true; } } } } return false; } void QmitkSingleNodeSelectionWidget::EditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this, m_PopUpTitel, m_PopUpHint); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(this->GetCurrentInternalSelection()); dialog->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); dialog->SetSelectionMode(QAbstractItemView::SingleSelection); m_Controls.btnSelect->setChecked(true); if (dialog->exec()) { this->HandleChangeOfInternalSelection(dialog->GetSelectedNodes()); } m_Controls.btnSelect->setChecked(false); delete dialog; } void QmitkSingleNodeSelectionWidget::UpdateInfo() { if (this->GetSelectedNode().IsNull()) { if (m_IsOptional) { m_Controls.btnSelect->SetNodeInfo(m_EmptyInfo); } else { m_Controls.btnSelect->SetNodeInfo(m_InvalidInfo); } m_Controls.btnSelect->SetSelectionIsOptional(m_IsOptional); m_Controls.btnClear->setVisible(false); } else { m_Controls.btnClear->setVisible(m_IsOptional); } m_Controls.btnSelect->SetSelectedNode(this->GetSelectedNode()); } void QmitkSingleNodeSelectionWidget::SetCurrentSelectedNode(mitk::DataNode* selectedNode) { NodeList selection; if (selectedNode) { selection.append(selectedNode); } this->SetCurrentSelection(selection); } void QmitkSingleNodeSelectionWidget::OnDataStorageChanged() { this->AutoSelectNodes(); } void QmitkSingleNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* /*node*/) { this->AutoSelectNodes(); } +void QmitkSingleNodeSelectionWidget::OnNodePredicateChanged() +{ + this->AutoSelectNodes(); +} + bool QmitkSingleNodeSelectionWidget::GetAutoSelectNewNodes() const { return m_AutoSelectNodes; } void QmitkSingleNodeSelectionWidget::SetAutoSelectNewNodes(bool autoSelect) { m_AutoSelectNodes = autoSelect; this->AutoSelectNodes(); } void QmitkSingleNodeSelectionWidget::AutoSelectNodes() { if (this->GetSelectedNode().IsNull() && m_AutoSelectNodes) { auto autoNode = this->DetermineAutoSelectNode(); if (autoNode.IsNotNull()) { this->HandleChangeOfInternalSelection({ autoNode }); } } } mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::DetermineAutoSelectNode(const NodeList& ignoreNodes) { mitk::DataNode::Pointer result; auto storage = m_DataStorage.Lock(); if (storage.IsNotNull()) { auto ignoreCheck = [ignoreNodes](const mitk::DataNode * node) { bool result = true; for (const auto& ignoreNode : ignoreNodes) { if (node == ignoreNode) { result = false; break; } } return result; }; mitk::NodePredicateFunction::Pointer isNotIgnoredNode = mitk::NodePredicateFunction::New(ignoreCheck); mitk::NodePredicateBase::Pointer predicate = isNotIgnoredNode.GetPointer(); if (m_NodePredicate.IsNotNull()) { predicate = mitk::NodePredicateAnd::New(m_NodePredicate.GetPointer(), predicate.GetPointer()).GetPointer(); } result = storage->GetNode(predicate); } return result; } diff --git a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp index aae35e9ac6..f1d8bbbc7a 100644 --- a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp @@ -1,709 +1,708 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // mitk qt widgets module #include #include #include #include // mitk core module #include #include #include #include QmitkSynchronizedNodeSelectionWidget::QmitkSynchronizedNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) { m_Controls.setupUi(this); m_StorageModel = std::make_unique(this); m_Controls.tableView->setModel(m_StorageModel.get()); m_Controls.tableView->horizontalHeader()->setVisible(false); m_Controls.tableView->verticalHeader()->setVisible(false); m_Controls.tableView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.tableView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.tableView->setContextMenuPolicy(Qt::CustomContextMenu); + m_Controls.tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_Controls.tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); this->SetUpConnections(); this->Initialize(); } QmitkSynchronizedNodeSelectionWidget::~QmitkSynchronizedNodeSelectionWidget() { bool isSynchronized = this->IsSynchronized(); if (isSynchronized) { emit DeregisterSynchronization(); return; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } // If the model is not synchronized, // we know that renderer-specific properties exist for all nodes. // These properties need to be removed from the nodes. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // Delete the relevant renderer-specific properties for the node using the current base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); } } void QmitkSynchronizedNodeSelectionWidget::SetBaseRenderer(mitk::BaseRenderer* baseRenderer) { if (m_BaseRenderer == baseRenderer) { // no need to do something return; } if (nullptr == baseRenderer) { return; } auto oldBaseRenderer = m_BaseRenderer.Lock(); m_BaseRenderer = baseRenderer; auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } bool isSynchronized = this->IsSynchronized(); if (isSynchronized) { // If the model is synchronized, // all nodes use global / default properties. // No renderer-specific property lists should exist // so there is no need to transfer any property values. } else { // If the model is not synchronized, // we know that renderer-specific properties exist for all nodes. // These properties need to be removed from the nodes and // we need to transfer their values to new renderer-specific properties. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // Set the relevant renderer-specific properties for the node using the new base renderer. // By transferring the values from the old property list, // the same property-state is kept when switching to another base renderer. mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, oldBaseRenderer); // Delete the relevant renderer-specific properties for the node using the old base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, oldBaseRenderer); } } this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::SetSelectAll(bool selectAll) { if (selectAll == m_Controls.selectionModeCheckBox->isChecked()) { // no need to do something return; } m_Controls.selectionModeCheckBox->setChecked(selectAll); } bool QmitkSynchronizedNodeSelectionWidget::GetSelectAll() const { return m_Controls.selectionModeCheckBox->isChecked(); } void QmitkSynchronizedNodeSelectionWidget::SetSynchronized(bool synchronize) { if (synchronize == this->IsSynchronized()) { // no need to do something return; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } if (synchronize) { // set the base renderer of the model to nullptr, such that global properties are used m_StorageModel->SetCurrentRenderer(nullptr); // If the model is synchronized, // we know that the model was not synchronized before. // That means that all nodes use renderer-specific properties, // but now all nodes need global properties. // Thus we need to remove the renderer-specific properties of all nodes of the // datastorage. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // For helper / hidden nodes: // If the node predicate does not match, do not remove the renderer-specific property // This is relevant for the crosshair data nodes, which are only visible inside their // corresponding render window. if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { // Delete the relevant renderer-specific properties for the node using the current base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); } } } else { // set the base renderer of the model to current base renderer, such that renderer-specific properties are used m_StorageModel->SetCurrentRenderer(baseRenderer); // If the model is not synchronized anymore, // we know that the model was synchronized before. // That means that all nodes use global / default properties, // but now all nodes need renderer-specific properties. // Thus we need to modify the renderer-specific properties of all nodes of the // datastorage: // - hide those nodes, which are not part of the newly selected nodes. // - keep the property values of those nodes, which are part of the new selection AND // have been selected before auto currentNodeSelection = this->GetCurrentInternalSelection(); auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // check if the node is part of the current selection auto finding = std::find(std::begin(currentNodeSelection), std::end(currentNodeSelection), node); if (finding != std::end(currentNodeSelection)) // node found / part of the current selection { // Set the relevant renderer-specific properties for the node using the curent base renderer. // By transferring the values from the global / default property list, // the same property-state is kept when switching to non-synchronized mode. mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, nullptr); } else { // If the node is not part of the selection, unset the relevant renderer-specific properties. // This will unset the "visible" and "layer" property for the renderer-specific property list and // hide the node for this renderer. // ATTENTION: This is required, since the synchronized property needs to be overwritten // to make sure that the visibility is correctly set for the specific base renderer. this->DeselectNode(node); } } } // Since the synchronization might lead to a different node order depending on the layer properties, the render window // needs to be updated. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); } bool QmitkSynchronizedNodeSelectionWidget::IsSynchronized() const { return m_StorageModel->GetCurrentRenderer().IsNull(); } -void QmitkSynchronizedNodeSelectionWidget::OnModelUpdated() -{ - m_Controls.tableView->resizeRowsToContents(); - m_Controls.tableView->resizeColumnsToContents(); -} - void QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged(bool selectAll) { emit SelectionModeChanged(selectAll); if (selectAll) { this->SelectAll(); } } void QmitkSynchronizedNodeSelectionWidget::OnEditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(m_StorageModel->GetCurrentSelection()); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); m_Controls.changeSelectionButton->setChecked(true); if (dialog->exec()) { m_Controls.selectionModeCheckBox->setChecked(false); emit SelectionModeChanged(false); auto selectedNodes = dialog->GetSelectedNodes(); this->HandleChangeOfInternalSelection(selectedNodes); } m_Controls.changeSelectionButton->setChecked(false); delete dialog; } void QmitkSynchronizedNodeSelectionWidget::OnTableClicked(const QModelIndex& index) { if (!index.isValid() || m_StorageModel.get() != index.model()) { return; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } QVariant dataNodeVariant = index.data(QmitkDataNodeRole); auto dataNode = dataNodeVariant.value(); if (index.column() == 1) // node visibility column { bool visibiliy = index.data(Qt::EditRole).toBool(); m_StorageModel->setData(index, QVariant(!visibiliy), Qt::EditRole); return; } if (index.column() == 2) // reinit node column { this->ReinitNode(dataNode); return; } if (index.column() == 3) // remove node column { this->RemoveFromInternalSelection(dataNode); return; } } void QmitkSynchronizedNodeSelectionWidget::SetUpConnections() { - connect(m_StorageModel.get(), &QmitkRenderWindowDataNodeTableModel::ModelUpdated, - this, &QmitkSynchronizedNodeSelectionWidget::OnModelUpdated); - connect(m_Controls.selectionModeCheckBox, &QCheckBox::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged); connect(m_Controls.changeSelectionButton, &QPushButton::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnEditSelection); connect(m_Controls.tableView, &QTableView::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnTableClicked); } +void QmitkSynchronizedNodeSelectionWidget::SetSelection(const NodeList& newSelection) +{ + this->HandleChangeOfInternalSelection(newSelection); + m_Controls.selectionModeCheckBox->setChecked(false); +} + void QmitkSynchronizedNodeSelectionWidget::Initialize() { auto baseRenderer = m_BaseRenderer.Lock(); auto dataStorage = m_DataStorage.Lock(); m_StorageModel->SetDataStorage(dataStorage); m_StorageModel->SetCurrentRenderer(baseRenderer); if (baseRenderer.IsNull() || dataStorage.IsNull()) { m_Controls.selectionModeCheckBox->setEnabled(false); m_Controls.changeSelectionButton->setEnabled(false); // reset the model if no data storage is defined m_StorageModel->removeRows(0, m_StorageModel->rowCount()); return; } // Use the new data storage / node predicate to correctly set the list of // currently selected data nodes for the model. // If a new data storage or node predicate has been defined, // we switch to the "selectAll" mode and synchronize the selection for simplicity. // enable UI m_Controls.selectionModeCheckBox->setEnabled(true); m_Controls.changeSelectionButton->setEnabled(true); m_Controls.selectionModeCheckBox->setChecked(true); // set the base renderer of the model to nullptr, such that global properties are used (synchronized mode) m_StorageModel->SetCurrentRenderer(nullptr); } void QmitkSynchronizedNodeSelectionWidget::UpdateInfo() { } void QmitkSynchronizedNodeSelectionWidget::OnDataStorageChanged() { this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::OnNodePredicateChanged() { this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } bool isSynchronized = this->IsSynchronized(); if (isSynchronized) { this->ReviseSynchronizedSelectionChanged(oldInternalSelection, newInternalSelection); } else { this->ReviseDesynchronizedSelectionChanged(oldInternalSelection, newInternalSelection); } // Since a new selection might have a different rendering tree the render windows // need to be updated. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); } void QmitkSynchronizedNodeSelectionWidget::OnInternalSelectionChanged() { m_StorageModel->SetCurrentSelection(this->GetCurrentInternalSelection()); } bool QmitkSynchronizedNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& /*emissionCandidates*/) const { return this->IsSynchronized(); } void QmitkSynchronizedNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* node) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } // For helper / hidden nodes if (m_NodePredicate.IsNotNull() && !m_NodePredicate->CheckNode(node)) { // If the node predicate does not match, do not add the node to the current selection. // Leave the visibility as it is. return; } // The selection mode determines if we want to show all nodes from the data storage // or use a local selected list of nodes. // We need to hide each new incoming data node, if we use a local selection, // since we do not want to show / select newly added nodes immediately. // We need to add the incoming node to our selection, if the selection mode check box // is checked. // We want to add the incoming node to our selection, if the node is a child node // of an already selected node. // Nodes added to the selection will be made visible. if (m_Controls.selectionModeCheckBox->isChecked() || this->IsParentNodeSelected(node)) { auto currentSelection = this->GetCurrentInternalSelection(); // Check if the nodes is already part of the internal selection. // That can happen if another render window already added the new node and sent out the new, updated // selection to be synchronized. auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), node); if (finding != std::end(currentSelection)) // node found { // node already part of the selection return; } currentSelection.append(const_cast(node)); // This function will call 'QmitkSynchronizedNodeSelectionWidget::ReviseSelectionChanged' // which will take care of the visibility-property for newly added node. this->HandleChangeOfInternalSelection(currentSelection); } else { // If the widget is in "local-selection" state (selectionModeCheckBox unchecked), // the new incoming node needs to be hid. // Here it depends on the synchronization-state which properties need // to be modified. if (this->IsSynchronized()) { // If the node will not be part of the new selection, hide the node. const_cast(node)->SetVisibility(false); } else { // If the widget is not synchronized, all nodes use renderer-specific properties. // Thus we need to modify the renderer-specific properties of the node: // - hide the node, which is not part of the selection this->DeselectNode(const_cast(node)); } } } void QmitkSynchronizedNodeSelectionWidget::OnNodeModified(const itk::Object* caller, const itk::EventObject& event) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (!itk::ModifiedEvent().CheckEvent(&event)) { return; } auto node = dynamic_cast(caller); if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { auto currentSelection = this->GetCurrentInternalSelection(); // check if the node to be modified is part of the current selection auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), node); if (finding == std::end(currentSelection)) // node not found { // node not part of the selection return; } // We know that the node is relevant, but we don't know if the node modification was relevant // for the rendering. We just request an update here. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); m_StorageModel->UpdateModelData(); } } void QmitkSynchronizedNodeSelectionWidget::ReviseSynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { // If the model is synchronized, all nodes use global / default properties. // Thus we need to modify the global properties of the selection: // - a) show those nodes, which are part of the new selection AND have not been // selected before // - b) keep the property values of those nodes, which are part of the new selection AND // have been selected before // - c) hide those nodes, which are part of the old selection AND // have not been newly selected for (auto& node : newInternalSelection) { // check if the node is part of the old selection auto finding = std::find(std::begin(oldInternalSelection), std::end(oldInternalSelection), node); if (finding == std::end(oldInternalSelection)) // node not found { // If the node is part of the new selection and was not already part of the old selection, // set the relevant renderer-specific properties. // This will set the "visible" property for the global / default property list // and show the node for this renderer. node->SetVisibility(true); // item a) } // else: item b): node that was already selected before does not need to be modified } for (auto& node : oldInternalSelection) { // check if the node is part of the new selection auto finding = std::find(std::begin(newInternalSelection), std::end(newInternalSelection), node); if (finding == std::end(newInternalSelection)) // node not found { // If the node is not part of the new selection, hide the node. node->SetVisibility(false); // item c) } // else: item b): node that was already selected before does not need to be modified } } void QmitkSynchronizedNodeSelectionWidget::ReviseDesynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } // If the model is not synchronized, all nodes need renderer-specific properties. // Thus we need to modify the renderer-specific properties of the selection: // - a) set the renderer-specific properties of those nodes, which are part of the new selection AND // have not been selected before (see 'SelectNode') // - b) show those nodes, which are part of the new selection AND have not been // selected before // - c) keep the property values of those nodes, which are part of the new selection AND // have been selected before // - d) hide those nodes, which are part of the old selection AND // have not been newly selected // - e) set the renderer-specific properties of those nodes, which are part of the old selection AND // have not been newly selected, to denote which nodes are selected for (auto& node : newInternalSelection) { // check if the node is part of the old selection auto finding = std::find(std::begin(oldInternalSelection), std::end(oldInternalSelection), node); if (finding == std::end(oldInternalSelection)) // node not found { // If the node is part of the new selection and was not already part of the old selection, // set the relevant renderer-specific properties. // This will set the "visible" and "layer" property for the renderer-specific property list // such that the global / default property list values are overwritten mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(node, baseRenderer); // item a) // Explicitly set the visibility to true for selected nodes to show them in the render window. node->SetVisibility(true, baseRenderer); // item b) } // else: item c): node that was already selected before does not need to be modified } for (auto& node : oldInternalSelection) { // check if the node is part of the new selection auto finding = std::find(std::begin(newInternalSelection), std::end(newInternalSelection), node); if (finding == std::end(newInternalSelection)) // node not found { // If the node is not part of the new selection, unset the relevant renderer-specific properties. // This will unset the "visible" and "layer" property for the renderer-specific property list and // hide the node for this renderer. // ATTENTION: This is required, since the synchronized global property needs to be overwritten // to make sure that the visibility is correctly set for the specific base renderer. this->DeselectNode(node); // item d) and e) } // else: item c): node that was already selected before does not need to be modified } } void QmitkSynchronizedNodeSelectionWidget::ReinitNode(const mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto selectedImage = dynamic_cast(dataNode->GetData()); if (nullptr == selectedImage) { return; } auto boundingBoxPredicate = mitk::NodePredicateNot::New( mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false), baseRenderer)); if (!boundingBoxPredicate->CheckNode(dataNode)) { return; } mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), selectedImage->GetTimeGeometry()); } void QmitkSynchronizedNodeSelectionWidget::RemoveFromInternalSelection(mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (this->IsSynchronized()) { // If the model is synchronized, all nodes use global / default properties. // Thus we need to modify the global property of the node. // Explicitly set the visibility to false for unselected nodes to hide them in the render window. dataNode->SetVisibility(false); } m_Controls.selectionModeCheckBox->setChecked(false); emit SelectionModeChanged(false); this->RemoveNodeFromSelection(dataNode); } bool QmitkSynchronizedNodeSelectionWidget::IsParentNodeSelected(const mitk::DataNode* dataNode) const { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return false; } auto currentSelection = this->GetCurrentInternalSelection(); auto parentNodes = dataStorage->GetSources(dataNode, m_NodePredicate, false); for (auto it = parentNodes->Begin(); it != parentNodes->End(); ++it) { const mitk::DataNode* parentNode = it->Value(); auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), parentNode); if (finding != std::end(currentSelection)) // parent node found { // at least one parent node is part of the selection return true; } } return false; } void QmitkSynchronizedNodeSelectionWidget::DeselectNode(mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (nullptr == dataNode) { return; } if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(dataNode)) { // If the node should not be part of the selection, set the relevant renderer-specific properties. // This will set the "visible" and "layer" property for the renderer-specific property list, // such that the global / default property list values are overwritten. mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, baseRenderer); // Explicitly set the visibility to false for the node to hide them in the render window. dataNode->SetVisibility(false, baseRenderer); } } void QmitkSynchronizedNodeSelectionWidget::SelectAll() { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } auto allNodes = m_NodePredicate ? dataStorage->GetSubset(m_NodePredicate) : dataStorage->GetAll(); NodeList currentSelection; for (auto& node : *allNodes) { currentSelection.append(node); } this->HandleChangeOfInternalSelection(currentSelection); } diff --git a/Modules/QtWidgetsExt/files.cmake b/Modules/QtWidgetsExt/files.cmake index 8a7b348f0a..a13bc3ce58 100644 --- a/Modules/QtWidgetsExt/files.cmake +++ b/Modules/QtWidgetsExt/files.cmake @@ -1,94 +1,91 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES qclickablelabel.cpp QmitkAboutDialog.cpp QmitkBasePropertyView.cpp QmitkBoolPropertyWidget.cpp QmitkBoundingObjectWidget.cpp QmitkCallbackFromGUIThread.cpp QmitkColorPropertyEditor.cpp QmitkColorPropertyView.cpp QmitkColorTransferFunctionCanvas.cpp QmitkCrossWidget.cpp QmitkEditPointDialog.cpp QmitkEnumerationPropertyWidget.cpp QmitkFileChooser.cpp - QmitkGnuplotWidget.cpp QmitkHistogram.cpp QmitkHotkeyLineEdit.cpp QmitkModulesDialog.cpp QmitkModuleTableModel.cpp QmitkNumberPropertyEditor.cpp QmitkNumberPropertySlider.cpp QmitkNumberPropertyView.cpp QmitkPiecewiseFunctionCanvas.cpp QmitkPlotDialog.cpp QmitkPlotWidget.cpp QmitkPointListModel.cpp QmitkPointListView.cpp QmitkPointListWidget.cpp QmitkPrimitiveMovieNavigatorWidget.cpp QmitkPropertyViewFactory.cpp QmitkSliceWidget.cpp QmitkStandardViews.cpp QmitkStringPropertyEditor.cpp QmitkStringPropertyOnDemandEdit.cpp QmitkStringPropertyView.cpp QmitkTransferFunctionCanvas.cpp QmitkTransferFunctionGeneratorWidget.cpp QmitkTransferFunctionWidget.cpp QmitkUGCombinedRepresentationPropertyWidget.cpp QmitkVideoBackground.cpp QtWidgetsExtRegisterClasses.cpp ) set(MOC_H_FILES include/qclickablelabel.h include/QmitkAboutDialog.h include/QmitkBasePropertyView.h include/QmitkBoolPropertyWidget.h include/QmitkBoundingObjectWidget.h include/QmitkCallbackFromGUIThread.h include/QmitkColorPropertyEditor.h include/QmitkColorPropertyView.h include/QmitkColorTransferFunctionCanvas.h include/QmitkCrossWidget.h include/QmitkEditPointDialog.h include/QmitkEnumerationPropertyWidget.h include/QmitkFileChooser.h - include/QmitkGnuplotWidget.h include/QmitkHotkeyLineEdit.h include/QmitkNumberPropertyEditor.h include/QmitkNumberPropertySlider.h include/QmitkNumberPropertyView.h include/QmitkPiecewiseFunctionCanvas.h include/QmitkPlotWidget.h include/QmitkPointListModel.h include/QmitkPointListView.h include/QmitkPointListWidget.h include/QmitkPrimitiveMovieNavigatorWidget.h include/QmitkSliceWidget.h include/QmitkStandardViews.h include/QmitkStringPropertyEditor.h include/QmitkStringPropertyOnDemandEdit.h include/QmitkStringPropertyView.h include/QmitkTransferFunctionCanvas.h include/QmitkTransferFunctionGeneratorWidget.h include/QmitkTransferFunctionWidget.h include/QmitkUGCombinedRepresentationPropertyWidget.h include/QmitkVideoBackground.h ) set(UI_FILES src/QmitkAboutDialogGUI.ui - src/QmitkGnuplotWidget.ui src/QmitkPrimitiveMovieNavigatorWidget.ui src/QmitkSliceWidget.ui src/QmitkTransferFunctionGeneratorWidget.ui src/QmitkTransferFunctionWidget.ui ) set(QRC_FILES resource/QtWidgetsExt.qrc ) diff --git a/Modules/QtWidgetsExt/include/QmitkGnuplotWidget.h b/Modules/QtWidgetsExt/include/QmitkGnuplotWidget.h deleted file mode 100644 index 25d895be44..0000000000 --- a/Modules/QtWidgetsExt/include/QmitkGnuplotWidget.h +++ /dev/null @@ -1,74 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkGnuplotWidget_h -#define QmitkGnuplotWidget_h - -#include -#include -#include -#include -#include - -class QAction; -class QMenu; - -namespace Ui -{ - class QmitkGnuplotWidget; -} - -class MITKQTWIDGETSEXT_EXPORT QmitkGnuplotWidget : public QWidget -{ - Q_OBJECT - -public: - explicit QmitkGnuplotWidget(QWidget *parent = nullptr); - ~QmitkGnuplotWidget() override; - - QString GetGnuplotPath() const; - void SetGnuplotPath(const QString &path); - - QStringList GetCommands() const; - void SetCommands(const QStringList &commands); - - void Update(); - - QSize sizeHint() const override; - -protected: - void contextMenuEvent(QContextMenuEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - -private slots: - void OnProcessStateChanged(QProcess::ProcessState state); - void OnProcessError(QProcess::ProcessError error); - void OnProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); - void OnCopyPlot(); - void OnCopyScript(); - -private: - void CreateContextMenu(); - QString CreateSetTermCommand() const; - - QScopedPointer m_Ui; - QMenu *m_ContextMenu; - QAction *m_CopyPlotAction; - QAction *m_CopyScriptAction; - QProcess *m_Process; - QString m_GnuplotPath; - QStringList m_Commands; - itk::TimeStamp m_ModifiedTime; - itk::TimeStamp m_UpdateTime; -}; - -#endif diff --git a/Modules/QtWidgetsExt/src/QmitkGnuplotWidget.cpp b/Modules/QtWidgetsExt/src/QmitkGnuplotWidget.cpp deleted file mode 100644 index d7dce8f540..0000000000 --- a/Modules/QtWidgetsExt/src/QmitkGnuplotWidget.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkGnuplotWidget.h" -#include -#include -#include -#include - -QmitkGnuplotWidget::QmitkGnuplotWidget(QWidget *parent) - : QWidget(parent), - m_Ui(new Ui::QmitkGnuplotWidget), - m_ContextMenu(nullptr), - m_CopyPlotAction(nullptr), - m_CopyScriptAction(nullptr), - m_Process(new QProcess(this)) -{ - m_Ui->setupUi(this); - - connect( - m_Process, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(OnProcessStateChanged(QProcess::ProcessState))); - connect(m_Process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(OnProcessError(QProcess::ProcessError))); - connect( - m_Process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(OnProcessFinished(int, QProcess::ExitStatus))); - - this->CreateContextMenu(); -} - -QmitkGnuplotWidget::~QmitkGnuplotWidget() -{ -} - -void QmitkGnuplotWidget::CreateContextMenu() -{ - m_CopyPlotAction = new QAction("Copy &Plot", this); - connect(m_CopyPlotAction, SIGNAL(triggered()), this, SLOT(OnCopyPlot())); - - m_CopyScriptAction = new QAction("Copy &Script", this); - connect(m_CopyScriptAction, SIGNAL(triggered()), this, SLOT(OnCopyScript())); - - m_ContextMenu = new QMenu(this); - - m_ContextMenu->addActions(QList() << m_CopyPlotAction << m_CopyScriptAction); -} - -void QmitkGnuplotWidget::contextMenuEvent(QContextMenuEvent *event) -{ - auto plot = m_Ui->label->pixmap(); - - m_CopyPlotAction->setEnabled(!plot.isNull()); - m_CopyScriptAction->setEnabled(!m_Commands.empty()); - - m_ContextMenu->popup(event->globalPos()); - - event->accept(); -} - -void QmitkGnuplotWidget::OnCopyPlot() -{ - auto plot = m_Ui->label->pixmap(); - - if (!plot.isNull()) - QApplication::clipboard()->setPixmap(plot); -} - -void QmitkGnuplotWidget::OnCopyScript() -{ - if (m_Commands.empty()) - return; - - QString script = this->CreateSetTermCommand(); - - Q_FOREACH (const QString &command, m_Commands) - { - script += command + "\n"; - } - - QApplication::clipboard()->setText(script); -} - -void QmitkGnuplotWidget::resizeEvent(QResizeEvent *) -{ - m_ModifiedTime.Modified(); - - if (m_Process->isOpen() || m_Commands.isEmpty() || m_GnuplotPath.isEmpty()) - return; - - this->Update(); -} - -QString QmitkGnuplotWidget::GetGnuplotPath() const -{ - return m_GnuplotPath; -} - -void QmitkGnuplotWidget::SetGnuplotPath(const QString &path) -{ - m_GnuplotPath = path; - m_ModifiedTime.Modified(); -} - -QStringList QmitkGnuplotWidget::GetCommands() const -{ - return m_Commands; -} - -void QmitkGnuplotWidget::SetCommands(const QStringList &commands) -{ - m_Commands = commands; - m_ModifiedTime.Modified(); -} - -void QmitkGnuplotWidget::Update() -{ - if (m_UpdateTime < m_ModifiedTime) - m_Process->start(m_GnuplotPath, QStringList() << "-"); -} - -QSize QmitkGnuplotWidget::sizeHint() const -{ - return QSize(400, 300); -} - -QString QmitkGnuplotWidget::CreateSetTermCommand() const -{ - return QString("set term pngcairo size %1,%2 enhanced font '%3,%4'\n") - .arg(this->width()) - .arg(this->height()) - .arg(this->font().family()) - .arg(this->font().pointSize()); -} - -void QmitkGnuplotWidget::OnProcessStateChanged(QProcess::ProcessState state) -{ - if (state == QProcess::Running) - { - m_UpdateTime = m_ModifiedTime; - - m_Process->write(this->CreateSetTermCommand().toLatin1()); - - Q_FOREACH (const QString &command, m_Commands) - { - m_Process->write(QString("%1\n").arg(command).toLatin1()); - } - - m_Process->write("exit\n"); - m_Process->closeWriteChannel(); - } -} - -void QmitkGnuplotWidget::OnProcessError(QProcess::ProcessError error) -{ - switch (error) - { - case QProcess::FailedToStart: - m_Ui->label->setText("Gnuplot failed to start!"); - break; - - case QProcess::Crashed: - m_Ui->label->setText("Gnuplot crashed!"); - break; - - case QProcess::Timedout: - m_Ui->label->setText("Gnuplot timed out!"); - break; - - case QProcess::WriteError: - m_Ui->label->setText("Could not write to gnuplot!"); - break; - - case QProcess::ReadError: - m_Ui->label->setText("Could not read from gnuplot!"); - break; - - default: - m_Ui->label->setText("An unknown error occurred!"); - break; - } -} - -void QmitkGnuplotWidget::OnProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - bool needUpdate = false; - - if (exitStatus != QProcess::CrashExit) - { - if (exitCode == 0) - { - if (m_UpdateTime < m_ModifiedTime) - { - needUpdate = true; - } - else - { - m_Ui->label->setPixmap(QPixmap::fromImage(QImage::fromData(m_Process->readAllStandardOutput(), "PNG"))); - } - } - else - { - m_Ui->label->setText(QString("Gnuplot exit code: %1!").arg(exitCode)); - } - } - - m_Process->close(); - - if (needUpdate) - this->Update(); -} diff --git a/Modules/QtWidgetsExt/src/QmitkGnuplotWidget.ui b/Modules/QtWidgetsExt/src/QmitkGnuplotWidget.ui deleted file mode 100644 index c36919fa6c..0000000000 --- a/Modules/QtWidgetsExt/src/QmitkGnuplotWidget.ui +++ /dev/null @@ -1,56 +0,0 @@ - - - QmitkGnuplotWidget - - - - 0 - 0 - 400 - 300 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 1 - 1 - - - - background-color: rgb(255, 255, 255); -color: rgb(255, 0, 0); - - - true - - - Qt::AlignCenter - - - true - - - Qt::NoTextInteraction - - - - - - - - diff --git a/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp b/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp deleted file mode 100644 index 29a588aa6c..0000000000 --- a/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp +++ /dev/null @@ -1,196 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkFeatureBasedEdgeDetectionFilter.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -mitk::FeatureBasedEdgeDetectionFilter::FeatureBasedEdgeDetectionFilter() -{ - this->SetNumberOfRequiredInputs(1); - - this->SetNumberOfIndexedOutputs(1); -} - -mitk::FeatureBasedEdgeDetectionFilter::~FeatureBasedEdgeDetectionFilter() -{ -} - -void mitk::FeatureBasedEdgeDetectionFilter::GenerateData() -{ - mitk::Image::ConstPointer image = ImageToUnstructuredGridFilter::GetInput(); - - if (m_SegmentationMask.IsNull()) - { - MITK_WARN << "Please set a segmentation mask first" << std::endl; - return; - } - // First create a threshold segmentation of the image. The threshold is determined - // by the mean +/- stddev of the pixel values that are covered by the segmentation mask - - // Compute mean and stdDev based on the current segmentation - mitk::ImageStatisticsCalculator::Pointer statCalc = mitk::ImageStatisticsCalculator::New(); - statCalc->SetInputImage(image); - - mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); - imgMask->SetInputImage(image); - imgMask->SetImageMask(m_SegmentationMask); - statCalc->SetMask(imgMask); - - auto stats = statCalc->GetStatistics()->GetStatistics(ImageStatisticsContainer::NO_MASK_LABEL_VALUE,0); - double mean = stats.GetValueConverted(mitk::ImageStatisticsConstants::MEAN()); - double stdDev = stats.GetValueConverted(mitk::ImageStatisticsConstants::STANDARDDEVIATION()); - - double upperThreshold = mean + stdDev; - double lowerThreshold = mean - stdDev; - - // Perform thresholding - mitk::Image::Pointer thresholdImage = mitk::Image::New(); - AccessByItk_3(image.GetPointer(), ITKThresholding, lowerThreshold, upperThreshold, thresholdImage) - - mitk::ProgressBar::GetInstance() - ->Progress(2); - - // Postprocess threshold segmentation - // First a closing will be executed - mitk::Image::Pointer closedImage = mitk::Image::New(); - AccessByItk_1(thresholdImage, ThreadedClosing, closedImage); - - // Then we will holes that might exist - mitk::MorphologicalOperations::FillHoles(closedImage); - - mitk::ProgressBar::GetInstance()->Progress(); - - // Extract the binary edges of the resulting segmentation - mitk::Image::Pointer edgeImage = mitk::Image::New(); - AccessByItk_1(closedImage, ContourSearch, edgeImage); - - // Convert the edge image into an unstructured grid - mitk::ImageToUnstructuredGridFilter::Pointer i2UFilter = mitk::ImageToUnstructuredGridFilter::New(); - i2UFilter->SetInput(edgeImage); - i2UFilter->SetThreshold(1.0); - i2UFilter->Update(); - - m_PointGrid = this->GetOutput(); - if (m_PointGrid.IsNull()) - m_PointGrid = mitk::UnstructuredGrid::New(); - - m_PointGrid->SetVtkUnstructuredGrid(i2UFilter->GetOutput()->GetVtkUnstructuredGrid()); - - mitk::ProgressBar::GetInstance()->Progress(); -} - -template -void mitk::FeatureBasedEdgeDetectionFilter::ThreadedClosing(itk::Image *originalImage, - mitk::Image::Pointer &result) -{ - typedef itk::BinaryBallStructuringElement myKernelType; - - myKernelType ball; - ball.SetRadius(1); - ball.CreateStructuringElement(); - - typedef typename itk::Image ImageType; - - typename itk::DilateObjectMorphologyImageFilter::Pointer dilationFilter = - itk::DilateObjectMorphologyImageFilter::New(); - dilationFilter->SetInput(originalImage); - dilationFilter->SetKernel(ball); - dilationFilter->Update(); - - typename itk::Image::Pointer dilatedImage = dilationFilter->GetOutput(); - - typename itk::ErodeObjectMorphologyImageFilter::Pointer erodeFilter = - itk::ErodeObjectMorphologyImageFilter::New(); - erodeFilter->SetInput(dilatedImage); - erodeFilter->SetKernel(ball); - erodeFilter->Update(); - - mitk::GrabItkImageMemory(erodeFilter->GetOutput(), result); -} - -template -void mitk::FeatureBasedEdgeDetectionFilter::ContourSearch(itk::Image *originalImage, - mitk::Image::Pointer &result) -{ - typedef itk::Image ImageType; - typedef itk::BinaryContourImageFilter binaryContourImageFilterType; - - typename binaryContourImageFilterType::Pointer binaryContourFilter = binaryContourImageFilterType::New(); - binaryContourFilter->SetInput(originalImage); - binaryContourFilter->SetForegroundValue(1); - binaryContourFilter->SetBackgroundValue(0); - binaryContourFilter->Update(); - - typename itk::Image::Pointer itkImage = itk::Image::New(); - itkImage->Graft(binaryContourFilter->GetOutput()); - - mitk::GrabItkImageMemory(itkImage, result); -} - -template -void mitk::FeatureBasedEdgeDetectionFilter::ITKThresholding(const itk::Image *originalImage, - double lower, - double upper, - mitk::Image::Pointer &result) -{ - typedef itk::Image ImageType; - typedef itk::Image SegmentationType; - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - - if (typeid(TPixel) != typeid(float) && typeid(TPixel) != typeid(double)) - { - // round the thresholds if we have nor a float or double image - lower = std::floor(lower + 0.5); - upper = std::floor(upper - 0.5); - } - if (lower >= upper) - { - upper = lower; - } - - typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); - filter->SetInput(originalImage); - filter->SetLowerThreshold(lower); - filter->SetUpperThreshold(upper); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - - mitk::GrabItkImageMemory(filter->GetOutput(), result); -} - -void mitk::FeatureBasedEdgeDetectionFilter::SetSegmentationMask(mitk::Image::Pointer segmentation) -{ - this->m_SegmentationMask = segmentation; -} - -void mitk::FeatureBasedEdgeDetectionFilter::GenerateOutputInformation() -{ - Superclass::GenerateOutputInformation(); -} diff --git a/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.h b/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.h deleted file mode 100644 index 499c0c8a9b..0000000000 --- a/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.h +++ /dev/null @@ -1,73 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkFeatureBasedEdgeDetectionFilter_h -#define mitkFeatureBasedEdgeDetectionFilter_h - -#include -#include - -namespace mitk -{ - /** - * @brief Calculates edges and extracts them as an UnstructuredGrid with respect - * to the given segmentation. - * - * At first the statistic of the grey values within the segmentation is - * calculated. Based on this statistic a thresholding is executed. The - * thresholded image will be processed by morphological filters. The resulting - * image will be used for masking the input image. The masked image is used as - * input for the ImageToPointCloudFilter, which output is an UnstructuredGrid. - */ - class MITKSEGMENTATION_EXPORT FeatureBasedEdgeDetectionFilter : public ImageToUnstructuredGridFilter - { - public: - mitkClassMacro(FeatureBasedEdgeDetectionFilter, ImageToUnstructuredGridFilter); - itkFactorylessNewMacro(Self); - - /** Sets the segmentation for calculating the statistics within that */ - void SetSegmentationMask(mitk::Image::Pointer); - - protected: - /** This method is called by Update(). */ - void GenerateData() override; - - /** Initializes the output information */ - void GenerateOutputInformation() override; - - /** Constructor */ - FeatureBasedEdgeDetectionFilter(); - - /** Destructor */ - ~FeatureBasedEdgeDetectionFilter() override; - - /** Execute a thresholding filter with the given lower and upper bound */ - template - void ITKThresholding(const itk::Image *originalImage, - double lower, - double upper, - mitk::Image::Pointer &result); - - template - void ContourSearch(itk::Image *originalImage, mitk::Image::Pointer &result); - - template - void ThreadedClosing(itk::Image *originalImage, mitk::Image::Pointer &result); - - private: - mitk::UnstructuredGrid::Pointer m_PointGrid; - - /** The used mask given by the segmentation*/ - mitk::Image::Pointer m_SegmentationMask; - }; -} -#endif diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index d07c107ce8..b5a37ba586 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,110 +1,108 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkOtsuSegmentationFilter.h" #include #include // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } mitk::OtsuTool3D::OtsuTool3D() : SegWithPreviewTool() { this->ResetsToEmptyPreviewOn(); this->UseSpecialPreviewColorOff(); } void mitk::OtsuTool3D::Activated() { Superclass::Activated(); m_NumberOfBins = 128; m_NumberOfRegions = 2; m_UseValley = false; this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu.svg"); return resource; } const char* mitk::OtsuTool3D::GetName() const { return "Otsu"; } void mitk::OtsuTool3D::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType timeStep) { int numberOfThresholds = m_NumberOfRegions - 1; mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); otsuFilter->SetNumberOfThresholds(numberOfThresholds); otsuFilter->SetValleyEmphasis(m_UseValley); otsuFilter->SetNumberOfBins(m_NumberOfBins); otsuFilter->SetInput(inputAtTimeStep); otsuFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); try { otsuFilter->Update(); } catch (...) { mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; } - auto otsuResultImage = otsuFilter->GetOutput(); - - mitk::ImageReadAccessor newMitkImgAcc(otsuResultImage); + mitk::ImageReadAccessor newMitkImgAcc(otsuFilter->GetOutput()); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::OtsuTool3D::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); preview->RemoveLabels(preview->GetAllLabelValues()); for (unsigned int i = 0; i < m_NumberOfRegions; ++i) { - auto label = LabelSetImageHelper::CreateNewLabel(preview, "Otsu " + std::to_string(i)); + auto label = LabelSetImageHelper::CreateNewLabel(preview, "Otsu " + std::to_string(i), true); label->SetValue(i + 1); preview->AddLabel(label, preview->GetActiveLayer(), false, false); } } unsigned int mitk::OtsuTool3D::GetMaxNumberOfBins() const { const auto min = this->GetReferenceData()->GetStatistics()->GetScalarValueMin(); const auto max = this->GetReferenceData()->GetStatistics()->GetScalarValueMaxNoRecompute(); return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index 1ae00d3b1d..e20ac8569b 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,849 +1,850 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews): Tool("dummy"), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::~SegWithPreviewTool() { } void mitk::SegWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) { m_MergeStyle = mergeStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) { m_OverwriteStyle = overwriteStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferScope(LabelTransferScope labelTransferScope) { m_LabelTransferScope = labelTransferScope; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferMode(LabelTransferMode labelTransferMode) { m_LabelTransferMode = labelTransferMode; this->Modified(); } void mitk::SegWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer) { m_SelectedLabels = labelsToTransfer; this->Modified(); } bool mitk::SegWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return false; auto* referenceImage = dynamic_cast(referenceData); if (referenceImage == nullptr) return false; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* workingImage = dynamic_cast(workingData); if (workingImage == nullptr) return false; // If the working image is a normal image and not a label set image // it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == workingImage->GetPixelType(); } void mitk::SegWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { this->GetToolManager()->ActivateTool(-1); } + m_IsPreviewGenerated = false; } void mitk::SegWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::SegWithPreviewTool::ConfirmSegmentation() { bool labelChanged = this->EnsureUpToDateUserDefinedActiveLabel(); if ((m_LazyDynamicPreviews && m_CreateAllTimeSteps) || labelChanged) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { this->GetToolManager()->ActivateTool(-1); } this->ConfirmCleanUp(); } void mitk::SegWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } const mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() const { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::SegWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::SegWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::SegWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void mitk::SegWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) { auto previewImage = GetImageByTimeStep(this->GetPreviewSegmentation(), timeStep); if (nullptr != previewImage) { AccessByItk(previewImage, ClearBufferProcessing); } } void mitk::SegWithPreviewTool::ResetPreviewContent() { auto previewImage = this->GetPreviewSegmentation(); if (nullptr != previewImage) { auto castedPreviewImage = dynamic_cast(previewImage); if (nullptr == castedPreviewImage) mitkThrow() << "Application is on wrong state / invalid tool implementation. Preview image should always be of type LabelSetImage now."; castedPreviewImage->ClearBuffer(); } } void mitk::SegWithPreviewTool::ResetPreviewNode() { if (m_IsUpdating) { mitkThrow() << "Used tool is implemented incorrectly. ResetPreviewNode is called while preview update is ongoing. Check implementation!"; } itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { LabelSetImage::ConstPointer workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (this->GetResetsToEmptyPreview()) { newPreviewImage->ClearBuffer(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); if (newPreviewImage->GetNumberOfLayers() == 0) { newPreviewImage->AddLayer(); newPreviewImage->SetActiveLayer(0); } auto* activeLabel = newPreviewImage->GetActiveLabel(); if (nullptr == activeLabel) { activeLabel = newPreviewImage->AddLabel("toolresult", previewColor, newPreviewImage->GetActiveLayer()); newPreviewImage->UpdateLookupTable(activeLabel->GetValue()); } else if (m_UseSpecialPreviewColor) { // Let's paint the feedback node green... activeLabel->SetColor(previewColor); newPreviewImage->UpdateLookupTable(activeLabel->GetValue()); } activeLabel->SetVisible(true); } else { Image::ConstPointer workingImageBin = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { Image::Pointer newPreviewImage; if (this->GetResetsToEmptyPreview()) { newPreviewImage = Image::New(); newPreviewImage->Initialize(workingImageBin); } else { auto newPreviewImage = workingImageBin->Clone(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } mitk::SegWithPreviewTool::LabelMappingType mitk::SegWithPreviewTool::GetLabelMapping() const { LabelSetImage::LabelValueType offset = 0; if (LabelTransferMode::AddLabel == m_LabelTransferMode && LabelTransferScope::ActiveLabel!=m_LabelTransferScope) { //If we are not just working on active label and transfer mode is add, we need to compute an offset for adding the //preview labels instat of just mapping them to existing segmentation labels. const auto segmentation = this->GetTargetSegmentation(); if (nullptr == segmentation) mitkThrow() << "Invalid state of SegWithPreviewTool. Cannot GetLabelMapping if no target segmentation is set."; auto labels = segmentation->GetLabels(); auto maxLabelIter = std::max_element(std::begin(labels), std::end(labels), [](const Label::Pointer& a, const Label::Pointer& b) { return a->GetValue() < b->GetValue(); }); if (maxLabelIter != labels.end()) { offset = maxLabelIter->GetPointer()->GetValue(); } } LabelMappingType labelMapping = {}; switch (this->m_LabelTransferScope) { case LabelTransferScope::SelectedLabels: { for (auto label : this->m_SelectedLabels) { labelMapping.push_back({label, label + offset}); } } break; case LabelTransferScope::AllLabels: { const auto labelValues = this->GetPreviewSegmentation()->GetLabelValuesByGroup(this->GetPreviewSegmentation()->GetActiveLayer()); for (auto labelValue : labelValues) { labelMapping.push_back({ labelValue, labelValue + offset}); } } break; default: { if (m_SelectedLabels.empty()) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but no label is indicated as selected label. Check " "implementation of derived tool class."; if (m_SelectedLabels.size() > 1) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but more then one selected label is indicated." "Should be only one. Check implementation of derived tool class."; labelMapping.push_back({m_SelectedLabels.front(), this->GetUserDefinedActiveLabel()}); } break; } return labelMapping; } void mitk::SegWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); auto resultSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), destinationImage, timeStep)->Clone(); auto destLSImage = dynamic_cast(destinationImage); //We need to transfer explictly to a copy of the current working image to ensure that labelMapping is done and things //like merge style, overwrite style and locks are regarded. TransferLabelContentAtTimeStep(sourceSlice, resultSlice, destLSImage->GetConstLabelsByValue(destLSImage->GetLabelValuesByGroup(destLSImage->GetActiveLayer())), timeStep, 0, 0, destLSImage->GetUnlabeledLabelLock(), labelMapping, m_MergeStyle, m_OverwriteStyle); //We use WriteBackSegmentationResult to ensure undo/redo is supported also by derived tools of this class. SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, resultSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast(sourceImage); auto destLSImage = dynamic_cast(destinationImage); TransferLabelContentAtTimeStep(sourceLSImage, destLSImage, timeStep, labelMapping, m_MergeStyle, m_OverwriteStyle); } } catch (mitk::Exception& e) { Tool::ErrorMessage(e.GetDescription()); mitkReThrow(e); } } void mitk::SegWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working referenceImage (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot confirm/transfer segmentation. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } auto labelMapping = this->GetLabelMapping(); this->PreparePreviewToResultTransfer(labelMapping); if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } // since we are maybe working on a smaller referenceImage, pad it to the size of the original referenceImage if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { PadImageFilter::Pointer padFilter = PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::SegWithPreviewTool::OnRoiDataChanged() { DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { MaskAndCutRoiImageFilter::Pointer roiFilter = MaskAndCutRoiImageFilter::New(); Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); DataNode::Pointer tmpNode = DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::SegWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic referenceImage this->UpdatePreview(); } } } bool mitk::SegWithPreviewTool::EnsureUpToDateUserDefinedActiveLabel() { bool labelChanged = true; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (const auto& labelSetImage = dynamic_cast(workingImage)) { // this is a fix for T28131 / T28986, which should be refactored if T28524 is being worked on auto newLabel = labelSetImage->GetActiveLabel()->GetValue(); labelChanged = newLabel != m_UserDefinedActiveLabel; m_UserDefinedActiveLabel = newLabel; } else { m_UserDefinedActiveLabel = 1; labelChanged = false; } return labelChanged; } void mitk::SegWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); this->EnsureUpToDateUserDefinedActiveLabel(); this->CurrentlyBusy.Send(true); m_IsUpdating = true; m_IsPreviewGenerated = false; this->UpdatePrepare(); const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback referenceImage. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback referenceImage feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); - if (previewImage->GetNumberOfLabels(previewImage->GetActiveLayer()) > 0) + if (!previewImage->GetAllLabelValues().empty()) { // check if labels exits for the preview m_IsPreviewGenerated = true; } } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::SegWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::SegWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::ConfirmCleanUp() { // default implementation does nothing // reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target) { for (const auto& [sourceLabel, targetLabel] : labelMapping) { if (LabelSetImage::UNLABELED_VALUE != sourceLabel && LabelSetImage::UNLABELED_VALUE != targetLabel && !target->ExistLabel(targetLabel, target->GetActiveLayer())) { if (!source->ExistLabel(sourceLabel, source->GetActiveLayer())) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Preview seems invalid as label is missing. Missing label: " << sourceLabel; } auto clonedLabel = source->GetLabel(sourceLabel)->Clone(); clonedLabel->SetValue(targetLabel); target->AddLabel(clonedLabel,target->GetActiveLayer(), false, false); } } } void mitk::SegWithPreviewTool::PreparePreviewToResultTransfer(const LabelMappingType& labelMapping) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); if (nullptr == resultSegmentation) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Tool is in invalid state as segmentation is not existing or of right type"; } auto preview = this->GetPreviewSegmentation(); TransferLabelInformation(labelMapping, preview, resultSegmentation); } } mitk::TimePointType mitk::SegWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } mitk::LabelSetImage::LabelValueType mitk::SegWithPreviewTool::GetActiveLabelValueOfPreview() const { const auto previewImage = this->GetPreviewSegmentation(); const auto activeLabel = previewImage->GetActiveLabel(); if (nullptr == activeLabel) mitkThrow() << this->GetNameOfClass() <<" is in an invalid state, as " "preview has no active label indicated. Check " "implementation of the class."; return activeLabel->GetValue(); } const char* mitk::SegWithPreviewTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimeStep(const mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::Pointer mitk::SegWithPreviewTool::GetImageByTimeStep(mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { return SelectImageByTimePoint(image, timePoint); } void mitk::SegWithPreviewTool::EnsureTargetSegmentationNodeInDataStorage() const { auto targetNode = this->GetTargetSegmentationNode(); auto dataStorage = this->GetToolManager()->GetDataStorage(); if (!dataStorage->Exists(targetNode)) { dataStorage->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); } } std::string mitk::SegWithPreviewTool::GetCurrentSegmentationName() { auto workingData = this->GetToolManager()->GetWorkingData(0); return nullptr != workingData ? workingData->GetName() : ""; } mitk::DataNode* mitk::SegWithPreviewTool::GetTargetSegmentationNode() const { return this->GetToolManager()->GetWorkingData(0); } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetTargetSegmentation() const { auto node = this->GetTargetSegmentationNode(); if (nullptr == node) return nullptr; return dynamic_cast(node->GetData()); } void mitk::SegWithPreviewTool::TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep) { mitk::ImageReadAccessor newMitkImgAcc(source); LabelMappingType labelMapping; const auto labelValues = source->GetLabelValuesByGroup(source->GetActiveLayer()); for (const auto& labelValue : labelValues) { labelMapping.push_back({ labelValue,labelValue }); } TransferLabelInformation(labelMapping, source, target); target->SetVolume(newMitkImgAcc.GetData(), timeStep); } bool mitk::SegWithPreviewTool::ConfirmBeforeDeactivation() { return m_IsPreviewGenerated && m_RequestDeactivationConfirmation; } diff --git a/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp b/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp index 2808f95126..2b6dc58665 100644 --- a/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp +++ b/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp @@ -1,185 +1,182 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkBooleanOperation.h" -#include -#include -#include +#include #include #include #include +#include -typedef itk::Image ImageType; +using ITKGroupImageType = itk::Image; +using BoolOpsFunctionType = mitk::Label::PixelType(const std::vector& inputArray); -static mitk::Image::Pointer Get3DSegmentation(mitk::Image::Pointer segmentation, mitk::TimePointType time) +/** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. +* For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. +*/ +template +class BoolOpsFunctor { - if (segmentation->GetDimension() != 4) - return segmentation; - auto imageTimeSelector = mitk::ImageTimeSelector::New(); - imageTimeSelector->SetInput(segmentation); - imageTimeSelector->SetTimeNr(segmentation->GetTimeGeometry()->TimePointToTimeStep(time)); +public: + BoolOpsFunctor() {}; - imageTimeSelector->UpdateLargestPossibleRegion(); - - return imageTimeSelector->GetOutput(); -} - -static ImageType::Pointer CastTo3DItkImage(mitk::Image::Pointer segmentation, mitk::TimePointType time) -{ - ImageType::Pointer result; - mitk::CastToItkImage(Get3DSegmentation(segmentation, time), result); - return result; -} - -mitk::BooleanOperation::BooleanOperation(Type type, - mitk::Image::Pointer segmentationA, - mitk::Image::Pointer segmentationB, - TimePointType time) - : m_Type(type), m_SegmentationA(segmentationA), m_SegmentationB(segmentationB), m_TimePoint(time) -{ - this->ValidateSegmentations(); -} + BoolOpsFunctor(std::function opsFunction) : m_Function(opsFunction) + { + }; -mitk::BooleanOperation::~BooleanOperation() -{ -} + ~BoolOpsFunctor() {}; -mitk::LabelSetImage::Pointer mitk::BooleanOperation::GetResult() const -{ - switch (m_Type) + bool operator!=(const BoolOpsFunctor& other)const { - case Difference: - return this->GetDifference(); + return !(*this == other); + } - case Intersection: - return this->GetIntersection(); + bool operator==(const BoolOpsFunctor& other) const + { + if ((this->m_Function.target_type() == other.m_Function.target_type()) + && (this->m_Function.template target() && other.m_Function.template target()) + // If both std::function objects hold function pointers of the same signature, + // we can compare the pointers to check if they point to the same function. + && (*this->m_Function.template target() == *other.m_Function.template target())) + { + return true; + } + return false; + } - case Union: - return this->GetUnion(); + BoolOpsFunctor& operator=(const BoolOpsFunctor& other) + { + this->m_Function = other.m_Function; - default: - mitkThrow() << "Unknown boolean operation type '" << m_Type << "'!"; + return *this; } -} -mitk::LabelSetImage::Pointer mitk::BooleanOperation::GetDifference() const -{ - auto input1 = CastTo3DItkImage(m_SegmentationA, m_TimePoint); - auto input2 = CastTo3DItkImage(m_SegmentationB, m_TimePoint); - - auto notFilter = itk::NotImageFilter::New(); - notFilter->SetInput(input2); + inline TPixelType operator()(const std::vector& inputArray) + { + return m_Function(inputArray); + } - auto andFilter = itk::AndImageFilter::New(); - andFilter->SetInput1(input1); - andFilter->SetInput2(notFilter->GetOutput()); +private: + std::function m_Function; +}; - andFilter->UpdateLargestPossibleRegion(); - auto tempResult = Image::New(); - CastToMitkImage(andFilter->GetOutput(), tempResult); +mitk::Image::Pointer GenerateInternal(const mitk::LabelSetImage* segmentation, mitk::LabelSetImage::LabelValueVectorType labelValues, std::function opsFunction, + std::function progressCallback = [](float) {}) +{ + if (nullptr == segmentation) mitkThrow() << "Cannot perform boolean operation. Passed segmentation is not valid"; - tempResult->DisconnectPipeline(); + mitk::Image::Pointer result = mitk::Image::New(); + result->Initialize(mitk::MakeScalarPixelType(), *(segmentation->GetTimeGeometry())); - auto result = mitk::LabelSetImage::New(); - result->InitializeByLabeledImage(tempResult); + const auto timeStepCount = segmentation->GetTimeGeometry()->CountTimeSteps(); - return result; -} + for (mitk::TimeStepType i = 0; i < timeStepCount; ++i) + { + using OpsFilterType = itk::NaryFunctorImageFilter >; + auto opsFilter = OpsFilterType::New(); -mitk::LabelSetImage::Pointer mitk::BooleanOperation::GetIntersection() const -{ - auto input1 = CastTo3DItkImage(m_SegmentationA, m_TimePoint); - auto input2 = CastTo3DItkImage(m_SegmentationB, m_TimePoint); + mitk::ITKEventObserverGuard eventGuard(opsFilter, itk::ProgressEvent(), [&opsFilter, progressCallback, i, timeStepCount](const itk::EventObject& /*event*/) + { progressCallback(opsFilter->GetProgress() + static_cast(i) / static_cast(timeStepCount)); }); - auto andFilter = itk::AndImageFilter::New(); - andFilter->SetInput1(input1); - andFilter->SetInput2(input2); + BoolOpsFunctor functor(opsFunction); + opsFilter->SetFunctor(functor); + std::vector < ITKGroupImageType::ConstPointer > inputImages; - andFilter->UpdateLargestPossibleRegion(); + unsigned int inputIndex = 0; + for (auto value : labelValues) + { + auto groupImage = segmentation->GetGroupImage(segmentation->GetGroupIndexOfLabel(value)); + auto groupImageAtTS = mitk::SelectImageByTimeStep(groupImage, i); - auto tempResult = Image::New(); - CastToMitkImage(andFilter->GetOutput(), tempResult); + ITKGroupImageType::Pointer itkImage; + mitk::CastToItkImage(groupImageAtTS, itkImage); + inputImages.push_back(itkImage); + opsFilter->SetInput(inputIndex, itkImage); + ++inputIndex; + } - tempResult->DisconnectPipeline(); + opsFilter->Update(); + auto resultTS = opsFilter->GetOutput(); - auto result = mitk::LabelSetImage::New(); - result->InitializeByLabeledImage(tempResult); + result->SetVolume(resultTS->GetBufferPointer(), i); + } return result; } -mitk::LabelSetImage::Pointer mitk::BooleanOperation::GetUnion() const -{ - auto input1 = CastTo3DItkImage(m_SegmentationA, m_TimePoint); - auto input2 = CastTo3DItkImage(m_SegmentationB, m_TimePoint); - - auto orFilter = itk::OrImageFilter::New(); - orFilter->SetInput1(input1); - orFilter->SetInput2(input2); - orFilter->UpdateLargestPossibleRegion(); - - auto tempResult = Image::New(); - CastToMitkImage(orFilter->GetOutput(), tempResult); - - tempResult->DisconnectPipeline(); - - auto result = mitk::LabelSetImage::New(); - result->InitializeByLabeledImage(tempResult); - - return result; +mitk::Image::Pointer mitk::BooleanOperation::GenerateUnion(const LabelSetImage* segmentation, LabelSetImage::LabelValueVectorType labelValues, + std::function progressCallback) +{ + auto unionOps = [labelValues](const std::vector& inputArray) + { + mitk::Label::PixelType result = 0; + for (auto inIt = inputArray.cbegin(), refIt = labelValues.cbegin(); inIt != inputArray.cend(); ++inIt, ++refIt) + { + if (*inIt == *refIt) + { + result = 1; + break; + } + } + return result; + }; + + return GenerateInternal(segmentation, labelValues, unionOps, progressCallback); } -void mitk::BooleanOperation::ValidateSegmentation(mitk::Image::Pointer segmentation) const +mitk::Image::Pointer mitk::BooleanOperation::GenerateIntersection(const LabelSetImage* segmentation, LabelSetImage::LabelValueVectorType labelValues, + std::function progressCallback) { - if (segmentation.IsNull()) - mitkThrow() << "Segmentation is nullptr!"; - - if (segmentation->GetImageDescriptor()->GetNumberOfChannels() != 1) - mitkThrow() << "Segmentation has more than one channel!"; - - auto pixelType = segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType(); - - if (pixelType.GetPixelType() != itk::IOPixelEnum::SCALAR || - (pixelType.GetComponentType() != itk::IOComponentEnum::UCHAR && - pixelType.GetComponentType() != itk::IOComponentEnum::USHORT)) - mitkThrow() << "Segmentation is neither of type 'unsigned char' nor type 'unsigned short'!"; - - auto dimension = segmentation->GetDimension(); - - if (dimension > 4) - mitkThrow() << "Segmentation has more than four dimensions!"; - - if (dimension < 3) - mitkThrow() << "Segmentation has less than three dimensions!"; - - if (!segmentation->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) - mitkThrow() << "Segmentation is not defined for specified time point. Time point: " << m_TimePoint; + auto intersectOps = [labelValues](const std::vector& inputArray) + { + mitk::Label::PixelType result = 1; + for (auto inIt = inputArray.cbegin(), refIt = labelValues.cbegin(); inIt != inputArray.cend(); ++inIt, ++refIt) + { + if (*inIt != *refIt) + { + result = 0; + break; + } + } + return result; + }; + + return GenerateInternal(segmentation, labelValues, intersectOps, progressCallback); } -void mitk::BooleanOperation::ValidateSegmentations() const +mitk::Image::Pointer mitk::BooleanOperation::GenerateDifference(const LabelSetImage* segmentation, LabelSetImage::LabelValueType minuendLabelValue, + const LabelSetImage::LabelValueVectorType subtrahendLabelValues, std::function progressCallback) { - this->ValidateSegmentation(m_SegmentationA); - this->ValidateSegmentation(m_SegmentationB); - - if (m_SegmentationA->GetDimension() != m_SegmentationB->GetDimension()) - mitkThrow() << "Segmentations have different dimensions!"; - - const auto geometryA = m_SegmentationA->GetTimeGeometry()->GetGeometryForTimePoint(m_TimePoint); - const auto geometryB = m_SegmentationB->GetTimeGeometry()->GetGeometryForTimePoint(m_TimePoint); - if (!mitk::Equal(*(geometryA.GetPointer()), *(geometryB.GetPointer()),eps,false)) - { - mitkThrow() << "Segmentations have different geometries and cannot be used for boolean operations!"; - } + auto intersectOps = [minuendLabelValue, subtrahendLabelValues](const std::vector& inputArray) + { + if (minuendLabelValue != inputArray.front()) + return mitk::Label::PixelType(0); + + mitk::Label::PixelType result = 1; + for (auto inIt = inputArray.cbegin()+1, refIt = subtrahendLabelValues.cbegin(); inIt != inputArray.cend(); ++inIt, ++refIt) + { + if (*inIt == *refIt) + { + result = 0; + break; + } + } + return result; + }; + + auto labelValues = subtrahendLabelValues; + labelValues.insert(labelValues.begin(), minuendLabelValue); + return GenerateInternal(segmentation, labelValues, intersectOps, progressCallback); } diff --git a/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.h b/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.h index 8822957cd2..878133a987 100644 --- a/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.h +++ b/Modules/Segmentation/SegmentationUtilities/BooleanOperations/mitkBooleanOperation.h @@ -1,73 +1,38 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkBooleanOperation_h #define mitkBooleanOperation_h #include #include namespace mitk { /** \brief Executes a boolean operation on two different segmentations. - * * All parameters of the boolean operations must be specified during construction. * The actual operation is executed when calling GetResult(). */ - class MITKSEGMENTATION_EXPORT BooleanOperation + namespace BooleanOperation { - public: - enum Type - { - None, - Difference, - Intersection, - Union - }; - /* \brief Construct a boolean operation. - * - * Throws an mitk::Exception when segmentations are somehow invalid. - * - * \param[in] type The type of the boolean operation. - * \param[in] segmentationA The first operand of the boolean operation. - * \param[in] segmentationB The second operand of the boolean operation. - * \param[in] The time point at which the operation will be executed. - */ - BooleanOperation(Type type, Image::Pointer segmentationA, Image::Pointer segmentationB, TimePointType time = 0.); - ~BooleanOperation(); + Image::Pointer MITKSEGMENTATION_EXPORT GenerateUnion(const LabelSetImage*, LabelSetImage::LabelValueVectorType labelValues, + std::function progressCallback = [](float) {}); + Image::Pointer MITKSEGMENTATION_EXPORT GenerateIntersection(const LabelSetImage*, LabelSetImage::LabelValueVectorType labelValues, + std::function progressCallback = [](float) {}); + Image::Pointer MITKSEGMENTATION_EXPORT GenerateDifference(const LabelSetImage*, LabelSetImage::LabelValueType minuendLabelValue, + const LabelSetImage::LabelValueVectorType subtrahendLabelValues, std::function progressCallback = [](float) {}); - /* \brief Execute boolean operation and return resulting segmentation. - * - * \return The resulting segmentation. - */ - LabelSetImage::Pointer GetResult() const; - - private: - BooleanOperation(const BooleanOperation &); - BooleanOperation &operator=(const BooleanOperation &); - - LabelSetImage::Pointer GetDifference() const; - LabelSetImage::Pointer GetIntersection() const; - LabelSetImage::Pointer GetUnion() const; - - void ValidateSegmentation(Image::Pointer segmentation) const; - void ValidateSegmentations() const; - - Type m_Type; - Image::Pointer m_SegmentationA; - Image::Pointer m_SegmentationB; - TimePointType m_TimePoint; }; } #endif diff --git a/Modules/Segmentation/Testing/files.cmake b/Modules/Segmentation/Testing/files.cmake index c707e4775d..e4d304e6d1 100644 --- a/Modules/Segmentation/Testing/files.cmake +++ b/Modules/Segmentation/Testing/files.cmake @@ -1,26 +1,25 @@ set(MODULE_TESTS mitkContourMapper2DTest.cpp mitkContourTest.cpp mitkContourModelSetToImageFilterTest.cpp mitkDataNodeSegmentationTest.cpp - mitkFeatureBasedEdgeDetectionFilterTest.cpp mitkImageToContourFilterTest.cpp mitkSegmentationInterpolationTest.cpp mitkOverwriteSliceFilterTest.cpp mitkOverwriteSliceFilterObliquePlaneTest.cpp # mitkToolManagerTest.cpp mitkToolManagerProviderTest.cpp mitkManualSegmentationToSurfaceFilterTest.cpp #new cpp unit style mitkToolInteractionTest.cpp ) set(MODULE_CUSTOM_TESTS ) set(MODULE_TESTIMAGE US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) diff --git a/Modules/Segmentation/Testing/mitkFeatureBasedEdgeDetectionFilterTest.cpp b/Modules/Segmentation/Testing/mitkFeatureBasedEdgeDetectionFilterTest.cpp deleted file mode 100644 index ff8e4865ff..0000000000 --- a/Modules/Segmentation/Testing/mitkFeatureBasedEdgeDetectionFilterTest.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkTestingMacros.h" -#include -#include - -#include - -class mitkFeatureBasedEdgeDetectionFilterTestSuite : public mitk::TestFixture -{ - CPPUNIT_TEST_SUITE(mitkFeatureBasedEdgeDetectionFilterTestSuite); - MITK_TEST(testFeatureBasedEdgeDetectionFilterInitialization); - MITK_TEST(testInput); - MITK_TEST(testUnstructuredGridGeneration); - CPPUNIT_TEST_SUITE_END(); - -private: - /** Members used inside the different test methods. All members are initialized via setUp().*/ - mitk::Image::Pointer m_Pic3D; - mitk::Image::Pointer m_Segmentation; - -public: - /** - * @brief Setup Always call this method before each Test-case to ensure correct and new intialization of the used - * members for a new test case. (If the members are not used in a test, the method does not need to be called). - */ - void setUp() override - { - m_Pic3D = mitk::IOUtil::Load(GetTestDataFilePath("Pic3D.nrrd")); - m_Segmentation = mitk::IOUtil::Load(GetTestDataFilePath("PlaneSuggestion/pic3D_segmentation.nrrd")); - } - - void testFeatureBasedEdgeDetectionFilterInitialization() - { - mitk::FeatureBasedEdgeDetectionFilter::Pointer testFilter = mitk::FeatureBasedEdgeDetectionFilter::New(); - CPPUNIT_ASSERT_MESSAGE("Testing instantiation of test object", testFilter.IsNotNull()); - } - - void testInput() - { - mitk::FeatureBasedEdgeDetectionFilter::Pointer testFilter = mitk::FeatureBasedEdgeDetectionFilter::New(); - testFilter->SetInput(m_Pic3D); - CPPUNIT_ASSERT_MESSAGE("Testing set / get input!", testFilter->GetInput() == m_Pic3D); - } - - void testUnstructuredGridGeneration() - { - mitk::FeatureBasedEdgeDetectionFilter::Pointer testFilter = mitk::FeatureBasedEdgeDetectionFilter::New(); - testFilter->SetInput(m_Pic3D); - testFilter->SetSegmentationMask(m_Segmentation); - testFilter->Update(); - - CPPUNIT_ASSERT_MESSAGE("Testing surface generation!", testFilter->GetOutput()->GetVtkUnstructuredGrid() != nullptr); - } -}; - -MITK_TEST_SUITE_REGISTRATION(mitkFeatureBasedEdgeDetectionFilter) diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 25969c1317..4dee0e555b 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,121 +1,120 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp - Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkGrowCutSegmentationFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkSegmentationHelper.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkSegWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCloseRegionTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkEditableContourTool.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkLassoTool.cpp Interactions/mitkContourTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionBaseTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkGrowCutTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkPickingTool.cpp Interactions/mitknnUnetTool.cpp Interactions/mitkProcessExecutor.cpp Interactions/mitkSegmentAnythingProcessExecutor.cpp Interactions/mitkMonaiLabelTool.cpp Interactions/mitkMonaiLabel2DTool.cpp Interactions/mitkMonaiLabel3DTool.cpp Interactions/mitkTotalSegmentatorTool.cpp Interactions/mitkSegmentAnythingTool.cpp Interactions/mitkSegmentAnythingPythonService.cpp Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add.svg Add_Cursor.svg AI.svg AI_Cursor.svg Close.svg Close_Cursor.svg Erase.svg Erase_Cursor.svg Fill.svg Fill_Cursor.svg LiveWire.svg LiveWire_Cursor.svg Lasso.svg GrowCut.svg Lasso_Cursor.svg Otsu.svg Paint.svg Paint_Cursor.svg Picking.svg RegionGrowing.svg RegionGrowing_Cursor.svg Subtract.svg Subtract_Cursor.svg Threshold.svg ULThreshold.svg Wipe.svg Wipe_Cursor.svg Interactions/dummy.xml Interactions/EditableContourTool.xml Interactions/PickingTool.xml Interactions/MouseReleaseOnly.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationConfig.xml Interactions/SegmentationInteraction.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index f7fa9929f6..a5ab871e72 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1336 +1,1415 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include // mitk #include #include #include +#include // Qmitk #include #include #include #include // Qt #include #include #include #include #include +namespace +{ + void ActivateLabelHighlights(mitk::DataNode* node, const mitk::LabelSetImage::LabelValueVectorType highlightedValues) + { + const std::string propertyName = "org.mitk.multilabel.labels.highlighted"; + + mitk::IntVectorProperty::Pointer prop = dynamic_cast(node->GetNonConstProperty(propertyName)); + if (nullptr == prop) + { + prop = mitk::IntVectorProperty::New(); + node->SetProperty(propertyName, prop); + } + + mitk::IntVectorProperty::VectorType intValues(highlightedValues.begin(), highlightedValues.end()); + prop->SetValue(intValues); + prop->Modified(); //see T30386; needed because VectorProperty::SetValue does currently trigger no modified + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } + + void DeactivateLabelHighlights(mitk::DataNode* node) + { + std::string propertyName = "org.mitk.multilabel.labels.highlighted"; + + mitk::IntVectorProperty::Pointer prop = dynamic_cast(node->GetNonConstProperty(propertyName)); + if (nullptr != prop) + { + prop->SetValue({}); + prop->Modified(); //see T30386; needed because VectorProperty::SetValue does currently trigger no modified + + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } + } +} + QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector), m_SegmentationNodeDataMTime(0) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); auto* view = this->m_Controls->view; view->setItemDelegateForColumn(1, m_LockItemDelegate); view->setItemDelegateForColumn(2, m_ColorItemDelegate); view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); auto* header = view->header(); header->setSectionResizeMode(0,QHeaderView::Stretch); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); header->setSectionResizeMode(3, QHeaderView::ResizeToContents); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(m_Model, &QAbstractItemModel::dataChanged, this, &QmitkMultiLabelInspector::OnDataChanged); connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); + connect(view, &QAbstractItemView::entered, this, &QmitkMultiLabelInspector::OnEntered); + connect(view, &QmitkMultiLabelTreeView::MouseLeave, this, &QmitkMultiLabelInspector::OnMouseLeave); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; //in singel selection mode, if at least one label exist select the first label of the mode. if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0) { auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { m_Controls->view->setSelectionMode(multiMode ? QAbstractItemView::SelectionMode::MultiSelection : QAbstractItemView::SelectionMode::SingleSelection); } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool visibilityMod) { m_AllowVisibilityModification = visibilityMod; this->m_Model->SetAllowVisibilityModification(visibilityMod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lockMod) { m_AllowLockModification = lockMod; this->m_Model->SetAllowLockModification(lockMod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); emit SegmentationChanged(); } } mitk::LabelSetImage* QmitkMultiLabelInspector::GetMultiLabelSegmentation() const { return m_Segmentation; } void QmitkMultiLabelInspector::SetMultiLabelNode(mitk::DataNode* node) { if (node != this->m_SegmentationNode.GetPointer()) { m_SegmentationObserver.Reset(); m_SegmentationNode = node; m_SegmentationNodeDataMTime = 0; if (m_SegmentationNode.IsNotNull()) { auto& widget = *this; auto checkAndSetSeg = [&widget, node](const itk::EventObject&) { if (widget.m_SegmentationNodeDataMTime < node->GetDataReferenceChangedTime()) { auto newSeg = dynamic_cast(node->GetData()); if (nullptr == newSeg) mitkThrow() << "Invalid usage. Node set does not contain a segmentation."; widget.m_SegmentationNodeDataMTime = node->GetDataReferenceChangedTime(); widget.SetMultiLabelSegmentation(newSeg); } }; m_SegmentationObserver.Reset(node, itk::ModifiedEvent(), checkAndSetSeg); checkAndSetSeg(itk::ModifiedEvent()); } else { this->SetMultiLabelSegmentation(nullptr); } } } mitk::DataNode* QmitkMultiLabelInspector::GetMultiLabelNode() const { return m_SegmentationNode; } bool QmitkMultiLabelInspector::GetModelManipulationOngoing() const { return m_ModelManipulationOngoing; } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } void QmitkMultiLabelInspector::OnDataChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/, const QList& /*roles*/) { if (!m_ModelManipulationOngoing && topLeft.isValid()) m_Controls->view->expand(topLeft); } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin()); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { if (EqualLabelSelections(this->GetSelectedLabels(), selectedLabels)) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indexes of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); } void QmitkMultiLabelInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : std::as_const(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value(); currentIndexLabel = static_cast(uncastedLabel); } return currentIndexLabel; } QmitkMultiLabelInspector::IndexLevelType QmitkMultiLabelInspector::GetCurrentLevelType() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelInstanceVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceDataRole); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); if (labelInstanceVariant.isValid() ) { return IndexLevelType::LabelInstance; } else if (labelVariant.isValid()) { return IndexLevelType::LabelClass; } return IndexLevelType::Group; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetCurrentlyAffactedLabelInstances() const { auto currentIndex = m_Controls->view->currentIndex(); return m_Model->GetLabelsInSubTree(currentIndex); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetLabelInstancesOfSelectedFirstLabel() const { if (m_Segmentation.IsNull()) return {}; if (this->GetSelectedLabels().empty()) return {}; const auto index = m_Model->indexOfLabel(this->GetSelectedLabels().front()); return m_Model->GetLabelInstancesOfSameLabelClass(index); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstanceInternal(mitk::Label* templateLabel) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; if (nullptr == templateLabel) mitkThrow() << "QmitkMultiLabelInspector is in an invalid state. AddNewLabelInstanceInternal was called with a non existing label as template"; auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); m_ModelManipulationOngoing = true; auto newLabel = m_Segmentation->AddLabel(templateLabel, groupID, true); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; auto result = this->AddNewLabelInstanceInternal(currentLabel); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } return result; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) emit LabelRenameRequested(newLabel, false); m_ModelManipulationOngoing = true; m_Segmentation->AddLabel(newLabel, containingGroup, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (!index.isValid()) mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the " "model after adding it to the segmentation. Label value: " << newLabel->GetValue(); m_Controls->view->expand(index.parent()); emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } auto currentLabel = this->GetFirstSelectedLabelObject(); mitk::LabelSetImage::GroupIndexType groupID = nullptr != currentLabel ? m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()) : 0; auto result = AddNewLabelInternal(groupID); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } return result; } void QmitkMultiLabelInspector::DeleteLabelInstance() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInstance."; if (m_Segmentation.IsNull()) return; auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; auto index = m_Model->indexOfLabel(label->GetValue()); auto instanceName = index.data(Qt::DisplayRole); auto question = "Do you really want to delete label instance \"" + instanceName.toString() + "\"?"; auto answer = QMessageBox::question(this, QString("Delete label instances"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) { this->DeleteLabelInternal({ label->GetValue() }); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::DeleteLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabel."; if (m_Segmentation.IsNull()) return; const auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; const auto relevantLabels = this->GetLabelInstancesOfSelectedFirstLabel(); if (relevantLabels.empty()) return; auto question = "Do you really want to delete label \"" + QString::fromStdString(label->GetName()); question = relevantLabels.size()==1 ? question + "\"?" : question + "\" with all "+QString::number(relevantLabels.size()) +" instances?"; auto answer = QMessageBox::question(this, QString("Delete label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) { this->DeleteLabelInternal(relevantLabels); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::DeleteLabelInternal(const LabelValueVectorType& labelValues) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInternal."; if (m_Segmentation.IsNull()) { return; } QVariant nextLabelVariant; this->WaitCursorOn(); m_ModelManipulationOngoing = true; for (auto labelValue : labelValues) { if (labelValue == labelValues.back()) { auto currentIndex = m_Model->indexOfLabel(labelValue); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); nextLabelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); } m_Segmentation->RemoveLabel(labelValue); } m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (nextLabelVariant.isValid()) { auto newLabelValue = nextLabelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::Label* QmitkMultiLabelInspector::AddNewGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } mitk::LabelSetImage::GroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); m_Segmentation->SetActiveLayer(groupID); this->WaitCursorOff(); newLabel = this->AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Add group", "Could not add a new group. See error log for details."); } m_ModelManipulationOngoing = false; emit ModelUpdated(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } return newLabel; } void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) return; auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveGroup(groupID); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Delete group", "Could not delete the currently active group. See error log for details."); return; } if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::RemoveGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) { QMessageBox::information(this, "Delete group", "Cannot delete last remaining group. A segmentation must contain at least a single group."); return; } const auto* selectedLabel = this->GetFirstSelectedLabelObject(); if (selectedLabel == nullptr) return; const auto group = m_Segmentation->GetGroupIndexOfLabel(selectedLabel->GetValue()); auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(group); auto answer = QMessageBox::question(this, QStringLiteral("Delete group %1").arg(group), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(group); } void QmitkMultiLabelInspector::OnDeleteGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(groupID); auto answer = QMessageBox::question(this, QString("Delete group %1").arg(groupID), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(groupID); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); + auto currentIndex = this->m_Controls->view->currentIndex(); + //this ensures correct highlighting is the context menu is triggered while + //another context menu is already open. + if (currentIndex.isValid() && this->m_AboutToShowContextMenu) this->OnEntered(this->m_Controls->view->currentIndex()); + + + QMenu* menu = new QMenu(this); + if (IndexLevelType::Group == indexLevel) { - QMenu* menu = new QMenu(this); - if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg")), "&Add label", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); if (m_Segmentation->GetNumberOfLayers() > 1) { QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg")), "Delete group", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); menu->addAction(removeAction); } } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock group", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock group", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show group", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide group", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } - menu->popup(QCursor::pos()); } else if (IndexLevelType::LabelClass == indexLevel) { - QMenu* menu = new QMenu(this); - if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "&Delete label", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock label instances", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock label instances", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show label instances", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide label instances", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr!=opacityAction) menu->addAction(opacityAction); } - menu->popup(QCursor::pos()); } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; - QMenu* menu = new QMenu(this); - if (this->GetMultiSelectionMode() && selectedLabelValues.size() > 1) { if (m_AllowLabelModification) { QAction* mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete selected labels", this); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnDeleteLabels(bool))); menu->addAction(removeLabelsAction); QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); menu->addAction(clearLabelsAction); } if (m_AllowVisibilityModification) { if (m_AllowLabelModification) menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "&Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); const auto selectedLabelIndex = m_Model->indexOfLabel(selectedLabelValues.front()); if (m_Model->GetLabelInstancesOfSameLabelClass(selectedLabelIndex).size() > 1) // Only labels that actually appear as instance (having additional instances) { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label instance", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete label instance", this); QObject::connect(removeInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabelInstance); menu->addAction(removeInstanceAction); } else { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); } QAction* removeLabelAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "Delete &label", this); QObject::connect(removeLabelAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabel); menu->addAction(removeLabelAction); QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); menu->addAction(clearAction); } if (m_AllowVisibilityModification) { if (m_AllowLabelModification) menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } - menu->popup(QCursor::pos()); } + + QObject::connect(menu, &QMenu::aboutToHide, this, &QmitkMultiLabelInspector::OnMouseLeave); + m_AboutToShowContextMenu = true; + menu->popup(QCursor::pos()); } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto selectedLabelValues = this->GetSelectedLabels(); auto relevantLabelValues = !this->GetMultiSelectionMode() || selectedLabelValues.size() <= 1 ? this->GetCurrentlyAffactedLabelInstances() : selectedLabelValues; std::vector relevantLabels; if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; relevantLabels.emplace_back(label); } auto* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto opacity = relevantLabels.front()->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); auto segmentation = m_Segmentation; + auto node = m_SegmentationNode; - QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels](const int value) - { - auto opacity = static_cast(value) / 100.0f; - for (auto label : relevantLabels) + auto onChangeLambda = [segmentation, relevantLabels](const int value) { - label->SetOpacity(opacity); - segmentation->UpdateLookupTable(label->GetValue()); - } - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } - ); + auto opacity = static_cast(value) / 100.0f; + for (auto label : relevantLabels) + { + label->SetOpacity(opacity); + segmentation->UpdateLookupTable(label->GetValue()); + } + segmentation->GetLookupTable()->Modified(); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + }; + QObject::connect(opacitySlider, &QSlider::valueChanged, this, onChangeLambda); + + auto onPressedLambda = [node]() + { + DeactivateLabelHighlights(node); + }; + QObject::connect(opacitySlider, &QSlider::sliderPressed, this, onPressedLambda); + + auto onReleasedLambda = [node, relevantLabelValues]() + { + ActivateLabelHighlights(node, relevantLabelValues); + }; + QObject::connect(opacitySlider, &QSlider::sliderReleased, this, onReleasedLambda); QLabel* opacityLabel = new QLabel("Opacity: "); QVBoxLayout* opacityWidgetLayout = new QVBoxLayout; opacityWidgetLayout->setContentsMargins(4, 4, 4, 4); opacityWidgetLayout->addWidget(opacityLabel); opacityWidgetLayout->addWidget(opacitySlider); QWidget* opacityWidget = new QWidget; opacityWidget->setLayout(opacityWidgetLayout); QWidgetAction* opacityAction = new QWidgetAction(this); opacityAction->setDefaultWidget(opacityWidget); return opacityAction; } return nullptr; } void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Clear selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabels(this->GetSelectedLabels()); this->WaitCursorOff(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnDeleteAffectedLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto affectedLabels = GetCurrentlyAffactedLabelInstances(); auto currentLabel = m_Segmentation->GetLabel(affectedLabels.front()); QString question = "Do you really want to delete all instances of label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnDeleteLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels()); this->WaitCursorOff(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnAddLabel() { auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); this->AddNewLabelInternal(groupID); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to clear the contents of label \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Clear label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabel(currentLabel->GetValue()); this->WaitCursorOff(); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); emit LabelRenameRequested(currentLabel, true); for (auto value : relevantLabelValues) { if (value != currentLabel->GetValue()) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetName(currentLabel->GetName()); label->SetColor(currentLabel->GetColor()); m_Segmentation->UpdateLookupTable(label->GetValue()); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); // this is needed as workaround for (T27307). It circumvents the fact that modifications // of data (here the segmentation) does not directly trigger the modification of the // owning node (see T27307). Therefore other code (like renderers or model views) that e.g. // listens to the datastorage for modification would not get notified. if (m_SegmentationNode.IsNotNull()) { m_SegmentationNode->Modified(); } } } emit ModelUpdated(); } void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetLocked(locked); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { this->SetLockOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnLockAffectedLabels() { this->SetLockOfAffectedLabels(true); } void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetVisible(visible); m_Segmentation->UpdateLookupTable(label->GetValue()); } + m_Segmentation->GetLookupTable()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; m_Segmentation->SetAllLabelsVisible(false); for (auto selectedValue : selectedLabelValues) { auto currentLabel = m_Segmentation->GetLabel(selectedValue); currentLabel->SetVisible(true); m_Segmentation->UpdateLookupTable(selectedValue); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(selectedLabelValues.front()); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; if (index.column() > 0) return; auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { this->OnRenameLabel(false); return; } this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::PrepareGoToLabel(mitk::Label::PixelType labelID) const { this->WaitCursorOn(); m_Segmentation->UpdateCenterOfMass(labelID); const auto currentLabel = m_Segmentation->GetLabel(labelID); const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } +void QmitkMultiLabelInspector::OnEntered(const QModelIndex& index) +{ + if (m_SegmentationNode.IsNotNull()) + { + auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); + + auto highlightedValues = m_Model->GetLabelsInSubTree(index); + + ActivateLabelHighlights(m_SegmentationNode, highlightedValues); + } + m_AboutToShowContextMenu = false; +} + +void QmitkMultiLabelInspector::OnMouseLeave() +{ + if (m_SegmentationNode.IsNotNull() && !m_AboutToShowContextMenu) + { + DeactivateLabelHighlights(m_SegmentationNode); + } + else + { + m_AboutToShowContextMenu = false; + } +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h index cadca6ed32..9d660a34d4 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h @@ -1,329 +1,333 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkMultiLabelInspector_h #define QmitkMultiLabelInspector_h #include #include #include #include #include #include class QmitkMultiLabelTreeModel; class QStyledItemDelegate; class QWidgetAction; namespace Ui { class QmitkMultiLabelInspector; } /* * @brief This is an inspector that offers a tree view on the labels and groups of a MultiLabelSegmentation instance. * It also allows some manipulation operations an the labels/groups accordin to the UI/selection state. */ class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelInspector : public QWidget { Q_OBJECT public: QmitkMultiLabelInspector(QWidget* parent = nullptr); ~QmitkMultiLabelInspector(); bool GetMultiSelectionMode() const; bool GetAllowVisibilityModification() const; bool GetAllowLockModification() const; bool GetAllowLabelModification() const; /** Indicates if the inspector is currently modifiying the model/segmentation. Thus as long as the manipulation is ongoing, one should assume the model to be in an invalid state.*/ bool GetModelManipulationOngoing() const; using LabelValueType = mitk::LabelSetImage::LabelValueType; using LabelValueVectorType = mitk::LabelSetImage::LabelValueVectorType; /** * @brief Retrieve the currently selected labels (equals the last CurrentSelectionChanged values). */ LabelValueVectorType GetSelectedLabels() const; /** @brief Returns the label that currently has the focus in the tree view. * * The focus is indicated by QTreeView::currentIndex, thus the mouse is over it and it has a dashed border line. * * The current label must not equal the selected label(s). If the mouse is not hovering above a label * (label class or instance item), the method will return nullptr. */ mitk::Label* GetCurrentLabel() const; enum class IndexLevelType { Group, LabelClass, LabelInstance }; /** @brief Returns the level of the index that currently has the focus in the tree view. * * The focus is indicated by QTreeView::currentIndex, thus the mouse is over it and it has a dashed border line. */ IndexLevelType GetCurrentLevelType() const; /** @brief Returns all label values that are currently affected. * * Affected means that these labels (including the one returned by GetCurrentLabel) are in the subtree of the tree * view element that currently has the focus (indicated by QTreeView::currentIndex, thus the mouse is over it and * it has a dashed border line. */ LabelValueVectorType GetCurrentlyAffactedLabelInstances() const; /** @brief Returns the values of all label instances that are of the same label (class) like the first selected label instance. * * If no label is selected an empty vector will be returned. */ LabelValueVectorType GetLabelInstancesOfSelectedFirstLabel() const; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(LabelValueVectorType labels) const; /** * @brief A signal that will be emitted if the user has requested to "go to" a certain label. * * Going to a label would be e.g. to focus the renderwindows on the centroid of the label. * @param label The label that should be focused. * @param point in World coordinate that should be focused. */ void GoToLabel(LabelValueType label, const mitk::Point3D& point) const; /** @brief Signal that is emitted, if a label should be (re)named and default * label naming is deactivated. * * The instance for which a new name is requested is passed with the signal. * @param label Pointer to the instance that needs a (new) name. * @param rename Indicates if it is a renaming or naming of a new label. */ void LabelRenameRequested(mitk::Label* label, bool rename) const; /** @brief Signal that is emitted, if the model was updated (e.g. by a delete or add operation).*/ void ModelUpdated() const; /** @brief Signal is emitted, if the segmentation is changed that is observed by the inspector.*/ void SegmentationChanged() const; public Q_SLOTS: /** * @brief Transform a list of label values into the new selection of the inspector. * @param selectedLabels A list of selected label values. * @remark Using this method to select labels will not trigger the CurrentSelectionChanged signal. Observers * should regard that to avoid signal loops. */ void SetSelectedLabels(const LabelValueVectorType& selectedLabels); /** * @brief The passed label will be used as new selection in the widget * @param selectedLabel Value of the selected label. * @remark Using this method to select labels will not trigger the CurrentSelectionChanged signal. Observers * should regard that to avoid signal loops. */ void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); /** @brief Sets the segmentation that will be used and monitored by the widget. * @param segmentation A pointer to the segmentation to set. * @remark You cannot set the segmentation directly if a segmentation node is * also set. Reset the node (nullptr) if you want to change to direct segmentation * setting. * @pre Segmentation node is nullptr. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); mitk::LabelSetImage* GetMultiLabelSegmentation() const; /** * @brief Sets the segmentation node that will be used /monitored by the widget. * * @param node A pointer to the segmentation node. * @remark If not set some features (e.g. highlighting in render windows) of the inspectors * are not active. * @remark Currently it is also needed to circumvent the fact that * modification of data does not directly trigger modification of the * node (see T27307). */ void SetMultiLabelNode(mitk::DataNode* node); mitk::DataNode* GetMultiLabelNode() const; void SetMultiSelectionMode(bool multiMode); void SetAllowVisibilityModification(bool visiblityMod); void SetAllowLockModification(bool lockMod); void SetAllowLabelModification(bool labelMod); void SetDefaultLabelNaming(bool defaultLabelNaming); /** @brief Adds an instance of the same label/class like the first label instance * indicated by GetSelectedLabels() to the segmentation. * * This new label instance is returned by the function. If the inspector has no selected label, * no new instance will be generated and nullptr will be returned. * * @remark The new label instance is a clone of the selected label instance. * Therefore all properties but the LabelValue will be the same. * * @pre AllowLabeModification must be set to true. */ mitk::Label* AddNewLabelInstance(); /** @brief Adds a new label to the segmentation. * Depending on the settings the name of * the label will be either default generated or the rename delegate will be used. The label * will be added to the same group as the first currently selected label. * * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewLabel(); /** @brief Removes the first currently selected label instance of the segmentation. * If no label is selected * nothing will happen. * * @pre AllowLabeModification must be set to true.*/ void DeleteLabelInstance(); /** @brief Delete the first currently selected label and all its instances of the segmentation. * If no label is selected * nothing will happen. * * @pre AllowLabeModification must be set to true.*/ void DeleteLabel(); /** @brief Adds a new group with a new label to segmentation. * * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewGroup(); /** @brief Removes the group of the first currently selected label of the segmentation. *If no label is selected nothing will happen. * * @pre AllowLabeModification must be set to true.*/ void RemoveGroup(); void SetVisibilityOfAffectedLabels(bool visible) const; void SetLockOfAffectedLabels(bool visible) const; protected: void Initialize(); void OnModelReset(); void OnDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList& roles = QList()); QmitkMultiLabelTreeModel* m_Model; mitk::LabelSetImage::Pointer m_Segmentation; LabelValueVectorType m_LastValidSelectedLabels; QStyledItemDelegate* m_LockItemDelegate; QStyledItemDelegate* m_ColorItemDelegate; QStyledItemDelegate* m_VisibilityItemDelegate; Ui::QmitkMultiLabelInspector* m_Controls; LabelValueVectorType GetSelectedLabelsFromSelectionModel() const; void UpdateSelectionModel(const LabelValueVectorType& selectedLabels); /** @brief Helper that returns the label object (if multiple labels are selected the first). */ mitk::Label* GetFirstSelectedLabelObject() const; mitk::Label* AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup); /**@brief Adds an instance of the same label/class like the passed label value */ mitk::Label* AddNewLabelInstanceInternal(mitk::Label* templateLabel); void RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID); void DeleteLabelInternal(const LabelValueVectorType& labelValues); private Q_SLOTS: /** @brief Transform a labels selection into a data node list and emit the 'CurrentSelectionChanged'-signal. * * The function adds the selected nodes from the original selection that could not be modified, if * m_SelectOnlyVisibleNodes is false. * This slot is internally connected to the 'selectionChanged'-signal of the selection model of the private member item view. * * @param selected The newly selected items. * @param deselected The newly deselected items. */ void OnChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected); void OnContextMenuRequested(const QPoint&); void OnAddLabel(); void OnAddLabelInstance(); void OnDeleteGroup(); void OnDeleteAffectedLabel(); void OnDeleteLabels(bool); void OnClearLabels(bool); void OnMergeLabels(bool); void OnRenameLabel(bool); void OnClearLabel(bool); void OnUnlockAffectedLabels(); void OnLockAffectedLabels(); void OnSetAffectedLabelsVisible(); void OnSetAffectedLabelsInvisible(); void OnSetOnlyActiveLabelVisible(bool); void OnItemDoubleClicked(const QModelIndex& index); void WaitCursorOn() const; void WaitCursorOff() const; void RestoreOverrideCursor() const; void PrepareGoToLabel(LabelValueType labelID) const; + void OnEntered(const QModelIndex& index); + void OnMouseLeave(); + QWidgetAction* CreateOpacityAction(); private: bool m_ShowVisibility = true; bool m_ShowLock = true; bool m_ShowOther = false; /** @brief Indicates if the context menu allows changes in visiblity. * * Visiblity includes also color */ bool m_AllowVisibilityModification = true; /** @brief Indicates if the context menu allows changes in lock state. */ bool m_AllowLockModification = true; /** @brief Indicates if the context menu allows label modifications (adding, removing, renaming ...) */ bool m_AllowLabelModification = false; bool m_DefaultLabelNaming = true; bool m_ModelManipulationOngoing = false; + bool m_AboutToShowContextMenu = false; mitk::DataNode::Pointer m_SegmentationNode; unsigned long m_SegmentationNodeDataMTime; mitk::ITKEventObserverGuard m_SegmentationObserver; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index 1380a9b500..9608c7f2dd 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,1051 +1,1052 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiLabelTreeModel.h" #include #include #include class QmitkMultiLabelSegTreeItem { public: enum class ItemType { Group, Label, Instance }; QmitkMultiLabelSegTreeItem() { }; explicit QmitkMultiLabelSegTreeItem(ItemType type, QmitkMultiLabelSegTreeItem* parentItem, mitk::Label* label = nullptr, std::string className = ""): m_parentItem(parentItem), m_ItemType(type), m_Label(label), m_ClassName(className) { }; ~QmitkMultiLabelSegTreeItem() { for (auto item : m_childItems) { delete item; } }; void AppendChild(QmitkMultiLabelSegTreeItem* child) { m_childItems.push_back(child); }; void RemoveChild(std::size_t row) { if (row < m_childItems.size()) { delete m_childItems[row]; m_childItems.erase(m_childItems.begin() + row); } }; int Row() const { if (m_parentItem) { auto finding = std::find(m_parentItem->m_childItems.begin(), m_parentItem->m_childItems.end(), this); if (finding != m_parentItem->m_childItems.end()) { return std::distance(m_parentItem->m_childItems.begin(), finding); } } return 0; }; QmitkMultiLabelSegTreeItem* ParentItem() { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* ParentItem() const { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* NextSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row + 1 < m_parentItem->m_childItems.size()) return m_parentItem->m_childItems[row+1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* PrevSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row > 0) return m_parentItem->m_childItems[row-1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* RootItem() const { auto item = this; while (item->m_parentItem != nullptr) { item = item->m_parentItem; } return item; }; std::size_t GetGroupID() const { auto root = this->RootItem(); auto item = this; if (root == this) return 0; while (root != item->m_parentItem) { item = item->m_parentItem; } auto iter = std::find(root->m_childItems.begin(), root->m_childItems.end(), item); if (root->m_childItems.end() == iter) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Root does not have an currentItem as child that has root as parent."; return std::distance(root->m_childItems.begin(), iter); } bool HandleAsInstance() const { return (ItemType::Instance == m_ItemType) || ((ItemType::Label == m_ItemType) && (m_childItems.size() == 1)); } mitk::Label* GetLabel() const { if (ItemType::Instance == m_ItemType) { return m_Label; } if (ItemType::Label == m_ItemType) { if (m_childItems.empty()) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Internal label currentItem has no instance currentItem."; return m_childItems[0]->GetLabel(); } return nullptr; }; mitk::LabelSetImage::LabelValueType GetLabelValue() const { auto label = this->GetLabel(); if (nullptr == label) { mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Called GetLabelValue on an group currentItem."; } return label->GetValue(); }; /** returns a vector containing all label values of referenced by this item or its child items.*/ std::vector< mitk::LabelSetImage::LabelValueType> GetLabelsInSubTree() const { if (this->m_ItemType == ItemType::Instance) { return { this->GetLabelValue() }; } std::vector< mitk::LabelSetImage::LabelValueType> result; for (const auto child : this->m_childItems) { auto childresult = child->GetLabelsInSubTree(); result.reserve(result.size() + childresult.size()); result.insert(result.end(), childresult.begin(), childresult.end()); } return result; } std::vector m_childItems; QmitkMultiLabelSegTreeItem* m_parentItem = nullptr; ItemType m_ItemType = ItemType::Group; mitk::Label::Pointer m_Label; std::string m_ClassName; }; QModelIndex GetIndexByItem(const QmitkMultiLabelSegTreeItem* start, const QmitkMultiLabelTreeModel* model) { QModelIndex parentIndex = QModelIndex(); if (nullptr != start->m_parentItem) { parentIndex = GetIndexByItem(start->m_parentItem, model); } else { return parentIndex; } return model->index(start->Row(), 0, parentIndex); } QmitkMultiLabelSegTreeItem* GetGroupItem(QmitkMultiLabelTreeModel::GroupIndexType groupIndex, QmitkMultiLabelSegTreeItem* root) { if (nullptr != root && groupIndex < root->m_childItems.size()) { return root->m_childItems[groupIndex]; } return nullptr; } QmitkMultiLabelSegTreeItem* GetInstanceItem(QmitkMultiLabelTreeModel::LabelValueType labelValue, QmitkMultiLabelSegTreeItem* root) { QmitkMultiLabelSegTreeItem* result = nullptr; for (auto item : root->m_childItems) { result = GetInstanceItem(labelValue, item); if (nullptr != result) return result; } if (root->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Instance && root->GetLabelValue() == labelValue) { return root; } return nullptr; } const QmitkMultiLabelSegTreeItem* GetFirstInstanceLikeItem(const QmitkMultiLabelSegTreeItem* startItem) { const QmitkMultiLabelSegTreeItem* result = nullptr; if (nullptr != startItem) { if (startItem->HandleAsInstance()) { result = startItem; } else if (!startItem->m_childItems.empty()) { result = GetFirstInstanceLikeItem(startItem->m_childItems.front()); } } return result; } QmitkMultiLabelSegTreeItem* GetLabelItemInGroup(const std::string& labelName, QmitkMultiLabelSegTreeItem* group) { if (nullptr != group) { auto predicate = [labelName](const QmitkMultiLabelSegTreeItem* item) { return labelName == item->m_ClassName; }; auto finding = std::find_if(group->m_childItems.begin(), group->m_childItems.end(), predicate); if (group->m_childItems.end() != finding) { return *finding; } } return nullptr; } QmitkMultiLabelTreeModel::QmitkMultiLabelTreeModel(QObject *parent) : QAbstractItemModel(parent) { m_RootItem = std::make_unique(); } QmitkMultiLabelTreeModel ::~QmitkMultiLabelTreeModel() { this->SetSegmentation(nullptr); }; int QmitkMultiLabelTreeModel::columnCount(const QModelIndex& /*parent*/) const { return 4; } int QmitkMultiLabelTreeModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; if (m_Segmentation.IsNull()) return 0; QmitkMultiLabelSegTreeItem* parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); if (parentItem->HandleAsInstance()) { return 0; } return parentItem->m_childItems.size(); } QVariant QmitkMultiLabelTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); if (!item) return QVariant(); if (role == Qt::DisplayRole||role == Qt::EditRole) { if (TableColumns::NAME_COL == index.column()) { switch (item->m_ItemType) { case QmitkMultiLabelSegTreeItem::ItemType::Group: return QVariant(QString("Group %1").arg(item->GetGroupID())); case QmitkMultiLabelSegTreeItem::ItemType::Label: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; QString name = QString::fromStdString(label->GetName()); if (!item->HandleAsInstance()) name = name + QString(" (%1 instances)").arg(item->m_childItems.size()); return QVariant(name); } case QmitkMultiLabelSegTreeItem::ItemType::Instance: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; return QVariant(QString::fromStdString(label->GetName()) + QString(" [%1]").arg(item->GetLabelValue())); } } } else { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { return QVariant(label->GetLocked()); } else if (TableColumns::COLOR_COL == index.column()) { return QVariant(QColor(label->GetColor().GetRed() * 255, label->GetColor().GetGreen() * 255, label->GetColor().GetBlue() * 255)); } else if (TableColumns::VISIBLE_COL == index.column()) { return QVariant(label->GetVisible()); } } } } else if (role == Qt::ToolTipRole) { if (item->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Group) { return QVariant(QString("Group %1").arg(item->GetGroupID())); } else { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is referring to a label that does not exist."; QString name = QString::fromStdString(""+label->GetName()+""); if (!item->HandleAsInstance()) { name = QString("Label class: %1\nContaining %2 instances").arg(name).arg(item->m_childItems.size()); } else { name = QString::fromStdString(label->GetName()) + QString("\nLabel instance ID: %1\nPixel value: %2").arg(item->GetLabelValue()).arg(item->GetLabelValue()); } return QVariant(name); } } else if (role == ItemModelRole::LabelDataRole) { auto label = item->GetLabel(); if (nullptr!=label) return QVariant::fromValue(label); } else if (role == ItemModelRole::LabelValueRole) { auto label = item->GetLabel(); if (nullptr != label) return QVariant(label->GetValue()); } else if (role == ItemModelRole::LabelInstanceDataRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant::fromValue(label); } } else if (role == ItemModelRole::LabelInstanceValueRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant(label->GetValue()); } } else if (role == ItemModelRole::GroupIDRole) { QVariant v; v.setValue(item->GetGroupID()); return v; } return QVariant(); } mitk::Color QtToMitk(const QColor& color) { mitk::Color mitkColor; mitkColor.SetRed(color.red() / 255.0f); mitkColor.SetGreen(color.green() / 255.0f); mitkColor.SetBlue(color.blue() / 255.0f); return mitkColor; } bool QmitkMultiLabelTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; auto item = static_cast(index.internalPointer()); if (!item) return false; if (role == Qt::EditRole) { if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { label->SetLocked(value.toBool()); } else if (TableColumns::COLOR_COL == index.column()) { label->SetColor(QtToMitk(value.value())); } else if (TableColumns::VISIBLE_COL == index.column()) { label->SetVisible(value.toBool()); } m_Segmentation->UpdateLookupTable(label->GetValue()); + m_Segmentation->GetLookupTable()->Modified(); m_Segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { } return true; } } return false; } QModelIndex QmitkMultiLabelTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); auto parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); QmitkMultiLabelSegTreeItem *childItem = parentItem->m_childItems[row]; if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkMultiLabelTreeModel::indexOfLabel(mitk::Label::PixelType labelValue) const { if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return QModelIndex(); auto relevantItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == relevantItem) return QModelIndex(); auto labelItem = relevantItem->ParentItem(); if (labelItem->m_childItems.size() == 1) { //was the only instance of the label, therefor return the label item instat. relevantItem = labelItem; } return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::indexOfGroup(mitk::LabelSetImage::GroupIndexType groupIndex) const { auto relevantItem = GetGroupItem(groupIndex, this->m_RootItem.get()); if (nullptr == relevantItem) QModelIndex(); return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkMultiLabelSegTreeItem *childItem = static_cast(child.internalPointer()); QmitkMultiLabelSegTreeItem *parentItem = childItem->ParentItem(); if (parentItem == m_RootItem.get()) return QModelIndex(); return createIndex(parentItem->Row(), 0, parentItem); } QModelIndex QmitkMultiLabelTreeModel::ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const { if (!currentIndex.isValid()) return QModelIndex(); auto currentItem = static_cast(currentIndex.internalPointer()); if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; auto searchItem = currentItem; const auto rootItem = currentItem->RootItem(); while (searchItem != rootItem) { const auto* sibling = searchItem; while (sibling != nullptr) { sibling = sibling->NextSibblingItem(); resultItem = GetFirstInstanceLikeItem(sibling); if (nullptr != resultItem) break; } if (nullptr != resultItem) break; // No next closest label instance on this level -> check for closest before sibling = searchItem; while (sibling != nullptr) { sibling = sibling->PrevSibblingItem(); resultItem = GetFirstInstanceLikeItem(sibling); if (nullptr != resultItem) break; } if (nullptr != resultItem) break; // No closest label instance before current on this level -> moeve one level up searchItem = searchItem->ParentItem(); } if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } QModelIndex QmitkMultiLabelTreeModel::FirstLabelInstanceIndex(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; resultItem = GetFirstInstanceLikeItem(currentItem); if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } ///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance //or instance node). If current index is at the end, an invalid index is returned.*/ //QModelIndex QmitkMultiLabelTreeModel::PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; std::vector QmitkMultiLabelTreeModel::GetLabelsInSubTree(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; return currentItem->GetLabelsInSubTree(); } std::vector QmitkMultiLabelTreeModel::GetLabelInstancesOfSameLabelClass(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (currentIndex.isValid()) { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Group == currentItem->m_ItemType) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Instance == currentItem->m_ItemType) currentItem = currentItem->ParentItem(); return currentItem->GetLabelsInSubTree(); } Qt::ItemFlags QmitkMultiLabelTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; if (!index.isValid()) return Qt::NoItemFlags; auto item = static_cast(index.internalPointer()); if (!item) return Qt::NoItemFlags; if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance() && ((TableColumns::VISIBLE_COL == index.column() && m_AllowVisibilityModification) || (TableColumns::COLOR_COL == index.column() && m_AllowVisibilityModification) || //m_AllowVisibilityModification controls visibility and color (TableColumns::LOCKED_COL == index.column() && m_AllowLockModification))) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; } else { return Qt::ItemIsEnabled; } } else { if (item->HandleAsInstance()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return Qt::ItemIsEnabled; } } return Qt::NoItemFlags; } QVariant QmitkMultiLabelTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (TableColumns::NAME_COL == section) { return "Name"; } else if (TableColumns::LOCKED_COL == section) { return "Locked"; } else if (TableColumns::COLOR_COL == section) { return "Color"; } else if (TableColumns::VISIBLE_COL == section) { return "Visibility"; } } return QVariant(); } const mitk::LabelSetImage* QmitkMultiLabelTreeModel::GetSegmentation() const { return m_Segmentation; } void QmitkMultiLabelTreeModel::SetSegmentation(mitk::LabelSetImage* segmentation) { if (m_Segmentation != segmentation) { this->m_Segmentation = segmentation; this->AddObserver(); this->UpdateInternalTree(); } } /**Helper function that adds a labek into the item tree. Passes back the new created instance iten*/ QmitkMultiLabelSegTreeItem* AddLabelToGroupTree(mitk::Label* label, QmitkMultiLabelSegTreeItem* groupItem, bool& newLabelItemCreated) { if (nullptr == groupItem) return nullptr; if (nullptr == label) return nullptr; newLabelItemCreated = false; std::set labelNames; for (auto labelItem : groupItem->m_childItems) { labelNames.emplace(labelItem->GetLabel()->GetName()); } QmitkMultiLabelSegTreeItem* labelItem = nullptr; auto finding = labelNames.find(label->GetName()); if (finding != labelNames.end()) { //other label with same name exists labelItem = groupItem->m_childItems[std::distance(labelNames.begin(), finding)]; } else { newLabelItemCreated = true; labelItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Label, groupItem, nullptr, label->GetName()); auto predicate = [label](const std::string& name) { return name > label->GetName(); }; auto insertFinding = std::find_if(labelNames.begin(), labelNames.end(), predicate); groupItem->m_childItems.insert(groupItem->m_childItems.begin() + std::distance(labelNames.begin(), insertFinding), labelItem); } auto instanceItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Instance, labelItem, label); auto predicate = [label](const QmitkMultiLabelSegTreeItem* item) { return item->GetLabelValue() > label->GetValue(); }; auto insertFinding = std::find_if(labelItem->m_childItems.begin(), labelItem->m_childItems.end(), predicate); labelItem->m_childItems.insert(labelItem->m_childItems.begin() + std::distance(labelItem->m_childItems.begin(), insertFinding), instanceItem); return instanceItem; } void QmitkMultiLabelTreeModel::GenerateInternalGroupTree(unsigned int groupID, QmitkMultiLabelSegTreeItem* groupItem) { auto labels = m_Segmentation->GetLabelsByValue(m_Segmentation->GetLabelValuesByGroup(groupID)); for (auto& label : labels) { if (label->GetValue()== mitk::LabelSetImage::UNLABELED_VALUE) continue; bool newItemCreated = false; AddLabelToGroupTree(label, groupItem, newItemCreated); } } QmitkMultiLabelSegTreeItem* QmitkMultiLabelTreeModel::GenerateInternalTree() { auto rootItem = new QmitkMultiLabelSegTreeItem(); if (m_Segmentation.IsNotNull()) { for (unsigned int groupID = 0; groupID < m_Segmentation->GetNumberOfLayers(); ++groupID) { auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); GenerateInternalGroupTree(groupID, groupItem); } } return rootItem; } void QmitkMultiLabelTreeModel::UpdateInternalTree() { emit beginResetModel(); auto newTree = this->GenerateInternalTree(); this->m_RootItem.reset(newTree); emit endResetModel(); emit modelChanged(); } void QmitkMultiLabelTreeModel::ITKEventHandler(const itk::EventObject& e) { if (mitk::LabelAddedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnLabelAdded(labelEvent->GetLabelValue()); } else if (mitk::LabelModifiedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnLabelModified(labelEvent->GetLabelValue()); } else if (mitk::LabelRemovedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnLabelRemoved(labelEvent->GetLabelValue()); } else if (mitk::GroupAddedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnGroupAdded(labelEvent->GetGroupID()); } else if (mitk::GroupModifiedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnGroupModified(labelEvent->GetGroupID()); } else if (mitk::GroupRemovedEvent().CheckEvent(&e)) { auto labelEvent = dynamic_cast(&e); this->OnGroupRemoved(labelEvent->GetGroupID()); } } void QmitkMultiLabelTreeModel::AddObserver() { m_LabelAddedObserver.Reset(); m_LabelModifiedObserver.Reset(); m_LabelRemovedObserver.Reset(); m_GroupAddedObserver.Reset(); m_GroupModifiedObserver.Reset(); m_GroupRemovedObserver.Reset(); if (this->m_Segmentation.IsNotNull()) { auto& model = *this; m_LabelAddedObserver.Reset(m_Segmentation, mitk::LabelAddedEvent(), [&model](const itk::EventObject& event){model.ITKEventHandler(event);}); m_LabelModifiedObserver.Reset(m_Segmentation, mitk::LabelModifiedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); m_LabelRemovedObserver.Reset(m_Segmentation, mitk::LabelRemovedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); m_GroupAddedObserver.Reset(m_Segmentation, mitk::GroupAddedEvent(), [&model](const itk::EventObject& event) { model.ITKEventHandler(event); }); m_GroupModifiedObserver.Reset(m_Segmentation, mitk::GroupModifiedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); m_GroupRemovedObserver.Reset(m_Segmentation, mitk::GroupRemovedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); } } void QmitkMultiLabelTreeModel::OnLabelAdded(LabelValueType labelValue) { GroupIndexType groupIndex = m_Segmentation->GetGroupIndexOfLabel(labelValue); auto label = m_Segmentation->GetLabel(labelValue); if (nullptr == label) mitkThrow() << "Invalid internal state. Segmentation signaled the addition of an label that does not exist in the segmentation. Invalid label value:" << labelValue; if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return; auto groupItem = GetGroupItem(groupIndex, this->m_RootItem.get()); bool newLabelCreated = false; auto instanceItem = AddLabelToGroupTree(label, groupItem, newLabelCreated); if (newLabelCreated) { if (groupItem->m_childItems.size() == 1) { //first label added auto groupIndex = GetIndexByItem(groupItem, this); emit dataChanged(groupIndex, groupIndex); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } else { //whole new label level added to group item auto groupIndex = GetIndexByItem(groupItem, this); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } } else { if (instanceItem->ParentItem()->m_childItems.size() < 3) { //second instance item was added, so label item will now able to collapse // -> the whole label node has to be updated. auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); emit dataChanged(labelIndex, labelIndex); this->beginInsertRows(labelIndex, 0, instanceItem->ParentItem()->m_childItems.size()-1); this->endInsertRows(); } else { // instance item was added to existing label item with multiple instances //-> just notify the row insertion auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); this->endInsertRows(); } } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) { mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel received a LabelModified signal for a label that is not represented in the model. Invalid label: " << labelValue; } auto labelItem = instanceItem->ParentItem(); if (labelItem->m_ClassName == instanceItem->GetLabel()->GetName()) { //only the state of the label changed, but not its position in the model tree. auto index = labelItem->HandleAsInstance() ? GetIndexByItem(labelItem, this) : GetIndexByItem(instanceItem, this); auto rightIndex = index.sibling(index.row(), this->columnCount() - 1); emit dataChanged(index, rightIndex); } else { //the name of the label changed and thus its place in the model tree, delete the current item and add a new one this->OnLabelRemoved(labelValue); this->OnLabelAdded(labelValue); } } void QmitkMultiLabelTreeModel::OnLabelRemoved(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel received a LabelRemoved signal for a label that is not represented in the model. Invalid label: " << labelValue; auto labelItem = instanceItem->ParentItem(); if (labelItem->m_childItems.size() > 2) { auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); } else if (labelItem->m_childItems.size() == 2) { //After removal only one label is left -> the whole label node is about to be changed (no instances are shown any more). auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); emit dataChanged(labelIndex, labelIndex); } else { //was the only instance of the label, therefor also remove the label node from the tree. auto groupItem = labelItem->ParentItem(); auto groupIndex = GetIndexByItem(groupItem, this); this->beginRemoveRows(groupIndex, labelItem->Row(), labelItem->Row()); groupItem->RemoveChild(labelItem->Row()); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::OnGroupAdded(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginInsertRows(QModelIndex(), groupIndex, groupIndex); auto rootItem = m_RootItem.get(); auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); this->GenerateInternalGroupTree(groupIndex, groupItem); this->endInsertRows(); } } void QmitkMultiLabelTreeModel::OnGroupModified(GroupIndexType /*groupIndex*/) { //currently not needed } void QmitkMultiLabelTreeModel::OnGroupRemoved(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginRemoveRows(QModelIndex(), groupIndex, groupIndex); auto root = m_RootItem.get(); root->RemoveChild(groupIndex); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::SetAllowVisibilityModification(bool vmod) { m_AllowVisibilityModification = vmod; } bool QmitkMultiLabelTreeModel::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelTreeModel::SetAllowLockModification(bool lmod) { m_AllowLockModification = lmod; } bool QmitkMultiLabelTreeModel::GetAllowLockModification() const { return m_AllowLockModification; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.cpp index cb51f1c1a5..608d084b78 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.cpp @@ -1,32 +1,51 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include -#include -#include +#include QmitkMultiLabelTreeView::QmitkMultiLabelTreeView(QWidget* parent) : QTreeView(parent) { } QItemSelectionModel::SelectionFlags QmitkMultiLabelTreeView::selectionCommand(const QModelIndex& index, const QEvent* event) const { auto value = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (index.column()!=0 || !value.isValid()) { return QItemSelectionModel::NoUpdate; } return QAbstractItemView::selectionCommand(index, event); } + +void QmitkMultiLabelTreeView::leaveEvent(QEvent* event) +{ + QTreeView::leaveEvent(event); + emit MouseLeave(); +} + +void QmitkMultiLabelTreeView::mouseMoveEvent(QMouseEvent* event) +{ + QModelIndex index = this->indexAt(event->pos()); + if (!index.isValid()) + { + if (viewport()->rect().contains(event->pos())) + { + emit MouseLeave(); + } + } + + QTreeView::mouseMoveEvent(event); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.h index ec64a35fa2..7aa5995881 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeView.h @@ -1,34 +1,40 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkMultiLabelTreeView_h #define QmitkMultiLabelTreeView_h #include #include /* * @brief This is an inspector that offers a simple list view on a data storage. */ class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelTreeView : public QTreeView { Q_OBJECT public: QmitkMultiLabelTreeView(QWidget* parent = nullptr); +Q_SIGNALS: + void MouseLeave() const; + protected: QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex& index, const QEvent* event = nullptr) const override; + + void leaveEvent(QEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp index e199373c53..dcfb2bc540 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp @@ -1,143 +1,154 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkOtsuTool3DGUI.h" #include "mitkOtsuTool3D.h" #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkOtsuTool3DGUI, "") QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { auto enableMLSelectedDelegate = [this](bool enabled) { if (this->m_FirstPreviewComputation) { return false; } else { return this->m_SuperclassEnableConfirmSegBtnFnc(enabled); } }; m_EnableConfirmSegBtnFnc = enableMLSelectedDelegate; } void QmitkOtsuTool3DGUI::ConnectNewTool(mitk::SegWithPreviewTool* newTool) { Superclass::ConnectNewTool(newTool); newTool->IsTimePointChangeAwareOff(); m_FirstPreviewComputation = true; } void QmitkOtsuTool3DGUI::InitializeUI(QBoxLayout* mainLayout) { m_Controls.setupUi(this); mainLayout->addLayout(m_Controls.verticalLayout); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.m_Spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnRegionSpinboxChanged(int))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); this->OnAdvancedSettingsButtonToggled(false); Superclass::InitializeUI(mainLayout); } void QmitkOtsuTool3DGUI::OnRegionSpinboxChanged(int numberOfRegions) { // we have to change to minimum number of histogram bins accordingly int curBinValue = m_Controls.m_BinsSpinBox->value(); if (curBinValue < numberOfRegions) m_Controls.m_BinsSpinBox->setValue(numberOfRegions); } void QmitkOtsuTool3DGUI::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.m_ValleyCheckbox->setVisible(toggled); m_Controls.binLabel->setVisible(toggled); m_Controls.m_BinsSpinBox->setVisible(toggled); auto tool = this->GetConnectedToolAs(); if (toggled && nullptr != tool) { int max = tool->GetMaxNumberOfBins(); if (max >= m_Controls.m_BinsSpinBox->minimum()) { m_Controls.m_BinsSpinBox->setMaximum(max); } } } void QmitkOtsuTool3DGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { if (!m_FirstPreviewComputation && (tool->GetNumberOfRegions() == static_cast(m_Controls.m_Spinbox->value()) && tool->GetUseValley() == m_Controls.m_ValleyCheckbox->isChecked() && tool->GetNumberOfBins() == static_cast(m_Controls.m_BinsSpinBox->value()))) return; m_FirstPreviewComputation = false; try { int proceed; QMessageBox *messageBox = new QMessageBox(QMessageBox::Question, nullptr, "The otsu segmentation computation may take several minutes depending " "on the number of Regions you selected. Proceed anyway?", QMessageBox::Ok | QMessageBox::Cancel); if (m_Controls.m_Spinbox->value() >= 5) { proceed = messageBox->exec(); if (proceed != QMessageBox::Ok) return; } tool->SetNumberOfRegions(static_cast(m_Controls.m_Spinbox->value())); tool->SetUseValley(m_Controls.m_ValleyCheckbox->isChecked()); tool->SetNumberOfBins(static_cast(m_Controls.m_BinsSpinBox->value())); tool->UpdatePreview(); } + catch (const std::exception& e) + { + this->setCursor(Qt::ArrowCursor); + QMessageBox* messageBox = + new QMessageBox(QMessageBox::Critical, + nullptr, + e.what()); + messageBox->exec(); + delete messageBox; + return; + } catch (...) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(QMessageBox::Critical, nullptr, - "itkOtsuFilter error: image dimension must be in {2, 3} and no RGB images can be handled."); + "Unkown error while computing Otsu preview."); messageBox->exec(); delete messageBox; return; } this->SetLabelSetPreview(tool->GetPreviewSegmentation()); tool->IsTimePointChangeAwareOn(); this->ActualizePreviewLabelVisibility(); } } void QmitkOtsuTool3DGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); m_Controls.m_ValleyCheckbox->setEnabled(enabled); m_Controls.binLabel->setEnabled(enabled); m_Controls.m_BinsSpinBox->setEnabled(enabled); m_Controls.previewButton->setEnabled(enabled); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp index bd18ca218b..7094b974ab 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp @@ -1,164 +1,165 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSimpleLabelSetListWidget.h" #include "mitkMessage.h" #include QmitkSimpleLabelSetListWidget::QmitkSimpleLabelSetListWidget(QWidget* parent) : QWidget(parent), m_LabelList(nullptr), m_Emmiting(false) { QGridLayout* layout = new QGridLayout(this); this->setContentsMargins(0, 0, 0, 0); m_LabelList = new QListWidget(this); m_LabelList->setSelectionMode(QAbstractItemView::MultiSelection); m_LabelList->setResizeMode(QListView::Adjust); m_LabelList->setAutoScrollMargin(0); layout->addWidget(m_LabelList); connect(m_LabelList, SIGNAL(itemSelectionChanged()), this, SLOT(OnLabelSelectionChanged())); } QmitkSimpleLabelSetListWidget::~QmitkSimpleLabelSetListWidget() { if (m_LabelSetImage.IsNotNull()) { m_LabelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); } } QmitkSimpleLabelSetListWidget::LabelVectorType QmitkSimpleLabelSetListWidget::SelectedLabels() const { auto selectedItems = m_LabelList->selectedItems(); LabelVectorType result; QList::Iterator it; for (it = selectedItems.begin(); it != selectedItems.end(); ++it) { auto labelValue = (*it)->data(Qt::UserRole).toUInt(); result.push_back(m_LabelSetImage->GetLabel(labelValue)); } return result; } const mitk::LabelSetImage* QmitkSimpleLabelSetListWidget::GetLabelSetImage() const { return m_LabelSetImage; } void QmitkSimpleLabelSetListWidget::SetLabelSetImage(const mitk::LabelSetImage* image) { if (image != m_LabelSetImage) { m_LabelAddedObserver.Reset(); m_LabelModifiedObserver.Reset(); m_LabelRemovedObserver.Reset(); m_LabelSetImage = image; if (m_LabelSetImage.IsNotNull()) { auto& widget = *this; m_LabelAddedObserver.Reset(m_LabelSetImage, mitk::LabelAddedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelChanged(labelEvent->GetLabelValue()); }); m_LabelModifiedObserver.Reset(m_LabelSetImage, mitk::LabelModifiedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelChanged(labelEvent->GetLabelValue()); }); m_LabelRemovedObserver.Reset(m_LabelSetImage, mitk::LabelRemovedEvent(), [&widget](const itk::EventObject& event) { auto labelEvent = dynamic_cast(&event); widget.OnLabelChanged(labelEvent->GetLabelValue()); }); m_LabelSetImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); } } } void QmitkSimpleLabelSetListWidget::OnLayerChanged() { if (!this->m_Emmiting) { this->ResetList(); this->m_Emmiting = true; emit ActiveLayerChanged(); emit SelectedLabelsChanged(this->SelectedLabels()); this->m_Emmiting = false; } } void QmitkSimpleLabelSetListWidget::OnLabelChanged(mitk::LabelSetImage::LabelValueType lv) { - if (!this->m_Emmiting && m_LabelSetImage->GetGroupIndexOfLabel(lv)==m_LabelSetImage->GetActiveLayer()) + if (!this->m_Emmiting + && (!m_LabelSetImage->ExistLabel(lv) || m_LabelSetImage->GetGroupIndexOfLabel(lv)==m_LabelSetImage->GetActiveLayer())) { this->ResetList(); this->m_Emmiting = true; emit ActiveLayerChanged(); emit SelectedLabelsChanged(this->SelectedLabels()); this->m_Emmiting = false; } } void QmitkSimpleLabelSetListWidget::OnLabelSelectionChanged() { if (!this->m_Emmiting) { this->m_Emmiting = true; emit SelectedLabelsChanged(this->SelectedLabels()); this->m_Emmiting = false; } } void QmitkSimpleLabelSetListWidget::ResetList() { m_LabelList->clear(); auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labels = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(activeLayerID)); for (auto& label : labels) { auto color = label->GetColor(); QPixmap pixmap(10, 10); pixmap.fill(QColor(color[0] * 255, color[1] * 255, color[2] * 255)); QIcon icon(pixmap); QListWidgetItem* item = new QListWidgetItem(icon, QString::fromStdString(label->GetName())); item->setData(Qt::UserRole, QVariant(label->GetValue())); m_LabelList->addItem(item); } } void QmitkSimpleLabelSetListWidget::SetSelectedLabels(const LabelVectorType& selectedLabels) { for (int i = 0; i < m_LabelList->count(); ++i) { QListWidgetItem* item = m_LabelList->item(i); auto labelValue = item->data(Qt::UserRole).toUInt(); auto finding = std::find_if(selectedLabels.begin(), selectedLabels.end(), [labelValue](const mitk::Label* label) {return label->GetValue() == labelValue; }); item->setSelected(finding != selectedLabels.end()); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index eeffaaf1f9..4881c9cfac 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1500 +1,1497 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSlicesInterpolator.h" #include "QmitkRenderWindow.h" #include "QmitkRenderWindowWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include #include "mitkToolManager.h" #include "mitkUndoController.h" #include #include #include #include #include #include #include #include #include #include #include #include // Includes for the merge operation #include "mitkImageToContourFilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { template itk::SmartPointer GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const QmitkSlicesInterpolator::ActionToSliceDimensionMapType QmitkSlicesInterpolator::CreateActionToSlicer(const QList& windows) { std::map actionToSliceDimension; for (auto* window : windows) { std::string windowName; auto renderWindowWidget = dynamic_cast(window->parentWidget()); if (renderWindowWidget) { windowName = renderWindowWidget->GetCornerAnnotationText(); } else { windowName = window->GetRenderer()->GetName(); } auto slicer = window->GetSliceNavigationController(); actionToSliceDimension[new QAction(QString::fromStdString(windowName), nullptr)] = slicer; } return actionToSliceDimension; } mitk::Image::Pointer ExtractSliceFromImage(mitk::Image* image, const mitk::PlaneGeometry * contourPlane, unsigned int timeStep) { vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(contourPlane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Update(); mitk::Image::Pointer slice = extractor->GetOutput(); return slice; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), m_Interpolator(mitk::SegmentationInterpolationController::New()), m_SurfaceInterpolator(mitk::SurfaceInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Initialized(false), m_LastSNC(nullptr), m_LastSliceIndex(0), m_2DInterpolationEnabled(false), m_3DInterpolationEnabled(false), m_CurrentActiveLabelValue(0), m_FirstRun(true) { m_GroupBoxEnableExclusiveInterpolationMode = new QGroupBox("Interpolation", this); QVBoxLayout *vboxLayout = new QVBoxLayout(m_GroupBoxEnableExclusiveInterpolationMode); - m_EdgeDetector = mitk::FeatureBasedEdgeDetectionFilter::New(); - m_PointScorer = mitk::PointCloudScoringFilter::New(); - m_CmbInterpolation = new QComboBox(m_GroupBoxEnableExclusiveInterpolationMode); m_CmbInterpolation->addItem("Disabled"); m_CmbInterpolation->addItem("2-Dimensional"); m_CmbInterpolation->addItem("3-Dimensional"); vboxLayout->addWidget(m_CmbInterpolation); m_BtnApply2D = new QPushButton("Confirm for single slice", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply2D); m_BtnApplyForAllSlices2D = new QPushButton("Confirm for all slices", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApplyForAllSlices2D); m_BtnApply3D = new QPushButton("Confirm", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply3D); m_BtnReinit3DInterpolation = new QPushButton("Reinit Interpolation", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnReinit3DInterpolation); m_ChkShowPositionNodes = new QCheckBox("Show Position Nodes", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_ChkShowPositionNodes); this->HideAllInterpolationControls(); connect(m_CmbInterpolation, SIGNAL(currentIndexChanged(int)), this, SLOT(OnInterpolationMethodChanged(int))); connect(m_BtnApply2D, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_BtnApplyForAllSlices2D, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); connect(m_BtnApply3D, SIGNAL(clicked()), this, SLOT(OnAccept3DInterpolationClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); auto command3 = itk::ReceptorMemberCommand::New(); command3->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationAborted); InterpolationAbortedObserverTag = m_Interpolator->AddObserver(itk::AbortEvent(), command3); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } void QmitkSlicesInterpolator::SetActiveLabelValue(mitk::LabelSetImage::LabelValueType labelValue) { bool changedValue = labelValue != this->m_CurrentActiveLabelValue; this->m_CurrentActiveLabelValue = labelValue; if (changedValue) this->OnActiveLabelChanged(labelValue); }; mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::InitializeWindow(QmitkRenderWindow* window) { auto slicer = window->GetSliceNavigationController(); if (slicer == nullptr) { MITK_WARN << "Tried setting up interpolation for a render window that does not have a slice navigation controller set"; return; } // Has to be initialized m_LastSNC = slicer; itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand); } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList& windows) { Q_ASSERT(!windows.empty()); if (m_Initialized) { // remove old observers this->Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag = timeNavigationController->AddObserver(mitk::TimeNavigationController::TimeEvent(0), timeChangedCommand); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); // connect to the slice navigation controller. after each change, call the interpolator for (auto* window : windows) { this->InitializeWindow(window); } m_ActionToSlicerMap = CreateActionToSlicer(windows); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); timeNavigationController->RemoveObserver(m_ControllerToTimeObserverTag); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } m_ActionToSlicerMap.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers this->Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); } // remove observer m_Interpolator->RemoveObserver(InterpolationAbortedObserverTag); m_Interpolator->RemoveObserver(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); m_SurfaceInterpolator->SetCurrentInterpolationSession(nullptr); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); m_ChkShowPositionNodes->setVisible(show); m_BtnReinit3DInterpolation->setVisible(show); } void QmitkSlicesInterpolator::OnInterpolationMethodChanged(int index) { switch (index) { case 0: // Disabled m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation"); this->HideAllInterpolationControls(); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(false); break; case 1: // 2D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show2DInterpolationControls(true); this->OnInterpolationActivated(true); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); m_CmbInterpolation->setCurrentIndex(0); return; } // Updating the current selected segmentation for the 3D interpolation this->SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast(&e)) { return; } const auto* timeNavigationController = dynamic_cast(sender); if (nullptr == timeNavigationController) { return; } bool timeChanged = m_TimePoint != timeNavigationController->GetSelectedTimePoint(); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (timeChanged) { if (m_3DInterpolationEnabled) { m_InterpolatedSurfaceNode->SetData(nullptr); } m_SurfaceInterpolator->Modified(); } if (nullptr == m_LastSNC) { return; } if (TranslateAndInterpolateChangedSlice(m_LastSNC->GetCreatedWorldGeometry())) { m_LastSNC->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast(&e)) { return; } auto sliceNavigationController = dynamic_cast(sender); if (nullptr == sliceNavigationController) { return; } if(m_2DInterpolationEnabled) { this->On2DInterpolationEnabled(m_2DInterpolationEnabled); } if (TranslateAndInterpolateChangedSlice(e, sliceNavigationController)) { sliceNavigationController->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject& e, mitk::SliceNavigationController* sliceNavigationController) { const mitk::SliceNavigationController::GeometrySliceEvent* event = dynamic_cast(&e); mitk::TimeGeometry* timeGeometry = event->GetTimeGeometry(); m_LastSNC = sliceNavigationController; return this->TranslateAndInterpolateChangedSlice(timeGeometry); } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const mitk::TimeGeometry* timeGeometry) { if (!m_2DInterpolationEnabled) { return false; } if (nullptr == timeGeometry) { return false; } if (!timeGeometry->IsValidTimePoint(m_TimePoint)) { return false; } mitk::SlicedGeometry3D* slicedGeometry = dynamic_cast(timeGeometry->GetGeometryForTimePoint(m_TimePoint).GetPointer()); if (nullptr == slicedGeometry) { return false; } mitk::PlaneGeometry* plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(m_LastSNC->GetStepper()->GetPos())); if (nullptr == plane) { return false; } this->Interpolate(plane); return true; } void QmitkSlicesInterpolator::Interpolate(mitk::PlaneGeometry *plane) { if (nullptr == m_ToolManager) { return; } mitk::DataNode* node = m_ToolManager->GetWorkingData(0); if (nullptr == node) { return; } m_Segmentation = dynamic_cast(node->GetData()); if (nullptr == m_Segmentation) { return; } if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot interpolate WorkingImage. Passed time point is not within the time bounds of WorkingImage. " "Time point: " << m_TimePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); int clickedSliceDimension = -1; int clickedSliceIndex = -1; // calculate real slice position, i.e. slice of the image mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); // maybe just have a variable that stores the active label color. if (m_ToolManager) { auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { auto* activeLabel = dynamic_cast(workingNode->GetData())->GetActiveLabel(); if (nullptr != activeLabel) { auto activeColor = activeLabel->GetColor(); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } } } m_LastSliceIndex = clickedSliceIndex; } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode && workingNode->GetData()) { const auto segmentation = dynamic_cast(workingNode->GetData()); if (segmentation == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; return; } mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(segmentation, m_CurrentActiveLabelValue, segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); if (interpolatedSurface.IsNotNull()) { m_BtnApply3D->setEnabled(true);; m_InterpolatedSurfaceNode->SetData(interpolatedSurface); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } } else { m_BtnApply3D->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } } m_BtnReinit3DInterpolation->setEnabled(true); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { auto* workingNode = m_ToolManager->GetWorkingData(0); auto* planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); auto* interpolatedPreview = dynamic_cast(m_FeedbackNode->GetData()); if (nullptr == workingNode || nullptr == interpolatedPreview) return; auto* segmentationImage = dynamic_cast(workingNode->GetData()); if (nullptr == segmentationImage) return; if (!segmentationImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the " "time bounds of segmentation. Time point: " << m_TimePoint; return; } const auto timeStep = segmentationImage->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); auto interpolatedSlice = mitk::SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, segmentationImage, timeStep)->Clone(); auto activeValue = segmentationImage->GetActiveLabel()->GetValue(); mitk::TransferLabelContentAtTimeStep( interpolatedPreview, interpolatedSlice, segmentationImage->GetConstLabelsByValue(segmentationImage->GetLabelValuesByGroup(segmentationImage->GetActiveLayer())), timeStep, 0, mitk::LabelSetImage::UNLABELED_VALUE, false, { {0, mitk::LabelSetImage::UNLABELED_VALUE}, {1, activeValue} } ); mitk::SegTool2D::WriteBackSegmentationResult(workingNode, planeGeometry, interpolatedSlice, timeStep); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { mitk::Image::Pointer segmentation3D = m_Segmentation; unsigned int timeStep = 0; if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not " "within the time bounds of segmentation. Time point: " << m_TimePoint; return; } mitk::Image::Pointer activeLabelImage; try { auto labelSetImage = dynamic_cast(m_Segmentation); activeLabelImage = mitk::CreateLabelMask(labelSetImage, labelSetImage->GetActiveLabel()->GetValue()); } catch (const std::exception& e) { MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; } m_Interpolator->SetSegmentationVolume(activeLabelImage); timeStep = geometry->TimePointToTimeStep(m_TimePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // Create an empty diff image for the undo operation auto diffImage = mitk::Image::New(); diffImage->Initialize(segmentation3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::IOComponentEnum::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension = -1; int sliceIndex = -1; mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); const auto numSlices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); std::atomic_uint totalChangedSlices; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); std::vector> sliceIndices(numThreads); for (std::remove_const_t sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); std::vector threads; threads.reserve(numThreads); // This lambda will be executed by the threads auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { auto clonedPlaneGeometry = planeGeometry->Clone(); auto origin = clonedPlaneGeometry->GetOrigin(); // Go through the sliced indices for (auto sliceIndex : sliceIndices[threadIndex]) { slicedGeometry->WorldToIndex(origin, origin); origin[sliceDimension] = sliceIndex; slicedGeometry->IndexToWorld(origin, origin); clonedPlaneGeometry->SetOrigin(origin); auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); if (interpolation.IsNotNull()) { // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume auto reslicer = vtkSmartPointer::New(); // Set overwrite mode to true to write back to the image volume reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); reslicer->SetOverwriteMode(true); reslicer->Modified(); auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); diffSliceWriter->SetInput(diffImage); diffSliceWriter->SetTimeStep(0); diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); diffSliceWriter->SetVtkOutputRequest(true); diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); diffSliceWriter->Modified(); diffSliceWriter->Update(); ++totalChangedSlices; } mitk::ProgressBar::GetInstance()->Progress(); } }; m_Interpolator->EnableSliceImageCache(); // Do the interpolation here. for (size_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) { interpolate(threadIndex); } m_Interpolator->DisableSliceImageCache(); const mitk::Label::PixelType newDestinationLabel = dynamic_cast(m_Segmentation)->GetActiveLabel()->GetValue(); // Do and Undo Operations if (totalChangedSlices > 0) { // Create do/undo operations auto* doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); auto* undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); undoOp->SetFactor(-1.0); auto comment = "Confirm all interpolations (" + std::to_string(totalChangedSlices) + ")"; auto* undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment); mitk::OperationEvent::IncCurrGroupEventId(); mitk::OperationEvent::IncCurrObjectEventId(); mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); mitk::DiffImageApplier::GetInstanceForUndo()->SetDestinationLabel(newDestinationLabel); // Apply the changes to the original image mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); for (auto it = m_ActionToSlicerMap.begin(); it != m_ActionToSlicerMap.end(); ++it) { orientationPopup.addAction(it->first); } connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto labelSetImage = dynamic_cast(segmentationDataNode->GetData()); auto activeLabelColor = labelSetImage->GetActiveLabel()->GetColor(); std::string activeLabelName = labelSetImage->GetActiveLabel()->GetName(); auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint) || !segmentationGeometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::IOComponentEnum::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); auto timeStep = segmentationGeometry->TimePointToTimeStep(m_TimePoint); const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabel()->GetValue(); TransferLabelContentAtTimeStep( interpolatedSegmentation, labelSetImage, labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(labelSetImage->GetActiveLayer())), timeStep, 0, 0, false, {{1, newDestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + " 3D-interpolation - " + activeLabelName; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); interpolatedSurfaceDataNode->SetColor(activeLabelColor); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { // Step 1. Load from the isContourPlaneGeometry nodes the contourNodes. mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } mitk::SurfaceInterpolationController::CPIVector newCPIs; // Adding label and timeStep information for the contourNodes. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto contourNode = it->Value(); auto labelID = dynamic_cast(contourNode->GetProperty("labelID"))->GetValue(); auto timeStep = dynamic_cast(contourNode->GetProperty("timeStep"))->GetValue(); auto planeGeometry = dynamic_cast(contourNode->GetData())->GetPlaneGeometry(); auto groupID = labelSetImage->GetGroupIndexOfLabel(labelID); auto sliceImage = ExtractSliceFromImage(labelSetImage->GetGroupImage(groupID), planeGeometry, timeStep); mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); contourExtractor->SetInput(sliceImage); contourExtractor->SetContourValue(labelID); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) continue; contour->DisconnectPipeline(); newCPIs.emplace_back(contour, planeGeometry->Clone(),labelID,timeStep); } m_SurfaceInterpolator->CompleteReinitialization(newCPIs); } catch(const std::exception& e) { MITK_ERROR << "Exception thrown casting toolmanager working data to labelsetImage"; } } } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { auto iter = m_ActionToSlicerMap.find(action); if (iter != m_ActionToSlicerMap.end()) { mitk::SliceNavigationController *slicer = iter->second; this->AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); std::cerr << "Ill construction in " __FILE__ " l. " << __LINE__ << std::endl; } } void QmitkSlicesInterpolator::OnInterpolationActivated(bool on) { m_2DInterpolationEnabled = on; try { if (m_DataStorage.IsNotNull()) { if (on && !m_DataStorage->Exists(m_FeedbackNode)) { m_DataStorage->Add(m_FeedbackNode); } } } catch (...) { // don't care (double add/remove) } if (m_ToolManager) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); QWidget::setEnabled(workingNode != nullptr); m_BtnApply2D->setEnabled(on); m_FeedbackNode->SetVisibility(on); if (!on) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } if (workingNode) { auto labelSetImage = dynamic_cast(workingNode->GetData()); if (nullptr == labelSetImage) { MITK_ERROR << "NO LABELSETIMAGE IN WORKING NODE\n"; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } const auto* activeLabel = labelSetImage->GetActiveLabel(); const auto* segmentation = dynamic_cast(workingNode->GetData()); if (nullptr != activeLabel && nullptr != segmentation) { auto activeLabelImage = mitk::CreateLabelMask(labelSetImage, activeLabel->GetValue()); m_Interpolator->SetSegmentationVolume(activeLabelImage); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { auto workingNode = m_ToolManager->GetWorkingData(0); if (workingNode == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no working data set."; return; } const auto segmentation = dynamic_cast(workingNode->GetData()); if (segmentation == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; return; } if (!segmentation->ExistLabel(m_CurrentActiveLabelValue)) { MITK_ERROR << "Run3DInterpolation triggered with no valid label selected. Currently selected invalid label: "<Interpolate(segmentation,m_CurrentActiveLabelValue,segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { if(m_ToolManager) { const auto* workingNode = m_ToolManager->GetWorkingData(0); const auto activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabel()->GetColor(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } m_Timer->stop(); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_3DWINDOWS); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; try { // this->PrepareInputsFor3DInterpolation(); m_SurfaceInterpolator->Modified(); } catch (...) { MITK_ERROR << "Error with 3D surface interpolation!"; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::EnableInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated OnInterpolationActivated(on); } void QmitkSlicesInterpolator::Enable3DInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated this->On3DInterpolationActivated(on); } void QmitkSlicesInterpolator::UpdateVisibleSuggestion() { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::OnInterpolationAborted(const itk::EventObject& /*e*/) { m_CmbInterpolation->setCurrentIndex(0); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { auto workingNode = m_ToolManager->GetWorkingData(0); if (workingNode == nullptr) { MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no working data set."; return; } const auto segmentation = dynamic_cast(workingNode->GetData()); if (segmentation == nullptr) { MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no MultiLabelSegmentation as working data."; return; } if (!segmentation->ExistLabel(m_CurrentActiveLabelValue)) { MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no valid label selected. Currently selected invalid label: " << m_CurrentActiveLabelValue; return; } if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_InterpolatedSurfaceNode->SetData(nullptr); m_Future = QtConcurrent::run(&QmitkSlicesInterpolator::Run3DInterpolation, this); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { QWidget::setEnabled(true); if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << m_TimePoint; return; } m_SurfaceInterpolator->SetDistanceImageVolume(50000); auto segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnActiveLabelChanged(mitk::Label::PixelType) { m_FeedbackNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_SurfaceInterpolator->Modified(); } if (m_2DInterpolationEnabled) { m_FeedbackNode->SetData(nullptr); this->OnInterpolationActivated(true); m_LastSNC->SendSlice(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (m_3DInterpolationEnabled && m_Segmentation && ((m_Segmentation->GetDimension() != 3) || (m_Segmentation->GetDimension() != 4)) ) { QMessageBox info; info.setWindowTitle("3D Interpolation Process"); info.setIcon(QMessageBox::Information); info.setText("3D Interpolation is only supported for 3D/4D images at the moment!"); info.exec(); m_CmbInterpolation->setCurrentIndex(0); } } } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSlicesInterpolator::WaitForFutures() { if (m_Watcher.isRunning()) { m_Watcher.waitForFinished(); } if (m_PlaneWatcher.isRunning()) { m_PlaneWatcher.waitForFinished(); } } void QmitkSlicesInterpolator::NodeRemoved(const mitk::DataNode* node) { if ((m_ToolManager && m_ToolManager->GetWorkingData(0) == node) || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h index 3960e2888c..88b645b99b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h @@ -1,344 +1,338 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSlicesInterpolator_h #define QmitkSlicesInterpolator_h #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkSegmentationInterpolationController.h" #include "mitkSurfaceInterpolationController.h" #include "mitkToolManager.h" #include -#include "mitkFeatureBasedEdgeDetectionFilter.h" -#include "mitkPointCloudScoringFilter.h" - #include #include #include #include #include #include #include #include "mitkVtkRepresentationProperty.h" #include "vtkProperty.h" // For running 3D interpolation in background #include #include #include #include namespace mitk { class PlaneGeometry; class SliceNavigationController; class TimeNavigationController; } class QPushButton; class QmitkRenderWindow; /** \brief GUI for slices interpolation. \ingroup ToolManagerEtAl \ingroup Widgets \sa QmitkInteractiveSegmentation \sa mitk::SegmentationInterpolation While mitk::SegmentationInterpolation does the bookkeeping of interpolation (keeping track of which slices contain how much segmentation) and the algorithmic work, QmitkSlicesInterpolator is responsible to watch the GUI, to notice, which slice is currently visible. It triggers generation of interpolation suggestions and also triggers acception of suggestions. */ class MITKSEGMENTATIONUI_EXPORT QmitkSlicesInterpolator : public QWidget { Q_OBJECT public: QmitkSlicesInterpolator(QWidget *parent = nullptr, const char *name = nullptr); /** To be called once before real use. */ void Initialize(mitk::ToolManager *toolManager, const QList& windows); /** * @brief * */ void Uninitialize(); ~QmitkSlicesInterpolator() override; /** * @brief Set the Data Storage object * * @param storage */ void SetDataStorage(mitk::DataStorage::Pointer storage); /** * @brief Get the Data Storage object * * @return mitk::DataStorage* */ mitk::DataStorage *GetDataStorage(); void SetActiveLabelValue(mitk::LabelSetImage::LabelValueType labelValue); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerWorkingDataModified(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerReferenceDataModified(); /** * @brief Reacts to the time changed event. * * @param sender */ void OnTimeChanged(itk::Object *sender, const itk::EventObject &); /** * @brief Reacts to the slice changed event * * @param sender */ void OnSliceChanged(itk::Object *sender, const itk::EventObject &); void OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationInfoChanged(const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationAborted(const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnSurfaceInterpolationInfoChanged(const itk::EventObject &); private: /** * @brief Set the visibility of the 3d interpolation */ void Show3DInterpolationResult(bool); /** * @brief Function that reacts to a change in the activeLabel of the working segmentation image. * */ void OnActiveLabelChanged(mitk::Label::PixelType); signals: void SignalRememberContourPositions(bool); void SignalShowMarkerNodes(bool); public slots: virtual void setEnabled(bool); /** Call this from the outside to enable/disable interpolation */ void EnableInterpolation(bool); void Enable3DInterpolation(bool); /** Call this from the outside to accept all interpolations */ void FinishInterpolation(mitk::SliceNavigationController *slicer = nullptr); protected slots: /** Reaction to button clicks. */ void OnAcceptInterpolationClicked(); /* Opens popup to ask about which orientation should be interpolated */ void OnAcceptAllInterpolationsClicked(); /* Reaction to button clicks */ void OnAccept3DInterpolationClicked(); /** * @brief Reaction to reinit 3D Interpolation. Re-reads the plane geometries of the image * that should have generated the * */ void OnReinit3DInterpolation(); /* * Will trigger interpolation for all slices in given orientation (called from popup menu of * OnAcceptAllInterpolationsClicked) */ void OnAcceptAllPopupActivated(QAction *action); /** Called on activation/deactivation */ void OnInterpolationActivated(bool); void On3DInterpolationActivated(bool); void OnInterpolationMethodChanged(int index); // Enhancement for 3D interpolation void On2DInterpolationEnabled(bool); void On3DInterpolationEnabled(bool); void OnInterpolationDisabled(bool); void OnShowMarkers(bool); void Run3DInterpolation(); /** * @brief Function triggers when the surface interpolation thread completes running. * It is responsible for retrieving the data, rendering it in the active color label, * storing the surface information in the feedback node. * */ void OnSurfaceInterpolationFinished(); void StartUpdateInterpolationTimer(); void StopUpdateInterpolationTimer(); void ChangeSurfaceColor(); protected: typedef std::map ActionToSliceDimensionMapType; const ActionToSliceDimensionMapType CreateActionToSlicer(const QList& windows); ActionToSliceDimensionMapType m_ActionToSlicerMap; void AcceptAllInterpolations(mitk::SliceNavigationController *slicer); /** Retrieves the currently selected PlaneGeometry from a SlicedGeometry3D that is generated by a SliceNavigationController and calls Interpolate to further process this PlaneGeometry into an interpolation. \param e is a actually a mitk::SliceNavigationController::GeometrySliceEvent, sent by a SliceNavigationController \param sliceNavigationController the SliceNavigationController */ bool TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *sliceNavigationController); bool TranslateAndInterpolateChangedSlice(const mitk::TimeGeometry* timeGeometry); /** Given a PlaneGeometry, this method figures out which slice of the first working image (of the associated ToolManager) should be interpolated. The actual work is then done by our SegmentationInterpolation object. */ void Interpolate(mitk::PlaneGeometry *plane); /** Called internally to update the interpolation suggestion. Finds out about the focused render window and requests an interpolation. */ void UpdateVisibleSuggestion(); void SetCurrentContourListID(); private: void InitializeWindow(QmitkRenderWindow* window); void HideAllInterpolationControls(); void Show2DInterpolationControls(bool show); void Show3DInterpolationControls(bool show); void CheckSupportedImageDimension(); void WaitForFutures(); void NodeRemoved(const mitk::DataNode* node); mitk::SegmentationInterpolationController::Pointer m_Interpolator; mitk::SurfaceInterpolationController::Pointer m_SurfaceInterpolator; - mitk::FeatureBasedEdgeDetectionFilter::Pointer m_EdgeDetector; - mitk::PointCloudScoringFilter::Pointer m_PointScorer; - mitk::ToolManager::Pointer m_ToolManager; bool m_Initialized; unsigned int m_ControllerToTimeObserverTag; QHash m_ControllerToSliceObserverTag; QHash m_ControllerToDeleteObserverTag; unsigned int InterpolationInfoChangedObserverTag; unsigned int SurfaceInterpolationInfoChangedObserverTag; unsigned int InterpolationAbortedObserverTag; QGroupBox *m_GroupBoxEnableExclusiveInterpolationMode; QComboBox *m_CmbInterpolation; QPushButton *m_BtnApply2D; QPushButton *m_BtnApplyForAllSlices2D; QPushButton *m_BtnApply3D; QCheckBox *m_ChkShowPositionNodes; QPushButton *m_BtnReinit3DInterpolation; mitk::DataNode::Pointer m_FeedbackNode; mitk::DataNode::Pointer m_InterpolatedSurfaceNode; mitk::Image *m_Segmentation; mitk::SliceNavigationController *m_LastSNC; unsigned int m_LastSliceIndex; mitk::TimePointType m_TimePoint; bool m_2DInterpolationEnabled; bool m_3DInterpolationEnabled; unsigned int m_numTimesLabelSetConnectionAdded; mitk::DataStorage::Pointer m_DataStorage; QFuture m_Future; QFutureWatcher m_Watcher; QFuture m_ModifyFuture; QFutureWatcher m_ModifyWatcher; QTimer *m_Timer; QFuture m_PlaneFuture; QFutureWatcher m_PlaneWatcher; mitk::Label::PixelType m_CurrentActiveLabelValue; bool m_FirstRun; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp index 44b094d5dd..3557881382 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp @@ -1,499 +1,501 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkTotalSegmentatorToolGUI.h" #include "mitkProcessExecutor.h" #include "mitkTotalSegmentatorTool.h" #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkTotalSegmentatorToolGUI, "") QmitkTotalSegmentatorToolGUI::QmitkTotalSegmentatorToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { // Nvidia-smi command returning zero doesn't always imply lack of GPUs. // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. if (m_GpuLoader.GetGPUCount() == 0) { std::string warning = "WARNING: No GPUs were detected on your machine. The TotalSegmentator tool can be very slow."; this->ShowErrorMessage(warning); } m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; } void QmitkTotalSegmentatorToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); m_FirstPreviewComputation = true; } void QmitkTotalSegmentatorToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); #ifndef _WIN32 m_Controls.sysPythonComboBox->addItem("/usr/bin"); #endif this->AutoParsePythonPaths(); m_Controls.sysPythonComboBox->addItem("Select"); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->addItem("Select"); m_Controls.pythonEnvComboBox->setDuplicatesEnabled(false); m_Controls.pythonEnvComboBox->setDisabled(true); m_Controls.previewButton->setDisabled(true); m_Controls.statusLabel->setTextFormat(Qt::RichText); m_Controls.subtaskComboBox->addItems(VALID_TASKS); QString welcomeText; this->SetGPUInfo(); if (m_GpuLoader.GetGPUCount() != 0) { welcomeText = "STATUS: Welcome to TotalSegmentator tool. You're in luck: " + QString::number(m_GpuLoader.GetGPUCount()) + " GPU(s) were detected."; } else { welcomeText = "STATUS: Welcome to TotalSegmentator tool. Sorry, " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected."; } connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.clearButton, SIGNAL(clicked()), this, SLOT(OnClearInstall())); connect(m_Controls.installButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked())); connect(m_Controls.overrideBox, SIGNAL(stateChanged(int)), this, SLOT(OnOverrideChecked(int))); connect(m_Controls.pythonEnvComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnPythonPathChanged(m_Controls.pythonEnvComboBox->itemText(index)); }); connect(m_Controls.sysPythonComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnSystemPythonChanged(m_Controls.sysPythonComboBox->itemText(index)); }); QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (!lastSelectedPyEnv.isEmpty() && lastSelectedPyEnv!= "Select") { m_Controls.pythonEnvComboBox->insertItem(0, lastSelectedPyEnv); } m_Controls.fastBox->setChecked(m_Settings.value("TotalSeg/LastFast").toBool()); const QString storageDir = m_Installer.GetVirtualEnvPath(); m_IsInstalled = this->IsTotalSegmentatorInstalled(storageDir); if (m_IsInstalled) { m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(storageDir).first; m_Installer.SetVirtualEnvPath(m_PythonPath); this->EnableAll(m_IsInstalled); welcomeText += " TotalSegmentator is already found installed."; } else { welcomeText += " TotalSegmentator is not installed. Please click on \"Install TotalSegmentator\" above."; } this->WriteStatusMessage(welcomeText); QIcon deleteIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/edit-delete.svg")); QIcon arrowIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Controls.clearButton->setIcon(deleteIcon); m_Controls.previewButton->setIcon(arrowIcon); mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); } void QmitkTotalSegmentatorToolGUI::SetGPUInfo() { std::vector specs = m_GpuLoader.GetAllGPUSpecs(); for (const QmitkGPUSpec &gpuSpec : specs) { m_Controls.gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); } if (specs.empty()) { m_Controls.gpuComboBox->setEditable(true); m_Controls.gpuComboBox->addItem(QString::number(0)); m_Controls.gpuComboBox->setValidator(new QIntValidator(0, 999, this)); } } unsigned int QmitkTotalSegmentatorToolGUI::FetchSelectedGPUFromUI() const { QString gpuInfo = m_Controls.gpuComboBox->currentText(); if (m_GpuLoader.GetGPUCount() == 0) { return static_cast(gpuInfo.toInt()); } else { QString gpuId = gpuInfo.split(":", Qt::SkipEmptyParts).first(); return static_cast(gpuId.toInt()); } } void QmitkTotalSegmentatorToolGUI::EnableAll(bool isEnable) { m_Controls.previewButton->setEnabled(isEnable); m_Controls.subtaskComboBox->setEnabled(isEnable); m_Controls.installButton->setEnabled((!isEnable)); } void QmitkTotalSegmentatorToolGUI::OnInstallBtnClicked() { bool isInstalled = false; const auto [path, version] = OnSystemPythonChanged(m_Controls.sysPythonComboBox->currentText()); if (path.isEmpty()) { this->WriteErrorMessage("ERROR: Couldn't find compatible Python."); return; } // check if python 3.12 and ask for confirmation if (version.startsWith("3.12") && QMessageBox::No == QMessageBox::question( nullptr, "Installing TotalSegmentator", QString("WARNING: This is an unsupported version of Python that may not work. " "We recommend using a supported Python version between 3.9 and 3.11.\n\n" "Continue anyway?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { return; } this->WriteStatusMessage("STATUS: Installing TotalSegmentator..."); m_Installer.SetSystemPythonPath(path); isInstalled = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); if (isInstalled) { m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(m_Installer.GetVirtualEnvPath()).first; this->WriteStatusMessage("STATUS: Successfully installed TotalSegmentator."); } else { this->WriteErrorMessage("ERROR: Couldn't install TotalSegmentator."); } this->EnableAll(isInstalled); } void QmitkTotalSegmentatorToolGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } try { m_Controls.previewButton->setEnabled(false); qApp->processEvents(); if (!this->IsTotalSegmentatorInstalled(m_PythonPath)) { throw std::runtime_error(WARNING_TOTALSEG_NOT_FOUND); } bool isFast = m_Controls.fastBox->isChecked(); QString subTask = m_Controls.subtaskComboBox->currentText(); if (subTask != VALID_TASKS[0]) { isFast = true; } tool->SetPythonPath(m_PythonPath.toStdString()); tool->SetGpuId(FetchSelectedGPUFromUI()); tool->SetFast(isFast); tool->SetSubTask(subTask.toStdString()); this->WriteStatusMessage(QString("STATUS: Starting Segmentation task... This might take a while.")); + m_FirstPreviewComputation = false; tool->UpdatePreview(); m_Controls.previewButton->setEnabled(true); - m_FirstPreviewComputation = false; } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for TotalSegmentator segmentation. Reason: " << e.what(); this->ShowErrorMessage(errorMsg.str()); this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); m_Controls.previewButton->setEnabled(true); + m_FirstPreviewComputation = true; return; } catch (...) { std::string errorMsg = "Unkown error occured while generation TotalSegmentator segmentation."; this->ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); + m_FirstPreviewComputation = true; return; } this->SetLabelSetPreview(tool->GetPreviewSegmentation()); this->ActualizePreviewLabelVisibility(); this->WriteStatusMessage("STATUS: Segmentation task finished successfully."); QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); if (!pythonPathTextItem.isEmpty() && pythonPathTextItem != "Select") // only cache if the prediction ended without errors. { QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (lastSelectedPyEnv != pythonPathTextItem) { m_Settings.setValue("TotalSeg/LastCustomPythonPath", pythonPathTextItem); } } m_Settings.setValue("TotalSeg/LastFast", m_Controls.fastBox->isChecked()); } void QmitkTotalSegmentatorToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); messageBox->exec(); delete messageBox; MITK_WARN << message; } void QmitkTotalSegmentatorToolGUI::WriteStatusMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); qApp->processEvents(); } void QmitkTotalSegmentatorToolGUI::WriteErrorMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); qApp->processEvents(); } bool QmitkTotalSegmentatorToolGUI::IsTotalSegmentatorInstalled(const QString &pythonPath) { QString fullPath = pythonPath; bool isPythonExists = false, isExists = false; #ifdef _WIN32 isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python.exe")) : isPythonExists; } isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator.exe")) && isPythonExists; #else isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python3")) : isPythonExists; } isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator")) && isPythonExists; #endif return isExists; } void QmitkTotalSegmentatorToolGUI::AutoParsePythonPaths() { QString homeDir = QDir::homePath(); std::vector searchDirs; #ifdef _WIN32 searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + QString("anaconda3")); #else // Add search locations for possible standard python paths here searchDirs.push_back(homeDir + QDir::separator() + "environments"); searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); #endif for (QString searchDir : searchDirs) { if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) { if (QDir(searchDir).exists()) { m_Controls.sysPythonComboBox->addItem("(base): " + searchDir); searchDir.append((QDir::separator() + QString("envs"))); } } for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) { subIt.next(); QString envName = subIt.fileName(); if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. { m_Controls.pythonEnvComboBox->addItem("(" + envName + "): " + subIt.filePath()); } } } } std::pair QmitkTotalSegmentatorToolGUI::OnSystemPythonChanged(const QString &pyEnv) { std::pair pyPath; if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.sysPythonComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnSystemPythonChanged(path); // recall same function for new path validation bool oldState = m_Controls.sysPythonComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.sysPythonComboBox->insertItem(0, path); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.sysPythonComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else { QString uiPyPath = this->GetPythonPathFromUI(pyEnv); pyPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(uiPyPath); } return pyPath; } void QmitkTotalSegmentatorToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnPythonPathChanged(path); // recall same function for new path validation bool oldState = m_Controls.pythonEnvComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.pythonEnvComboBox->insertItem(0, path); m_Controls.pythonEnvComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else if (!this->IsTotalSegmentatorInstalled(this->GetPythonPathFromUI(pyEnv))) { this->ShowErrorMessage(WARNING_TOTALSEG_NOT_FOUND); m_Controls.previewButton->setDisabled(true); } else {// Show positive status meeage m_Controls.previewButton->setDisabled(false); QString uiPyPath = this->GetPythonPathFromUI(pyEnv); m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(uiPyPath).first; } } QString QmitkTotalSegmentatorToolGUI::GetPythonPathFromUI(const QString &pyUI) const { QString fullPath = pyUI; if (-1 != fullPath.indexOf(")")) { fullPath = fullPath.mid(fullPath.indexOf(")") + 2); } return fullPath.simplified(); } void QmitkTotalSegmentatorToolGUI::OnOverrideChecked(int state) { bool isEnabled = false; if (state == Qt::Checked) { isEnabled = true; m_Controls.previewButton->setDisabled(true); m_PythonPath.clear(); } else { m_PythonPath.clear(); m_Controls.previewButton->setDisabled(true); if (m_IsInstalled) { const QString pythonPath = m_Installer.GetVirtualEnvPath(); auto pathObject = QmitkSetupVirtualEnvUtil::GetExactPythonPath(pythonPath); m_PythonPath = pathObject.first; this->EnableAll(m_IsInstalled); } } m_Controls.pythonEnvComboBox->setEnabled(isEnabled); } void QmitkTotalSegmentatorToolGUI::OnClearInstall() { QDir folderPath(m_Installer.GetVirtualEnvPath()); if (folderPath.removeRecursively()) { m_Controls.installButton->setEnabled(true); m_IsInstalled = false; if (!m_Controls.overrideBox->isChecked()) { m_Controls.previewButton->setEnabled(false); } } else { MITK_ERROR << "The virtual environment couldn't be removed. Please check if you have the required access privileges or, some other process is accessing the folders."; } } bool QmitkTotalSegmentatorToolInstaller::SetupVirtualEnv(const QString& venvName) { if (GetSystemPythonPath().isEmpty()) { return false; } QDir folderPath(GetBaseDir()); folderPath.mkdir(venvName); if (!folderPath.cd(venvName)) { return false; // Check if directory creation was successful. } mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&PrintProcessEvent); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-m"); args.push_back("venv"); args.push_back(venvName.toStdString()); #ifdef _WIN32 QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python.exe"; QString pythonExeFolder = "Scripts"; #else QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python3"; QString pythonExeFolder = "bin"; #endif spExec->Execute(GetBaseDir().toStdString(), pythonFile.toStdString(), args); // Setup local virtual environment if (folderPath.cd(pythonExeFolder)) { this->SetPythonPath(folderPath.absolutePath()); this->SetPipPath(folderPath.absolutePath()); this->InstallPytorch(); for (auto &package : PACKAGES) { this->PipInstall(package.toStdString(), &PrintProcessEvent); } std::string pythonCode; // python syntax to check if torch is installed with CUDA. pythonCode.append("import torch;"); pythonCode.append("print('Pytorch was installed with CUDA') if torch.cuda.is_available() else print('PyTorch was " "installed WITHOUT CUDA');"); this->ExecutePython(pythonCode, &PrintProcessEvent); return true; } return false; } QString QmitkTotalSegmentatorToolInstaller::GetVirtualEnvPath() { return STORAGE_DIR + VENV_NAME; } diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp index b39262f9ad..d69d23064b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp @@ -1,1197 +1,1200 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitknnUNetToolGUI.h" #include "mitkProcessExecutor.h" #include "mitknnUnetTool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitknnUNetToolGUI, "") QmitknnUNetToolGUI::QmitknnUNetToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { // Nvidia-smi command returning zero doesn't always imply lack of GPUs. // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. if (m_GpuLoader.GetGPUCount() == 0) { std::string warning = "WARNING: No GPUs were detected on your machine. The nnUNet tool might not work."; this->ShowErrorMessage(warning); } // define predicates for multi modal data selection combobox auto imageType = mitk::TNodePredicateDataType::New(); auto labelSetImageType = mitk::NodePredicateNot::New(mitk::TNodePredicateDataType::New()); m_MultiModalPredicate = mitk::NodePredicateAnd::New(imageType, labelSetImageType).GetPointer(); m_nnUNetThread = new QThread(this); m_Worker = new nnUNetDownloadWorker; m_Worker->moveToThread(m_nnUNetThread); m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; } QmitknnUNetToolGUI::~QmitknnUNetToolGUI() { m_nnUNetThread->quit(); m_nnUNetThread->wait(); } void QmitknnUNetToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); newTool->IsTimePointChangeAwareOff(); m_FirstPreviewComputation = true; } void QmitknnUNetToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); #ifndef _WIN32 m_Controls.pythonEnvComboBox->addItem("/usr/bin"); #endif m_Controls.pythonEnvComboBox->addItem("Select"); AutoParsePythonPaths(); SetGPUInfo(); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewRequested())); connect(m_Controls.modeldirectoryBox, SIGNAL(directoryChanged(const QString &)), this, SLOT(OnDirectoryChanged(const QString &))); connect( m_Controls.modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); connect(m_Controls.taskBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTaskChanged(const QString &))); connect( m_Controls.plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); connect(m_Controls.multiModalBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); connect(m_Controls.pythonEnvComboBox, #if QT_VERSION >= 0x050F00 // 5.15 SIGNAL(textActivated(const QString &)), #elif QT_VERSION >= 0x050C00 // 5.12 SIGNAL(currentTextChanged(const QString &)), #endif this, SLOT(OnPythonPathChanged(const QString &))); connect(m_Controls.refreshdirectoryBox, SIGNAL(clicked()), this, SLOT(OnRefreshPresssed())); connect(m_Controls.clearCacheButton, SIGNAL(clicked()), this, SLOT(OnClearCachePressed())); connect(m_Controls.startDownloadButton, SIGNAL(clicked()), this, SLOT(OnDownloadModel())); connect(m_Controls.stopDownloadButton, SIGNAL(clicked()), this, SLOT(OnStopDownload())); // Qthreads qRegisterMetaType(); qRegisterMetaType(); connect(this, &QmitknnUNetToolGUI::Operate, m_Worker, &nnUNetDownloadWorker::DoWork); connect(m_Worker, &nnUNetDownloadWorker::Exit, this, &QmitknnUNetToolGUI::OnDownloadWorkerExit); connect(m_nnUNetThread, &QThread::finished, m_Worker, &QObject::deleteLater); m_Controls.multiModalValueLabel->setStyleSheet("font-weight: bold; color: white"); m_Controls.multiModalValueLabel->setVisible(false); m_Controls.requiredModalitiesLabel->setVisible(false); m_Controls.stopDownloadButton->setVisible(false); m_Controls.previewButton->setEnabled(false); QIcon refreshIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/view-refresh.svg")); m_Controls.refreshdirectoryBox->setIcon(refreshIcon); QIcon dirIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg")); m_Controls.modeldirectoryBox->setIcon(dirIcon); m_Controls.refreshdirectoryBox->setEnabled(true); QIcon stopIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/status/dialog-error.svg")); m_Controls.stopDownloadButton->setIcon(stopIcon); m_Controls.statusLabel->setTextFormat(Qt::RichText); if (m_GpuLoader.GetGPUCount() != 0) { WriteStatusMessage(QString("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected.")); } else { WriteErrorMessage(QString("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected.")); } mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); m_UI_ROWS = m_Controls.advancedSettingsLayout->rowCount(); // Must do. Row count is correct only here. this->DisableEverything(); QString lastSelectedPyEnv = m_Settings.value("nnUNet/LastPythonPath").toString(); m_Controls.pythonEnvComboBox->setCurrentText(lastSelectedPyEnv); } void QmitknnUNetToolGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); } void QmitknnUNetToolGUI::ClearAllModalities() { m_Controls.multiModalBox->setChecked(false); this->ClearAllModalLabels(); } void QmitknnUNetToolGUI::ClearAllModalLabels() { for (auto modalLabel : m_ModalLabels) { delete modalLabel; // delete the layout item m_ModalLabels.pop_back(); } m_Controls.advancedSettingsLayout->update(); } void QmitknnUNetToolGUI::DisableEverything() { m_Controls.modeldirectoryBox->setEnabled(false); m_Controls.refreshdirectoryBox->setEnabled(false); m_Controls.previewButton->setEnabled(false); m_Controls.multiModalValueLabel->setVisible(false); m_Controls.multiModalBox->setEnabled(false); this->ClearAllComboBoxes(); this->ClearAllModalities(); } void QmitknnUNetToolGUI::ClearAllComboBoxes() { m_Controls.modelBox->clear(); m_Controls.taskBox->clear(); m_Controls.foldBox->clear(); m_Controls.trainerBox->clear(); m_Controls.plannerBox->clear(); for (auto &layout : m_EnsembleParams) { layout->modelBox->clear(); layout->trainerBox->clear(); layout->plannerBox->clear(); layout->foldBox->clear(); } } std::vector QmitknnUNetToolGUI::FetchMultiModalImagesFromUI() { std::vector modals; if (m_Controls.multiModalBox->isChecked() && !m_Modalities.empty()) { std::set nodeNames; // set container for keeping names of all nodes to check if they are added twice. for (QmitkSingleNodeSelectionWidget *modality : m_Modalities) { mitk::DataNode::Pointer node = modality->GetSelectedNode(); if (nodeNames.find(node->GetName()) == nodeNames.end()) { modals.push_back(dynamic_cast(node->GetData())); nodeNames.insert(node->GetName()); } else { throw std::runtime_error("Same modality is selected more than once. Please change your selection."); break; } } } return modals; } bool QmitknnUNetToolGUI::IsNNUNetInstalled(const QString &pythonPath) { QString fullPath = pythonPath; #ifdef _WIN32 if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); } #else if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); } #endif fullPath = fullPath.mid(fullPath.indexOf(" ") + 1); bool isExists = QFile::exists(fullPath + QDir::separator() + QString("nnUNet_predict")) && QFile::exists(fullPath + QDir::separator() + QString("python3")); return isExists; } void QmitknnUNetToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); messageBox->exec(); delete messageBox; MITK_WARN << message; } void QmitknnUNetToolGUI::WriteStatusMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); } void QmitknnUNetToolGUI::WriteErrorMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); } void QmitknnUNetToolGUI::ProcessEnsembleModelsParams(mitk::nnUNetTool::Pointer tool) { if (m_EnsembleParams[0]->modelBox->currentText() == m_EnsembleParams[1]->modelBox->currentText()) { throw std::runtime_error("Both models you have selected for ensembling are the same."); } QString taskName = m_Controls.taskBox->currentText(); bool isPPJson = m_Controls.postProcessingCheckBox->isChecked(); std::vector requestQ; QString ppDirFolderNamePart1 = "ensemble_"; QStringList ppDirFolderNameParts; for (auto &layout : m_EnsembleParams) { QStringList ppDirFolderName; QString modelName = layout->modelBox->currentText(); ppDirFolderName << modelName; ppDirFolderName << "__"; QString trainer = layout->trainerBox->currentText(); ppDirFolderName << trainer; ppDirFolderName << "__"; QString planId = layout->plannerBox->currentText(); ppDirFolderName << planId; if (!this->IsModelExists(modelName, taskName, QString(trainer + "__" + planId))) { std::string errorMsg = "The configuration " + modelName.toStdString() + " you have selected doesn't exist. Check your Results Folder again."; throw std::runtime_error(errorMsg); } std::vector testfold = FetchSelectedFoldsFromUI(layout->foldBox); mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, testfold); requestQ.push_back(modelObject); ppDirFolderNameParts << ppDirFolderName.join(QString("")); } tool->EnsembleOn(); if (isPPJson) { QString ppJsonFilePossibility1 = QDir::cleanPath( m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.first() + "--" + ppDirFolderNameParts.last() + QDir::separator() + "postprocessing.json"); QString ppJsonFilePossibility2 = QDir::cleanPath( m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.last() + "--" + ppDirFolderNameParts.first() + QDir::separator() + "postprocessing.json"); if (QFile(ppJsonFilePossibility1).exists()) { tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility1.toStdString()); const QString statusMsg = "Post Processing JSON file found: " + ppJsonFilePossibility1; this->WriteStatusMessage(statusMsg); } else if (QFile(ppJsonFilePossibility2).exists()) { tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility2.toStdString()); const QString statusMsg = "Post Processing JSON file found:" + ppJsonFilePossibility2; this->WriteStatusMessage(statusMsg); } else { std::string errorMsg = "No post processing file was found for the selected ensemble combination. Continuing anyway..."; this->ShowErrorMessage(errorMsg); } } tool->m_ParamQ.clear(); tool->m_ParamQ = requestQ; } void QmitknnUNetToolGUI::ProcessModelParams(mitk::nnUNetTool::Pointer tool) { tool->EnsembleOff(); std::vector requestQ; QString modelName = m_Controls.modelBox->currentText(); QString taskName = m_Controls.taskBox->currentText(); QString trainer = m_Controls.trainerBox->currentText(); QString planId = m_Controls.plannerBox->currentText(); std::vector fetchedFolds = this->FetchSelectedFoldsFromUI(m_Controls.foldBox); mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, fetchedFolds); requestQ.push_back(modelObject); tool->m_ParamQ.clear(); tool->m_ParamQ = requestQ; } bool QmitknnUNetToolGUI::IsModelExists(const QString &modelName, const QString &taskName, const QString &trainerPlanner) { QString modelSearchPath = QDir::cleanPath(m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + modelName + QDir::separator() + taskName + QDir::separator() + trainerPlanner); if (QDir(modelSearchPath).exists()) { return true; } return false; } void QmitknnUNetToolGUI::CheckAllInCheckableComboBox(ctkCheckableComboBox *foldBox) { // Recalling all added items to check-mark it. const QAbstractItemModel *qaim = foldBox->checkableModel(); auto rows = qaim->rowCount(); for (std::remove_const_t i = 0; i < rows; ++i) { const QModelIndex mi = qaim->index(i, 0); foldBox->setCheckState(mi, Qt::Checked); } } std::pair QmitknnUNetToolGUI::ExtractTrainerPlannerFromString(QStringList trainerPlanners) { QString splitterString = "__"; QStringList trainers, planners; for (const auto &trainerPlanner : trainerPlanners) { trainers << trainerPlanner.split(splitterString, Qt::SkipEmptyParts).first(); planners << trainerPlanner.split(splitterString, Qt::SkipEmptyParts).last(); } trainers.removeDuplicates(); planners.removeDuplicates(); return std::make_pair(trainers, planners); } std::vector QmitknnUNetToolGUI::FetchSelectedFoldsFromUI(ctkCheckableComboBox *foldBox) { std::vector folds; if (foldBox->noneChecked()) { this->CheckAllInCheckableComboBox(foldBox); } QModelIndexList foldList = foldBox->checkedIndexes(); for (const auto &index : foldList) { QString foldQString = foldBox->itemText(index.row()); if(foldQString != "dummy_element_that_nobody_can_see") { foldQString = foldQString.split("_", Qt::SkipEmptyParts).last(); folds.push_back(foldQString.toStdString()); } else { throw std::runtime_error("Folds are not recognized. Please check if your nnUNet results folder structure is legitimate"); } } return folds; } void QmitknnUNetToolGUI::UpdateCacheCountOnUI() { QString cacheText = m_CACHE_COUNT_BASE_LABEL + QString::number(m_Cache.size()); m_Controls.cacheCountLabel->setText(cacheText); } void QmitknnUNetToolGUI::AddToCache(size_t &hashKey, mitk::LabelSetImage::ConstPointer mlPreview) { nnUNetCache *newCacheObj = new nnUNetCache; newCacheObj->m_SegCache = mlPreview; m_Cache.insert(hashKey, newCacheObj); MITK_INFO << "New hash: " << hashKey << " " << newCacheObj->m_SegCache.GetPointer(); this->UpdateCacheCountOnUI(); } void QmitknnUNetToolGUI::SetGPUInfo() { std::vector specs = m_GpuLoader.GetAllGPUSpecs(); for (const QmitkGPUSpec &gpuSpec : specs) { m_Controls.gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); } if (specs.empty()) { m_Controls.gpuComboBox->setEditable(true); m_Controls.gpuComboBox->addItem(QString::number(0)); m_Controls.gpuComboBox->setValidator(new QIntValidator(0, 999, this)); } } unsigned int QmitknnUNetToolGUI::FetchSelectedGPUFromUI() { QString gpuInfo = m_Controls.gpuComboBox->currentText(); if (m_GpuLoader.GetGPUCount() == 0) { return static_cast(gpuInfo.toInt()); } else { QString gpuId = gpuInfo.split(":", Qt::SkipEmptyParts).first(); return static_cast(gpuId.toInt()); } } QString QmitknnUNetToolGUI::FetchResultsFolderFromEnv() { const char *pathVal = itksys::SystemTools::GetEnv("RESULTS_FOLDER"); QString retVal; if (pathVal) { retVal = QString::fromUtf8(pathVal); } else { retVal = m_Settings.value("nnUNet/LastRESULTS_FOLDERPath").toString(); } return retVal; } void QmitknnUNetToolGUI::DumpJSONfromPickle(const QString &picklePath) { const QString pickleFile = picklePath + QDir::separator() + m_PICKLE_FILENAME; const QString jsonFile = picklePath + QDir::separator() + m_MITK_EXPORT_JSON_FILENAME; if (!QFile::exists(jsonFile)) { mitk::ProcessExecutor::Pointer spExec = mitk::ProcessExecutor::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back("-c"); std::string pythonCode; // python syntax to parse plans.pkl file and export as Json file. pythonCode.append("import pickle;"); pythonCode.append("import json;"); pythonCode.append("loaded_pickle = pickle.load(open('"); pythonCode.append(pickleFile.toStdString()); pythonCode.append("','rb'));"); pythonCode.append("modal_dict = {key: loaded_pickle[key] for key in loaded_pickle.keys() if key in " "['modalities','num_modalities']};"); pythonCode.append("json.dump(modal_dict, open('"); pythonCode.append(jsonFile.toStdString()); pythonCode.append("', 'w'))"); args.push_back(pythonCode); try { spExec->Execute(m_PythonPath.toStdString(), "python3", args); } catch (const mitk::Exception &e) { MITK_ERROR << "Pickle parsing FAILED!" << e.GetDescription(); this->WriteStatusMessage( "Parsing failed in backend. Multiple Modalities will now have to be manually entered by the user."); } } } void QmitknnUNetToolGUI::ExportAvailableModelsAsJSON(const QString &resultsFolder) { const QString jsonPath = resultsFolder + QDir::separator() + m_AVAILABLE_MODELS_JSON_FILENAME; if (!QFile::exists(jsonPath)) { auto spExec = mitk::ProcessExecutor::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back("--export"); args.push_back(resultsFolder.toStdString()); try { spExec->Execute(m_PythonPath.toStdString(), "nnUNet_print_available_pretrained_models", args); } catch (const mitk::Exception &e) { MITK_ERROR << "Exporting information FAILED." << e.GetDescription(); this->WriteStatusMessage("Exporting information FAILED."); } } } void QmitknnUNetToolGUI::DisplayMultiModalInfoFromJSON(const QString &jsonPath) { std::ifstream file(jsonPath.toStdString()); if (file.is_open()) { auto jsonObj = nlohmann::json::parse(file, nullptr, false); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse \"" << jsonPath.toStdString() << "\" as JSON object!"; return; } auto num_mods = jsonObj["num_modalities"].get(); this->ClearAllModalLabels(); if (num_mods > 1) { m_Controls.multiModalBox->setChecked(true); m_Controls.multiModalBox->setEnabled(false); m_Controls.multiModalValueLabel->setText(QString::number(num_mods)); OnModalitiesNumberChanged(num_mods); m_Controls.advancedSettingsLayout->update(); auto obj = jsonObj["modalities"]; int count = 0; for (const auto &value : obj) { QLabel *label = new QLabel(QString::fromStdString("" + value.get() + ""), this); m_ModalLabels.push_back(label); m_Controls.advancedSettingsLayout->addWidget(label, m_UI_ROWS + 1 + count, 0); count++; } m_Controls.advancedSettingsLayout->update(); } else { m_Controls.multiModalBox->setChecked(false); } } } void QmitknnUNetToolGUI::FillAvailableModelsInfoFromJSON(const QString &jsonPath) { std::ifstream file(jsonPath.toStdString()); if (file.is_open() && m_Controls.availableBox->count() < 1) { auto jsonObj = nlohmann::json::parse(file, nullptr, false); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse \"" << jsonPath.toStdString() << "\" as JSON object!"; return; } for (const auto &obj : jsonObj.items()) { m_Controls.availableBox->addItem(QString::fromStdString(obj.key())); } } } mitk::ModelParams QmitknnUNetToolGUI::MapToRequest(const QString &modelName, const QString &taskName, const QString &trainer, const QString &planId, const std::vector &folds) { mitk::ModelParams requestObject; requestObject.model = modelName.toStdString(); requestObject.trainer = trainer.toStdString(); requestObject.planId = planId.toStdString(); requestObject.task = taskName.toStdString(); requestObject.folds = folds; mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); requestObject.inputName = tool->GetRefNode()->GetName(); requestObject.timeStamp = std::to_string(mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint()); return requestObject; } void QmitknnUNetToolGUI::SetComboBoxToNone(ctkCheckableComboBox* comboBox) { comboBox->clear(); comboBox->addItem("dummy_element_that_nobody_can_see"); qobject_cast(comboBox->view())->setRowHidden(0, true); // For the cosmetic purpose of showing "None" on the combobox. } /* ---------------------SLOTS---------------------------------------*/ void QmitknnUNetToolGUI::OnPreviewRequested() { mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); if (nullptr != tool) { QString pythonPathTextItem = ""; try { size_t hashKey(0); m_Controls.previewButton->setEnabled(false); // To prevent misclicked back2back prediction. qApp->processEvents(); tool->PredictOn(); // purposefully placed to make tool->GetMTime different than before. QString modelName = m_Controls.modelBox->currentText(); if (modelName.startsWith("ensemble", Qt::CaseInsensitive)) { this->ProcessEnsembleModelsParams(tool); } else { this->ProcessModelParams(tool); } pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); QString pythonPath = m_PythonPath; if (!this->IsNNUNetInstalled(pythonPath)) { throw std::runtime_error("nnUNet is not detected in the selected python environment. Please select a valid " "python environment or install nnUNet."); } tool->SetPythonPath(pythonPath.toStdString()); tool->SetModelDirectory(m_ParentFolder->getResultsFolder().toStdString()); // checkboxes tool->SetMirror(m_Controls.mirrorBox->isChecked()); tool->SetMixedPrecision(m_Controls.mixedPrecisionBox->isChecked()); tool->SetNoPip(false); bool doCache = m_Controls.enableCachingCheckBox->isChecked(); // Spinboxes tool->SetGpuId(FetchSelectedGPUFromUI()); // Multi-Modal tool->MultiModalOff(); if (m_Controls.multiModalBox->isChecked()) { tool->m_OtherModalPaths.clear(); tool->m_OtherModalPaths = FetchMultiModalImagesFromUI(); tool->MultiModalOn(); } if (doCache) { hashKey = nnUNetCache::GetUniqueHash(tool->m_ParamQ); if (m_Cache.contains(hashKey)) { tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. } } if (tool->GetPredict()) { tool->m_InputBuffer = nullptr; this->WriteStatusMessage( QString("STATUS: Starting Segmentation task... This might take a while.")); + m_FirstPreviewComputation = false; tool->UpdatePreview(); if (nullptr == tool->GetOutputBuffer()) { this->SegmentationProcessFailed(); } else { this->SegmentationResultHandler(tool); if (doCache) { this->AddToCache(hashKey, tool->GetOutputBuffer()); } tool->ClearOutputBuffer(); } tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. } else { MITK_INFO << "won't do segmentation. Key found: " << QString::number(hashKey).toStdString(); if (m_Cache.contains(hashKey)) { nnUNetCache *cacheObject = m_Cache[hashKey]; MITK_INFO << "fetched pointer " << cacheObject->m_SegCache.GetPointer(); tool->SetOutputBuffer(const_cast(cacheObject->m_SegCache.GetPointer())); this->SegmentationResultHandler(tool, true); } } m_Controls.previewButton->setEnabled(true); } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for nnUNet segmentation. Reason: " << e.what(); this->ShowErrorMessage(errorMsg.str()); this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); m_Controls.previewButton->setEnabled(true); tool->PredictOff(); + m_FirstPreviewComputation = true; return; } catch (...) { std::string errorMsg = "Unkown error occured while generation nnUNet segmentation."; this->ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); tool->PredictOff(); + m_FirstPreviewComputation = true; return; } if (!pythonPathTextItem.isEmpty()) { // only cache if the prediction ended without errors. m_Settings.setValue("nnUNet/LastPythonPath", pythonPathTextItem); } } } void QmitknnUNetToolGUI::OnRefreshPresssed() { const QString resultsFolder = m_Controls.modeldirectoryBox->directory(); this->OnDirectoryChanged(resultsFolder); } void QmitknnUNetToolGUI::OnDirectoryChanged(const QString &resultsFolder) { m_IsResultsFolderValid = false; m_Controls.previewButton->setEnabled(false); this->ClearAllComboBoxes(); this->ClearAllModalities(); m_ParentFolder = std::make_shared(resultsFolder); auto tasks = m_ParentFolder->getAllTasks(); tasks.removeDuplicates(); std::for_each(tasks.begin(), tasks.end(), [this](QString task) { m_Controls.taskBox->addItem(task); }); m_Settings.setValue("nnUNet/LastRESULTS_FOLDERPath", resultsFolder); } void QmitknnUNetToolGUI::OnModelChanged(const QString &model) { if (model.isEmpty()) { return; } this->ClearAllModalities(); auto selectedTask = m_Controls.taskBox->currentText(); ctkComboBox *box = qobject_cast(sender()); if (box == m_Controls.modelBox) { if (model == m_VALID_MODELS.last()) { m_Controls.trainerBox->setVisible(false); m_Controls.trainerLabel->setVisible(false); m_Controls.plannerBox->setVisible(false); m_Controls.plannerLabel->setVisible(false); m_Controls.foldBox->setVisible(false); m_Controls.foldLabel->setVisible(false); m_Controls.previewButton->setEnabled(false); this->ShowEnsembleLayout(true); auto models = m_ParentFolder->getModelsForTask(m_Controls.taskBox->currentText()); models.removeDuplicates(); models.removeOne(m_VALID_MODELS.last()); for (auto &layout : m_EnsembleParams) { layout->modelBox->clear(); layout->trainerBox->clear(); layout->plannerBox->clear(); std::for_each(models.begin(), models.end(), [&layout, this](QString model) { if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) layout->modelBox->addItem(model); }); } } else { m_Controls.trainerBox->setVisible(true); m_Controls.trainerLabel->setVisible(true); m_Controls.plannerBox->setVisible(true); m_Controls.plannerLabel->setVisible(true); m_Controls.foldBox->setVisible(true); m_Controls.foldLabel->setVisible(true); m_Controls.previewButton->setEnabled(false); this->ShowEnsembleLayout(false); m_Controls.trainerBox->clear(); m_Controls.plannerBox->clear(); auto trainerPlanners = m_ParentFolder->getTrainerPlannersForTask(selectedTask, model); if(trainerPlanners.isEmpty()) { this->ShowErrorMessage("No plans.pkl found for "+model.toStdString()+". Check your directory or download the task again."); this->SetComboBoxToNone(m_Controls.foldBox); return; } QStringList trainers, planners; std::tie(trainers, planners) = ExtractTrainerPlannerFromString(trainerPlanners); std::for_each( trainers.begin(), trainers.end(), [this](QString trainer) { m_Controls.trainerBox->addItem(trainer); }); std::for_each( planners.begin(), planners.end(), [this](QString planner) { m_Controls.plannerBox->addItem(planner); }); } } else if (!m_EnsembleParams.empty()) { m_Controls.previewButton->setEnabled(false); for (auto &layout : m_EnsembleParams) { if (box == layout->modelBox) { layout->trainerBox->clear(); layout->plannerBox->clear(); auto trainerPlanners = m_ParentFolder->getTrainerPlannersForTask(selectedTask, model); if(trainerPlanners.isEmpty()) { this->ShowErrorMessage("No plans.pkl found for "+model.toStdString()+". Check your directory or download the task again."); this->SetComboBoxToNone(layout->foldBox); return; } QStringList trainers, planners; std::tie(trainers, planners) = ExtractTrainerPlannerFromString(trainerPlanners); std::for_each(trainers.begin(), trainers.end(), [&layout](const QString &trainer) { layout->trainerBox->addItem(trainer); }); std::for_each(planners.begin(), planners.end(), [&layout](const QString &planner) { layout->plannerBox->addItem(planner); }); break; } } } } void QmitknnUNetToolGUI::OnTaskChanged(const QString &task) { if (task.isEmpty()) { return; } m_Controls.modelBox->clear(); auto models = m_ParentFolder->getModelsForTask(task); models.removeDuplicates(); if (!models.contains(m_VALID_MODELS.last(), Qt::CaseInsensitive)) { models << m_VALID_MODELS.last(); // add ensemble even if folder doesn't exist } std::for_each(models.begin(), models.end(), [this](QString model) { if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) m_Controls.modelBox->addItem(model); }); } void QmitknnUNetToolGUI::OnTrainerChanged(const QString &plannerSelected) { if (plannerSelected.isEmpty()) { return; } m_IsResultsFolderValid = false; QString parentPath; auto *box = qobject_cast(sender()); if (box == m_Controls.plannerBox) { m_Controls.foldBox->clear(); auto selectedTrainer = m_Controls.trainerBox->currentText(); auto selectedTask = m_Controls.taskBox->currentText(); auto selectedModel = m_Controls.modelBox->currentText(); auto folds = m_ParentFolder->getFoldsForTrainerPlanner( selectedTrainer, plannerSelected, selectedTask, selectedModel); if(folds.isEmpty()) { this->ShowErrorMessage("No valid folds found. Check your directory or download the task again."); this->SetComboBoxToNone(m_Controls.foldBox); return; } std::for_each(folds.begin(), folds.end(), [this](QString fold) { if (fold.startsWith("fold_", Qt::CaseInsensitive)) // imposed by nnUNet m_Controls.foldBox->addItem(fold); }); if (m_Controls.foldBox->count() != 0) { m_IsResultsFolderValid = true; this->CheckAllInCheckableComboBox(m_Controls.foldBox); auto tempPath = QStringList() << m_ParentFolder->getResultsFolder() << "nnUNet" << selectedModel << selectedTask << QString("%1__%2").arg(selectedTrainer, plannerSelected); parentPath = QDir::cleanPath(tempPath.join(QDir::separator())); } } else if (!m_EnsembleParams.empty()) { for (auto &layout : m_EnsembleParams) { if (box == layout->plannerBox) { layout->foldBox->clear(); auto selectedTrainer = layout->trainerBox->currentText(); auto selectedTask = m_Controls.taskBox->currentText(); auto selectedModel = layout->modelBox->currentText(); auto folds = m_ParentFolder->getFoldsForTrainerPlanner( selectedTrainer, plannerSelected, selectedTask, selectedModel); if(folds.isEmpty()) { this->ShowErrorMessage("No valid folds found. Check your directory."); this->SetComboBoxToNone(layout->foldBox); return; } std::for_each(folds.begin(), folds.end(), [&layout](const QString &fold) { if (fold.startsWith("fold_", Qt::CaseInsensitive)) // imposed by nnUNet layout->foldBox->addItem(fold); }); if (layout->foldBox->count() != 0) { this->CheckAllInCheckableComboBox(layout->foldBox); m_IsResultsFolderValid = true; auto tempPath = QStringList() << m_ParentFolder->getResultsFolder() << "nnUNet" << selectedModel << selectedTask << QString("%1__%2").arg(selectedTrainer, plannerSelected); parentPath = QDir::cleanPath(tempPath.join(QDir::separator())); } break; } } } if (m_IsResultsFolderValid) { m_Controls.previewButton->setEnabled(true); const QString mitkJsonFile = parentPath + QDir::separator() + m_MITK_EXPORT_JSON_FILENAME; this->DumpJSONfromPickle(parentPath); if (QFile::exists(mitkJsonFile)) { this->DisplayMultiModalInfoFromJSON(mitkJsonFile); } } } void QmitknnUNetToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnPythonPathChanged(path); // recall same function for new path validation m_Controls.pythonEnvComboBox->insertItem(0, path); m_Controls.pythonEnvComboBox->setCurrentIndex(0); } } else if (!this->IsNNUNetInstalled(pyEnv)) { std::string warning = "WARNING: nnUNet is not detected on the Python environment you selected. Please select another " "environment or create one. For more info refer https://github.com/MIC-DKFZ/nnUNet"; this->ShowErrorMessage(warning); this->DisableEverything(); m_Controls.availableBox->clear(); } else { m_Controls.modeldirectoryBox->setEnabled(true); m_Controls.refreshdirectoryBox->setEnabled(true); m_Controls.multiModalBox->setEnabled(true); QString setVal = this->FetchResultsFolderFromEnv(); if (!setVal.isEmpty()) { m_Controls.modeldirectoryBox->setDirectory(setVal); } this->OnRefreshPresssed(); m_PythonPath = pyEnv.mid(pyEnv.indexOf(" ") + 1); #ifdef _WIN32 if (!(m_PythonPath.endsWith("Scripts", Qt::CaseInsensitive) || m_PythonPath.endsWith("Scripts/", Qt::CaseInsensitive))) { m_PythonPath += QDir::separator() + QString("Scripts"); } #else if (!(m_PythonPath.endsWith("bin", Qt::CaseInsensitive) || m_PythonPath.endsWith("bin/", Qt::CaseInsensitive))) { m_PythonPath += QDir::separator() + QString("bin"); } #endif // Export available model info as json and fill them for Download QString tempPath = QString::fromStdString(mitk::IOUtil::GetTempPath()); this->ExportAvailableModelsAsJSON(tempPath); const QString jsonPath = tempPath + QDir::separator() + m_AVAILABLE_MODELS_JSON_FILENAME; if (QFile::exists(jsonPath)) { this->FillAvailableModelsInfoFromJSON(jsonPath); } } } void QmitknnUNetToolGUI::OnCheckBoxChanged(int state) { bool visibility = false; if (state == Qt::Checked) { visibility = true; } ctkCheckBox *box = qobject_cast(sender()); if (box != nullptr) { if (box->objectName() == QString("multiModalBox")) { m_Controls.requiredModalitiesLabel->setVisible(visibility); m_Controls.multiModalValueLabel->setVisible(visibility); if (!visibility) { this->OnModalitiesNumberChanged(0); m_Controls.multiModalValueLabel->setText("0"); this->ClearAllModalLabels(); } } } } void QmitknnUNetToolGUI::OnModalitiesNumberChanged(int num) { while (num > static_cast(m_Modalities.size())) { QmitkSingleNodeSelectionWidget *multiModalBox = new QmitkSingleNodeSelectionWidget(this); mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); multiModalBox->SetDataStorage(tool->GetDataStorage()); multiModalBox->SetInvalidInfo("Select corresponding modalities"); multiModalBox->SetNodePredicate(m_MultiModalPredicate); multiModalBox->setObjectName(QString("multiModal_" + QString::number(m_Modalities.size() + 1))); m_Controls.advancedSettingsLayout->addWidget(multiModalBox, m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); m_Modalities.push_back(multiModalBox); } while (num < static_cast(m_Modalities.size()) && !m_Modalities.empty()) { QmitkSingleNodeSelectionWidget *child = m_Modalities.back(); delete child; // delete the layout item m_Modalities.pop_back(); } m_Controls.advancedSettingsLayout->update(); } void QmitknnUNetToolGUI::AutoParsePythonPaths() { QString homeDir = QDir::homePath(); std::vector searchDirs; #ifdef _WIN32 searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + QString("anaconda3")); #else // Add search locations for possible standard python paths here searchDirs.push_back(homeDir + QDir::separator() + "environments"); searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); #endif for (QString searchDir : searchDirs) { if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) { if (QDir(searchDir).exists()) { m_Controls.pythonEnvComboBox->insertItem(0, "(base): " + searchDir); searchDir.append((QDir::separator() + QString("envs"))); } } for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) { subIt.next(); QString envName = subIt.fileName(); if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. { m_Controls.pythonEnvComboBox->insertItem(0, "(" + envName + "): " + subIt.filePath()); } } } m_Controls.pythonEnvComboBox->setCurrentIndex(-1); } void QmitknnUNetToolGUI::SegmentationProcessFailed() { + m_FirstPreviewComputation = true; this->WriteErrorMessage( "STATUS: Error in the segmentation process.
No resulting segmentation can be loaded.
"); this->setCursor(Qt::ArrowCursor); std::stringstream stream; stream << "Error in the segmentation process. No resulting segmentation can be loaded."; this->ShowErrorMessage(stream.str()); } void QmitknnUNetToolGUI::SegmentationResultHandler(mitk::nnUNetTool *tool, bool forceRender) { if (forceRender) { tool->RenderOutputBuffer(); } - m_FirstPreviewComputation = false; this->SetLabelSetPreview(tool->GetPreviewSegmentation()); this->WriteStatusMessage("STATUS: Segmentation task finished successfully."); this->ActualizePreviewLabelVisibility(); } void QmitknnUNetToolGUI::ShowEnsembleLayout(bool visible) { if (m_EnsembleParams.empty()) { ctkCollapsibleGroupBox *groupBoxModel1 = new ctkCollapsibleGroupBox(this); auto lay1 = std::make_unique(groupBoxModel1); groupBoxModel1->setObjectName(QString::fromUtf8("model_1_Box")); groupBoxModel1->setTitle(QString::fromUtf8("Model 1")); groupBoxModel1->setMinimumSize(QSize(0, 0)); groupBoxModel1->setCollapsedHeight(5); groupBoxModel1->setCollapsed(false); groupBoxModel1->setFlat(true); groupBoxModel1->setAlignment(Qt::AlignRight); m_Controls.advancedSettingsLayout->addWidget(groupBoxModel1, 5, 0, 1, 2); connect(lay1->modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); connect( lay1->plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); m_EnsembleParams.push_back(std::move(lay1)); ctkCollapsibleGroupBox *groupBoxModel2 = new ctkCollapsibleGroupBox(this); auto lay2 = std::make_unique(groupBoxModel2); groupBoxModel2->setObjectName(QString::fromUtf8("model_2_Box")); groupBoxModel2->setTitle(QString::fromUtf8("Model 2")); groupBoxModel2->setMinimumSize(QSize(0, 0)); groupBoxModel2->setCollapsedHeight(5); groupBoxModel2->setCollapsed(false); groupBoxModel2->setFlat(true); groupBoxModel2->setAlignment(Qt::AlignLeft); m_Controls.advancedSettingsLayout->addWidget(groupBoxModel2, 5, 2, 1, 2); connect(lay2->modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); connect( lay2->plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); m_EnsembleParams.push_back(std::move(lay2)); } for (auto &layout : m_EnsembleParams) { layout->setVisible(visible); } } void QmitknnUNetToolGUI::OnDownloadModel() { auto selectedTask = m_Controls.availableBox->currentText(); if(!selectedTask.isEmpty()) { auto spExec = mitk::ProcessExecutor::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back(selectedTask.toStdString()); this->WriteStatusMessage( "Downloading the requested task in to the selected Results Folder. This might take some time " "depending on your internet connection..."); m_Processes["DOWNLOAD"] = spExec; if (!m_nnUNetThread->isRunning()) { MITK_DEBUG << "Starting thread..."; m_nnUNetThread->start(); } QString resultsFolder = m_ParentFolder->getResultsFolder(); emit Operate(resultsFolder, m_PythonPath, spExec, args); m_Controls.stopDownloadButton->setVisible(true); m_Controls.startDownloadButton->setVisible(false); } } void QmitknnUNetToolGUI::OnDownloadWorkerExit(const bool isSuccess, const QString message) { if (isSuccess) { this->WriteStatusMessage(message + QString(" Click Refresh Results Folder to use the new Task.")); } else { MITK_ERROR << "Download FAILED! " << message.toStdString(); this->WriteStatusMessage(QString("Download failed. Check your internet connection. " + message)); } m_Controls.stopDownloadButton->setVisible(false); m_Controls.startDownloadButton->setVisible(true); } void QmitknnUNetToolGUI::OnStopDownload() { mitk::ProcessExecutor::Pointer spExec = m_Processes["DOWNLOAD"]; spExec->KillProcess(); this->WriteStatusMessage("Download Killed by the user."); m_Controls.stopDownloadButton->setVisible(false); m_Controls.startDownloadButton->setVisible(true); } void QmitknnUNetToolGUI::OnClearCachePressed() { m_Cache.clear(); this->UpdateCacheCountOnUI(); } diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp index 58d982de3f..ae6843eac7 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp @@ -1,157 +1,259 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkBooleanOperationsWidget.h" #include #include #include #include -#include +#include +#include +#include +#include -#include -#include - -static const char* const HelpText = "Select two different segmentations above"; - -namespace +QmitkBooleanOperationsWidget::QmitkBooleanOperationsWidget(mitk::DataStorage* dataStorage, QWidget* parent) + : QWidget(parent) { - static std::string GetPrefix(mitk::BooleanOperation::Type type) - { - switch (type) - { - case mitk::BooleanOperation::Difference: - return "DifferenceFrom_"; + m_Controls = new Ui::QmitkBooleanOperationsWidgetControls; + m_Controls->setupUi(this); - case mitk::BooleanOperation::Intersection: - return "IntersectionWith_"; + m_Controls->label1st->setText(""); + m_Controls->label1st->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + m_Controls->label2nd->setText(""); + m_Controls->label2nd->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - case mitk::BooleanOperation::Union: - return "UnionWith_"; + m_Controls->segNodeSelector->SetDataStorage(dataStorage); + m_Controls->segNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate()); + m_Controls->segNodeSelector->SetSelectionIsOptional(false); + m_Controls->segNodeSelector->SetInvalidInfo(QStringLiteral("Please select segmentation for extraction.")); + m_Controls->segNodeSelector->SetPopUpTitel(QStringLiteral("Select segmentation")); + m_Controls->segNodeSelector->SetPopUpHint(QStringLiteral("Select the segmentation that should be used as source for extraction.")); - default: - assert(false && "Unknown boolean operation type"); - return "UNKNOWN_BOOLEAN_OPERATION_WITH_"; - } - } + m_Controls->labelInspector->SetMultiSelectionMode(true); - static void AddToDataStorage(mitk::DataStorage::Pointer dataStorage, mitk::Image::Pointer segmentation, const std::string& name, mitk::DataNode::Pointer parent = nullptr) - { - if (dataStorage.IsNull()) - { - std::string exception = "Cannot add result to the data storage. Data storage invalid."; - MITK_ERROR << "Boolean operation failed: " << exception; - QMessageBox::information(nullptr, "Boolean operation failed", QString::fromStdString(exception)); - } + connect(m_Controls->segNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkBooleanOperationsWidget::OnSegSelectionChanged); - auto dataNode = mitk::DataNode::New(); - dataNode->SetName(name); - dataNode->SetData(segmentation); + connect(m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, + this, &QmitkBooleanOperationsWidget::OnLabelSelectionChanged); - dataStorage->Add(dataNode, parent); - } + connect(m_Controls->differenceButton, &QToolButton::clicked, this, &QmitkBooleanOperationsWidget::OnDifferenceButtonClicked); + connect(m_Controls->intersectionButton, &QToolButton::clicked, this, &QmitkBooleanOperationsWidget::OnIntersectionButtonClicked); + connect(m_Controls->unionButton, &QToolButton::clicked, this, &QmitkBooleanOperationsWidget::OnUnionButtonClicked); + + m_Controls->segNodeSelector->SetAutoSelectNewNodes(true); + this->ConfigureWidgets(); } -QmitkBooleanOperationsWidget::QmitkBooleanOperationsWidget(mitk::DataStorage* dataStorage, QWidget* parent) - : QWidget(parent) +QmitkBooleanOperationsWidget::~QmitkBooleanOperationsWidget() { - m_Controls = new Ui::QmitkBooleanOperationsWidgetControls; - m_Controls->setupUi(this); - - m_Controls->dataSelectionWidget->SetDataStorage(dataStorage); - m_Controls->dataSelectionWidget->AddDataSelection("", "Select 1st segmentation", "Select 1st segmentation", "", QmitkDataSelectionWidget::SegmentationPredicate); - m_Controls->dataSelectionWidget->AddDataSelection("", "Select 2nd segmentation", "Select 2nd segmentation", "", QmitkDataSelectionWidget::SegmentationPredicate); + m_Controls->labelInspector->SetMultiLabelNode(nullptr); +} - m_Controls->dataSelectionWidget->SetHelpText(HelpText); +void QmitkBooleanOperationsWidget::OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) +{ + auto node = m_Controls->segNodeSelector->GetSelectedNode(); + m_Controls->labelInspector->SetMultiLabelNode(node); - connect(m_Controls->dataSelectionWidget, SIGNAL(SelectionChanged(unsigned int, const mitk::DataNode*)), this, SLOT(OnSelectionChanged(unsigned int, const mitk::DataNode*))); - connect(m_Controls->differenceButton, SIGNAL(clicked()), this, SLOT(OnDifferenceButtonClicked())); - connect(m_Controls->intersectionButton, SIGNAL(clicked()), this, SLOT(OnIntersectionButtonClicked())); - connect(m_Controls->unionButton, SIGNAL(clicked()), this, SLOT(OnUnionButtonClicked())); + this->ConfigureWidgets(); } -QmitkBooleanOperationsWidget::~QmitkBooleanOperationsWidget() +void QmitkBooleanOperationsWidget::OnLabelSelectionChanged(mitk::LabelSetImage::LabelValueVectorType /*labels*/) { + this->ConfigureWidgets(); } -void QmitkBooleanOperationsWidget::OnSelectionChanged(unsigned int, const mitk::DataNode*) +namespace { - auto dataSelectionWidget = m_Controls->dataSelectionWidget; + std::string GenerateLabelHTML(const mitk::Label* label) + { + std::stringstream stream; + auto color = label->GetColor(); + stream << "(color.GetRed()*255) + << std::setw(2) << static_cast(color.GetGreen()*255) + << std::setw(2) << static_cast(color.GetBlue()*255) + << "; font-size: 20px '>■" << std::dec; + + stream << " " << label->GetName()<< ""; + return stream.str(); + } +} +void QmitkBooleanOperationsWidget::ConfigureWidgets() +{ + auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); + + auto styleSheet = qApp->styleSheet(); - auto nodeA = dataSelectionWidget->GetSelection(0); - auto nodeB = dataSelectionWidget->GetSelection(1); + m_Controls->line1stLabel->document()->setDefaultStyleSheet(styleSheet); + m_Controls->lineOtherLabels->document()->setDefaultStyleSheet(styleSheet); - if (nodeA.IsNotNull() && nodeB.IsNotNull() && nodeA != nodeB) + if (selectedLabelValues.empty()) { - dataSelectionWidget->SetHelpText(""); - this->EnableButtons(); + m_Controls->line1stLabel->setHtml(QStringLiteral("Select 1st label to proceed.")); } else { - dataSelectionWidget->SetHelpText(HelpText); - this->EnableButtons(false); + auto label = seg->GetLabel(selectedLabelValues.front()); + m_Controls->line1stLabel->setText(QString::fromStdString(GenerateLabelHTML(label))); } -} -void QmitkBooleanOperationsWidget::EnableButtons(bool enable) -{ - m_Controls->differenceButton->setEnabled(enable); - m_Controls->intersectionButton->setEnabled(enable); - m_Controls->unionButton->setEnabled(enable); + if (selectedLabelValues.size() < 2) + { + m_Controls->lineOtherLabels->setHtml(QStringLiteral("Select secondary label(s) to proceed.")); + } + else + { + std::stringstream stream; + for (auto iter = selectedLabelValues.cbegin() + 1; iter != selectedLabelValues.cend(); ++iter) + { + auto label = seg->GetLabel(*iter); + if (stream.rdbuf()->in_avail() != 0) stream << "; "; + stream << GenerateLabelHTML(label); + } + + m_Controls->lineOtherLabels->setText(QString::fromStdString(stream.str())); + } + + m_Controls->differenceButton->setEnabled(selectedLabelValues.size()>1); + m_Controls->intersectionButton->setEnabled(selectedLabelValues.size() > 1); + m_Controls->unionButton->setEnabled(selectedLabelValues.size() > 1); } void QmitkBooleanOperationsWidget::OnDifferenceButtonClicked() { - this->DoBooleanOperation(mitk::BooleanOperation::Difference); + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + mitk::ProgressBar::GetInstance()->Reset(); + mitk::ProgressBar::GetInstance()->AddStepsToDo(110); + unsigned int currentProgress = 0; + + auto progressCallback = [¤tProgress](float filterProgress) + { + auto delta = (filterProgress * 100) - currentProgress; + if (delta > 0) + { + currentProgress += delta; + mitk::ProgressBar::GetInstance()->Progress(delta); + } + }; + + auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); + auto minuend = selectedLabelValues.front(); + auto subtrahends = mitk::LabelSetImage::LabelValueVectorType(selectedLabelValues.begin() + 1, selectedLabelValues.end()); + + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); + + auto resultMask = mitk::BooleanOperation::GenerateDifference(seg, minuend, subtrahends, progressCallback); + + std::stringstream name; + name << "Difference " << seg->GetLabel(minuend)->GetName() << " -"; + for (auto label : subtrahends) + { + name << " " << seg->GetLabel(label)->GetName(); + } + + this->SaveResultLabelMask(resultMask, name.str()); + + mitk::ProgressBar::GetInstance()->Reset(); + QApplication::restoreOverrideCursor(); } void QmitkBooleanOperationsWidget::OnIntersectionButtonClicked() { - this->DoBooleanOperation(mitk::BooleanOperation::Intersection); -} + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + mitk::ProgressBar::GetInstance()->Reset(); + mitk::ProgressBar::GetInstance()->AddStepsToDo(110); + unsigned int currentProgress = 0; -void QmitkBooleanOperationsWidget::OnUnionButtonClicked() -{ - this->DoBooleanOperation(mitk::BooleanOperation::Union); + auto progressCallback = [¤tProgress](float filterProgress) + { + auto delta = (filterProgress * 100) - currentProgress; + if (delta > 0) + { + currentProgress += delta; + mitk::ProgressBar::GetInstance()->Progress(delta); + } + }; + + auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); + + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); + + auto resultMask = mitk::BooleanOperation::GenerateIntersection(seg, selectedLabelValues, progressCallback); + + std::stringstream name; + name << "Intersection"; + for (auto label : selectedLabelValues) + { + name << " " << seg->GetLabel(label)->GetName(); + } + this->SaveResultLabelMask(resultMask, name.str()); + + mitk::ProgressBar::GetInstance()->Reset(); + QApplication::restoreOverrideCursor(); } -void QmitkBooleanOperationsWidget::DoBooleanOperation(mitk::BooleanOperation::Type type) +void QmitkBooleanOperationsWidget::OnUnionButtonClicked() { - const auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); - assert(timeNavigationController != nullptr); + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + mitk::ProgressBar::GetInstance()->Reset(); + mitk::ProgressBar::GetInstance()->AddStepsToDo(110); + unsigned int currentProgress = 0; - mitk::Image::Pointer segmentationA = dynamic_cast(m_Controls->dataSelectionWidget->GetSelection(0)->GetData()); - mitk::Image::Pointer segmentationB = dynamic_cast(m_Controls->dataSelectionWidget->GetSelection(1)->GetData()); - mitk::Image::Pointer result; + auto progressCallback = [¤tProgress](float filterProgress) + { + auto delta = (filterProgress * 100) - currentProgress; + if (delta > 0) + { + currentProgress += delta; + mitk::ProgressBar::GetInstance()->Progress(delta); + } + }; - try - { - mitk::BooleanOperation booleanOperation(type, segmentationA, segmentationB, timeNavigationController->GetSelectedTimePoint()); - result = booleanOperation.GetResult(); + auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); - assert(result.IsNotNull()); + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); - auto dataSelectionWidget = m_Controls->dataSelectionWidget; + auto resultMask = mitk::BooleanOperation::GenerateUnion(seg, selectedLabelValues, progressCallback); - AddToDataStorage( - dataSelectionWidget->GetDataStorage(), - result, - GetPrefix(type) + dataSelectionWidget->GetSelection(1)->GetName(), - dataSelectionWidget->GetSelection(0)); - } - catch (const mitk::Exception& exception) + std::stringstream name; + name << "Union"; + for (auto label : selectedLabelValues) { - MITK_ERROR << "Boolean operation failed: " << exception.GetDescription(); - QMessageBox::information(nullptr, "Boolean operation failed", exception.GetDescription()); + name << " " << seg->GetLabel(label)->GetName(); } + + this->SaveResultLabelMask(resultMask, name.str()); + + mitk::ProgressBar::GetInstance()->Reset(); + QApplication::restoreOverrideCursor(); +} + +void QmitkBooleanOperationsWidget::SaveResultLabelMask(const mitk::Image* resultMask, const std::string& labelName) const +{ + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); + if (seg == nullptr) mitkThrow() << "Widget is in invalid state. Processing was triggered with no segmentation selected."; + + auto labels = m_Controls->labelInspector->GetSelectedLabels(); + if (labels.empty()) mitkThrow() << "Widget is in invalid state. Processing was triggered with no label selected."; + + auto groupID = seg->AddLayer(); + auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(seg, labelName, true); + seg->AddLabelWithContent(newLabel, resultMask, groupID, 1); + + m_Controls->labelInspector->GetMultiLabelSegmentation()->Modified(); + m_Controls->labelInspector->GetMultiLabelNode()->Modified(); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.h b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.h index 5d09e3eec6..6abacbf534 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.h +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.h @@ -1,54 +1,58 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkBooleanOperationsWidget_h #define QmitkBooleanOperationsWidget_h -#include +#include -#include +#include #include +#include + namespace Ui { class QmitkBooleanOperationsWidgetControls; } namespace mitk { class DataNode; class DataStorage; } class MITKSEGMENTATIONUI_EXPORT QmitkBooleanOperationsWidget : public QWidget { Q_OBJECT public: explicit QmitkBooleanOperationsWidget(mitk::DataStorage* dataStorage, QWidget* parent = nullptr); ~QmitkBooleanOperationsWidget() override; private slots: - void OnSelectionChanged(unsigned int index, const mitk::DataNode* selection); + void OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList nodes); + void OnLabelSelectionChanged(mitk::LabelSetImage::LabelValueVectorType labels); void OnDifferenceButtonClicked(); void OnIntersectionButtonClicked(); void OnUnionButtonClicked(); private: - void EnableButtons(bool enable = true); - void DoBooleanOperation(mitk::BooleanOperation::Type type); + void ConfigureWidgets(); + void SaveResultLabelMask(const mitk::Image* resultMask, const std::string& labelName) const; + Ui::QmitkBooleanOperationsWidgetControls* m_Controls; }; #endif diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui index 00a0389ab4..b9c623ea2b 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui @@ -1,181 +1,291 @@ QmitkBooleanOperationsWidgetControls 0 0 - 210 - 91 + 249 + 293 - - - - 0 - 0 - + + + + + + + + + Selected operands: + + + 6 + + + 0 + + + 6 + + + 6 + + + + + TextLabel + + + + + + + false + + + + 0 + 0 + + + + + 16777215 + 26 + + + + false + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QTextEdit::NoWrap + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +</style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Test text</p></body></html> + + + + + + + TextLabel + + + + + + + + 0 + 0 + + + + + 16777215 + 26 + + + + Qt::NoContextMenu + + + false + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QTextEdit::NoWrap + + + true + + + + false 0 0 Subtracts first segmentation from the second one Difference - - :/Qmitk/BooleanDifference_48x48.png:/Qmitk/BooleanDifference_48x48.png - + + :/Qmitk/BooleanDifference_48x48.png:/Qmitk/BooleanDifference_48x48.png 24 24 false false Qt::ToolButtonTextUnderIcon Qt::NoArrow false 0 0 Keeps just the overlapping areas of two the segmentations Intersection - - :/Qmitk/BooleanIntersection_48x48.png:/Qmitk/BooleanIntersection_48x48.png - + + :/Qmitk/BooleanIntersection_48x48.png:/Qmitk/BooleanIntersection_48x48.png 24 24 false false Qt::ToolButtonTextUnderIcon Qt::NoArrow false 0 0 Combines the two segmentations Union - - :/Qmitk/BooleanUnion_48x48.png:/Qmitk/BooleanUnion_48x48.png - + + :/Qmitk/BooleanUnion_48x48.png:/Qmitk/BooleanUnion_48x48.png 24 24 false false Qt::ToolButtonTextUnderIcon Qt::NoArrow Qt::Vertical 20 40 - QmitkDataSelectionWidget + QmitkSingleNodeSelectionWidget + QWidget +
QmitkSingleNodeSelectionWidget.h
+ 1 +
+ + QmitkMultiLabelInspector QWidget -
QmitkDataSelectionWidget.h
+
QmitkMultiLabelInspector.h
1
diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp index f4975aac24..0ae005d4d2 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp @@ -1,543 +1,546 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkConvertToMultiLabelSegmentationWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -mitk::NodePredicateBase::Pointer GetInputPredicate() +namespace { - auto isImage = mitk::TNodePredicateDataType::New(); - auto isNotSeg = mitk::NodePredicateNot::New(mitk::GetMultiLabelSegmentationPredicate()); + mitk::NodePredicateBase::Pointer GetInputPredicate() + { + auto isImage = mitk::TNodePredicateDataType::New(); + auto isNotSeg = mitk::NodePredicateNot::New(mitk::GetMultiLabelSegmentationPredicate()); - auto isSurface = mitk::TNodePredicateDataType::New(); - auto isContourModel = mitk::TNodePredicateDataType::New(); - auto isContourModelSet = mitk::TNodePredicateDataType::New(); + auto isSurface = mitk::TNodePredicateDataType::New(); + auto isContourModel = mitk::TNodePredicateDataType::New(); + auto isContourModelSet = mitk::TNodePredicateDataType::New(); - auto isData = mitk::NodePredicateOr::New(isImage, isContourModel, isContourModelSet); - isData->AddPredicate(isSurface); + auto isData = mitk::NodePredicateOr::New(isImage, isContourModel, isContourModelSet); + isData->AddPredicate(isSurface); - auto isValidInput = mitk::NodePredicateAnd::New(isNotSeg, isData); - isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); - isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); - return isValidInput.GetPointer(); + auto isValidInput = mitk::NodePredicateAnd::New(isNotSeg, isData); + isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); + isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); + return isValidInput.GetPointer(); + } } const mitk::DataNode* GetNodeWithLargestImageGeometry(const QmitkNodeSelectionDialog::NodeList& nodes) { mitk::BaseGeometry::ConstPointer refGeometry; mitk::DataNode* result = nullptr; for (auto& node : nodes) { auto castedData = dynamic_cast(node->GetData()); if (castedData != nullptr) { if (refGeometry.IsNull() || mitk::IsSubGeometry(*refGeometry, *(castedData->GetGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) { refGeometry = castedData->GetGeometry(); result = node; } } } return result; } QmitkNodeSelectionDialog::NodeList GetNonimageNodes(const QmitkNodeSelectionDialog::NodeList& nodes) { QmitkNodeSelectionDialog::NodeList result; for (auto& node : nodes) { auto castedData = dynamic_cast(node->GetData()); if (castedData == nullptr) { result.push_back(node); } } return result; } QmitkNodeSelectionDialog::NodeList GetImageNodes(const QmitkNodeSelectionDialog::NodeList& nodes) { QmitkNodeSelectionDialog::NodeList result; for (auto& node : nodes) { auto castedData = dynamic_cast(node->GetData()); if (castedData != nullptr) { result.push_back(node); } } return result; } QmitkNodeSelectionDialog::SelectionCheckFunctionType CheckForSameGeometry(const mitk::DataNode* refNode) { mitk::DataNode::ConstPointer refNodeLambda = refNode; auto lambda = [refNodeLambda](const QmitkNodeSelectionDialog::NodeList& nodes) { if (nodes.empty()) { return std::string(); } mitk::NodePredicateSubGeometry::Pointer geoPredicate; bool usedExternalGeo = false; std::string refNodeName; if (refNodeLambda.IsNotNull() && nullptr != refNodeLambda->GetData()) { geoPredicate = mitk::NodePredicateSubGeometry::New(refNodeLambda->GetData()->GetGeometry()); usedExternalGeo = true; refNodeName = refNodeLambda->GetName(); } if (geoPredicate.IsNull()) { auto imageNode = GetNodeWithLargestImageGeometry(nodes); if (nullptr != imageNode) { geoPredicate = mitk::NodePredicateSubGeometry::New(imageNode->GetData()->GetGeometry()); refNodeName = imageNode->GetName(); } } for (auto& node : nodes) { auto castedImageData = dynamic_cast(node->GetData()); if (nullptr != castedImageData) { if (!geoPredicate->CheckNode(node)) { std::stringstream ss; ss << "

Invalid selection: All selected images must have the same geometry or a sub geometry "; if (usedExternalGeo) ss << "of the selected reference/output"; ss << ".< / p>

Uses reference data: \""; ss << refNodeName << "\"

"; ss << "

Differing data selections i.a.: \""; ss << node->GetName() << "\"

"; return ss.str(); } } } return std::string(); }; return lambda; } QmitkConvertToMultiLabelSegmentationWidget::QmitkConvertToMultiLabelSegmentationWidget(mitk::DataStorage* dataStorage, QWidget* parent) : QWidget(parent), m_DataStorage(dataStorage) { m_Controls = new Ui::QmitkConvertToMultiLabelSegmentationWidgetControls; m_Controls->setupUi(this); m_Controls->inputNodesSelector->SetDataStorage(dataStorage); m_Controls->inputNodesSelector->SetNodePredicate(GetInputPredicate()); m_Controls->inputNodesSelector->SetSelectionCheckFunction(CheckForSameGeometry(nullptr)); m_Controls->inputNodesSelector->SetSelectionIsOptional(false); m_Controls->inputNodesSelector->SetInvalidInfo(QStringLiteral("Please select inputs (images, surfaces or contours) for conversion")); m_Controls->inputNodesSelector->SetPopUpTitel(QStringLiteral("Select inputs")); m_Controls->inputNodesSelector->SetPopUpHint(QStringLiteral("You may select multiple inputs for conversion. But all selected images must have the same geometry or a sub geometry.")); m_Controls->outputSegSelector->SetDataStorage(dataStorage); m_Controls->outputSegSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate()); m_Controls->outputSegSelector->SetSelectionIsOptional(false); m_Controls->outputSegSelector->SetInvalidInfo(QStringLiteral("Please select the target segmentation")); m_Controls->outputSegSelector->SetPopUpTitel(QStringLiteral("Select target segmentation")); m_Controls->outputSegSelector->SetPopUpHint(QStringLiteral("Select the segmentation to which the converted inputs should be added.")); m_Controls->outputSegSelector->SetAutoSelectNewNodes(true); m_Controls->refNodeSelector->SetDataStorage(dataStorage); m_Controls->refNodeSelector->SetNodePredicate(mitk::NodePredicateOr::New(GetInputPredicate(),mitk::GetMultiLabelSegmentationPredicate())); m_Controls->refNodeSelector->SetSelectionIsOptional(false); m_Controls->refNodeSelector->SetInvalidInfo(QStringLiteral("Please select a reference image or segmentation")); m_Controls->refNodeSelector->SetPopUpTitel(QStringLiteral("Select a reference image or segmentation")); m_Controls->refNodeSelector->SetPopUpHint(QStringLiteral("Select the image or segmentation that defines the geometry of the conversion result.")); this->ConfigureWidgets(); connect (m_Controls->btnConvert, &QAbstractButton::clicked, this, &QmitkConvertToMultiLabelSegmentationWidget::OnConvertPressed); connect(m_Controls->inputNodesSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkConvertToMultiLabelSegmentationWidget::OnInputSelectionChanged); connect(m_Controls->refNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkConvertToMultiLabelSegmentationWidget::OnRefSelectionChanged); connect(m_Controls->outputSegSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkConvertToMultiLabelSegmentationWidget::OnOutputSelectionChanged); auto widget = this; connect(m_Controls->radioAddToSeg, &QRadioButton::toggled, m_Controls->outputSegSelector, [widget](bool) {widget->ConfigureWidgets(); }); connect(m_Controls->checkMultipleOutputs, &QCheckBox::toggled, m_Controls->outputSegSelector, [widget](bool) {widget->ConfigureWidgets(); }); } QmitkConvertToMultiLabelSegmentationWidget::~QmitkConvertToMultiLabelSegmentationWidget() { } void QmitkConvertToMultiLabelSegmentationWidget::ConfigureWidgets() { m_InternalEvent = true; if (m_Controls->radioAddToSeg->isChecked()) { auto selectedNode = m_Controls->outputSegSelector->GetSelectedNode(); m_Controls->inputNodesSelector->SetSelectionCheckFunction(CheckForSameGeometry(selectedNode)); } else { m_Controls->inputNodesSelector->SetSelectionCheckFunction(CheckForSameGeometry(nullptr)); } m_Controls->outputSegSelector->setVisible(m_Controls->radioAddToSeg->isChecked()); if (m_Controls->radioNewSeg->isChecked()) { auto selectedNode = m_Controls->refNodeSelector->GetSelectedNode(); } m_Controls->checkMultipleOutputs->setVisible(m_Controls->radioNewSeg->isChecked()); bool refNeeded = m_Controls->radioNewSeg->isChecked() && !m_Controls->inputNodesSelector->GetSelectedNodes().empty() && nullptr == GetNodeWithLargestImageGeometry(m_Controls->inputNodesSelector->GetSelectedNodes()); m_Controls->refNodeSelector->setVisible(refNeeded); if (refNeeded) m_Controls->inputNodesSelector->SetSelectionCheckFunction(CheckForSameGeometry(m_Controls->refNodeSelector->GetSelectedNode())); m_Controls->groupGrouping->setVisible(m_Controls->radioAddToSeg->isChecked() || !(m_Controls->checkMultipleOutputs->isChecked())); bool inputIsOK = !m_Controls->inputNodesSelector->GetSelectedNodes().empty() && !m_Controls->inputNodesSelector->CurrentSelectionViolatesCheckFunction(); bool outputIsOK = !m_Controls->radioAddToSeg->isChecked() || m_Controls->outputSegSelector->GetSelectedNode().IsNotNull(); bool refIsOK = !m_Controls->radioNewSeg->isChecked() || !m_Controls->refNodeSelector->isVisible() || m_Controls->refNodeSelector->GetSelectedNode().IsNotNull(); m_Controls->btnConvert->setEnabled(inputIsOK && outputIsOK && refIsOK); m_InternalEvent = false; } void QmitkConvertToMultiLabelSegmentationWidget::OnInputSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { if (!m_InternalEvent) this->ConfigureWidgets(); } void QmitkConvertToMultiLabelSegmentationWidget::OnOutputSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { if (!m_InternalEvent) this->ConfigureWidgets(); } void QmitkConvertToMultiLabelSegmentationWidget::OnRefSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { if (!m_InternalEvent) this->ConfigureWidgets(); } void QmitkConvertToMultiLabelSegmentationWidget::OnConvertPressed() { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { mitkThrow() << "QmitkConvertToMultiLabelSegmentationWidget is in invalid state. No datastorage is set."; } auto nodes = m_Controls->inputNodesSelector->GetSelectedNodes(); mitk::ProgressBar::GetInstance()->Reset(); mitk::ProgressBar::GetInstance()->AddStepsToDo(3 * nodes.size() + 1); if (m_Controls->radioNewSeg->isChecked() && m_Controls->checkMultipleOutputs->isChecked()) { for (auto& node : nodes) { this->ConvertNodes({ node }); } } else { this->ConvertNodes(nodes); } } mitk::Image::Pointer ConvertSurfaceToImage(const mitk::Image* refImage, const mitk::Surface* surface) { mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->UShortBinaryPixelTypeOn(); surfaceToImageFilter->SetInput(surface); surfaceToImageFilter->SetImage(refImage); try { surfaceToImageFilter->Update(); } catch (itk::ExceptionObject& excpt) { MITK_ERROR << excpt.GetDescription(); return nullptr; } return surfaceToImageFilter->GetOutput(); } mitk::Image::Pointer ConvertContourModelSetToImage(mitk::Image* refImage, mitk::ContourModelSet* contourSet) { // Use mitk::ContourModelSetToImageFilter to fill the ContourModelSet into the image mitk::ContourModelSetToImageFilter::Pointer contourFiller = mitk::ContourModelSetToImageFilter::New(); contourFiller->SetImage(refImage); contourFiller->SetInput(contourSet); contourFiller->MakeOutputLabelPixelTypeOn(); try { contourFiller->Update(); } catch (const std::exception& e) { MITK_ERROR << "Error while converting contour model. " << e.what(); } catch (...) { MITK_ERROR << "Unknown error while converting contour model."; } return contourFiller->GetOutput(); } void CheckForLabelCollision(const QmitkNodeSelectionDialog::NodeList& nodes, const std::map& foundLabelsMap, mitk::LabelSetImage::LabelValueVectorType& usedLabelValues, std::map& labelsMappingMap) { for (const auto& node : nodes) { mitk::ProgressBar::GetInstance()->Progress(); const auto& foundLabels = foundLabelsMap.at(node); mitk::LabelSetImage::LabelValueVectorType correctedLabelValues; mitk::CheckForLabelValueConflictsAndResolve(foundLabels, usedLabelValues, correctedLabelValues); mitk::LabelValueMappingVector mapping; std::transform(foundLabels.begin(), foundLabels.end(), correctedLabelValues.begin(), std::back_inserter(mapping), [](mitk::LabelSetImage::LabelValueType a, mitk::LabelSetImage::LabelValueType b) { return std::make_pair(a, b); }); labelsMappingMap.emplace(node, mapping); } } void QmitkConvertToMultiLabelSegmentationWidget::ConvertNodes(const QmitkNodeSelectionDialog::NodeList& nodes) { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); auto nonimageNodes = GetNonimageNodes(nodes); auto imageNodes = GetImageNodes(nodes); mitk::LabelSetImage::Pointer outputSeg; mitk::Image::Pointer refImage; const mitk::DataNode* refNode; if (m_Controls->radioAddToSeg->isChecked()) { outputSeg = dynamic_cast(m_Controls->outputSegSelector->GetSelectedNode()->GetData()); if (outputSeg->GetNumberOfLayers() > 0) { refImage = outputSeg->GetGroupImage(0); } else { //in case we work with a output seg, we need to generate a template image //reason is that the conversion filters used for surfaces or contours need images //as reference and MultiLabelSegmentations is currently empty. refImage = mitk::Image::New(); refImage->Initialize(mitk::MakePixelType(), *(outputSeg->GetTimeGeometry())); } } else { outputSeg = mitk::LabelSetImage::New(); auto inputNode = GetNodeWithLargestImageGeometry(m_Controls->inputNodesSelector->GetSelectedNodes()); if (nullptr != inputNode) { refNode = inputNode; refImage = dynamic_cast(inputNode->GetData()); outputSeg->Initialize(refImage); } else { refNode = m_Controls->refNodeSelector->GetSelectedNode(); refImage = dynamic_cast(refNode->GetData()); outputSeg->Initialize(refImage); } } //convert non-image nodes to images std::map preparedImageMap; std::map foundLabelsMap; for (const auto& node : nonimageNodes) { mitk::ProgressBar::GetInstance()->Progress(); mitk::Image::Pointer convertedImage; auto surface = dynamic_cast(node->GetData()); auto contourModel = dynamic_cast(node->GetData()); auto contourModelSet = dynamic_cast(node->GetData()); if (nullptr != surface) { convertedImage = ConvertSurfaceToImage(refImage, surface); } else if (nullptr != contourModelSet) { convertedImage = ConvertContourModelSetToImage(refImage, contourModelSet); } else if (nullptr != contourModel) { auto contourModelSet = mitk::ContourModelSet::New(); contourModelSet->AddContourModel(contourModel); convertedImage = ConvertContourModelSetToImage(refImage, contourModelSet); } else { mitkThrow() << "Invalid state of QmitkConvertToMultiLabelSegmentationWidget. At least one input is of invalid type, that should not be possible to select. Invalid node: " << *(node.GetPointer()); } if (convertedImage.IsNotNull()) { preparedImageMap.emplace(node, convertedImage); //all non-image data is converted to binary maps foundLabelsMap.emplace(node, mitk::LabelSetImage::LabelValueVectorType({ 1 })); } else { mitkThrow() << "Failed to convert an input. Failed node: " << *(node.GetPointer()); } } //prepare image nodes and get contained labels for (const auto& node : imageNodes) { mitk::ProgressBar::GetInstance()->Progress(); mitk::LabelSetImage::LabelValueVectorType foundLabels; mitk::ProgressBar::GetInstance()->Progress(); mitk::Image::Pointer convertedImage = mitk::ConvertImageToGroupImage(dynamic_cast(node->GetData()), foundLabels); preparedImageMap.emplace(node, convertedImage); foundLabelsMap.emplace(node, foundLabels); } //check for label collision and fix if needed mitk::LabelSetImage::LabelValueVectorType usedLabelValues = outputSeg->GetAllLabelValues(); std::map labelsMappingMap; CheckForLabelCollision(imageNodes, foundLabelsMap, usedLabelValues, labelsMappingMap); CheckForLabelCollision(nonimageNodes, foundLabelsMap, usedLabelValues, labelsMappingMap); //Ensure that we have the first layer to add mitk::LabelSetImage::GroupIndexType currentGroupIndex = 0; if (m_Controls->radioAddToSeg->isChecked() || 0 == outputSeg->GetNumberOfLayers()) { currentGroupIndex = outputSeg->AddLayer(); } //Transfer content and add labels for (const auto& node : imageNodes) { mitk::ProgressBar::GetInstance()->Progress(); if (m_Controls->radioSingleGroup->isChecked() && node != imageNodes.front()) currentGroupIndex = outputSeg->AddLayer(); const auto& labelsMapping = labelsMappingMap.at(node); for (auto [oldV, correctedV] : labelsMapping) { std::string name = "Value " + std::to_string(oldV); if (m_Controls->radioMergeGroup->isChecked()) name = node->GetName() + " " + name; auto label = mitk::LabelSetImageHelper::CreateNewLabel(outputSeg, name, true); label->SetValue(correctedV); outputSeg->AddLabel(label, currentGroupIndex, false, false); } mitk::TransferLabelContent(preparedImageMap.at(node), outputSeg->GetGroupImage(currentGroupIndex), outputSeg->GetConstLabelsByValue(outputSeg->GetLabelValuesByGroup(currentGroupIndex)), mitk::LabelSetImage::UNLABELED_VALUE, mitk::LabelSetImage::UNLABELED_VALUE, false, labelsMapping); } for (const auto& node : nonimageNodes) { mitk::ProgressBar::GetInstance()->Progress(); if (m_Controls->radioSingleGroup->isChecked() && (node != nonimageNodes.front() || !imageNodes.empty())) currentGroupIndex = outputSeg->AddLayer(); const auto& labelsMapping = labelsMappingMap.at(node); for (auto [oldV, correctedV] : labelsMapping) { auto label = mitk::LabelSetImageHelper::CreateNewLabel(outputSeg, node->GetName(), true); label->SetValue(correctedV); mitk::ColorProperty::ConstPointer colorProp = dynamic_cast(node->GetConstProperty("color").GetPointer()); if (colorProp.IsNotNull()) label->SetColor(colorProp->GetColor()); outputSeg->AddLabel(label, currentGroupIndex, false, false); } mitk::TransferLabelContent(preparedImageMap.at(node), outputSeg->GetGroupImage(currentGroupIndex), outputSeg->GetConstLabelsByValue(outputSeg->GetLabelValuesByGroup(currentGroupIndex)), mitk::LabelSetImage::UNLABELED_VALUE, mitk::LabelSetImage::UNLABELED_VALUE, false, labelsMapping); } if (m_Controls->radioAddToSeg->isChecked()) { m_Controls->outputSegSelector->GetSelectedNode()->Modified(); } else { mitk::DataNode::Pointer outNode = mitk::DataNode::New(); std::stringstream stream; stream << "ConvertedSeg"; if (nodes.size() == 1) { stream << "_" << nodes.front()->GetName(); } outNode->SetName(stream.str()); outNode->SetData(outputSeg); auto dataStorage = m_DataStorage.Lock(); dataStorage->Add(outNode); } mitk::ProgressBar::GetInstance()->Reset(); QApplication::restoreOverrideCursor(); } diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidget.cpp deleted file mode 100644 index 2b0aaaafc8..0000000000 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidget.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "QmitkDataSelectionWidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static mitk::NodePredicateBase::Pointer CreatePredicate(QmitkDataSelectionWidget::Predicate predicate) -{ - auto imageType = mitk::TNodePredicateDataType::New(); - auto labelSetImageType = mitk::TNodePredicateDataType::New(); - auto surfaceType = mitk::TNodePredicateDataType::New(); - auto contourModelType = mitk::TNodePredicateDataType::New(); - auto contourModelSetType = mitk::TNodePredicateDataType::New(); - auto nonLabelSetImageType = mitk::NodePredicateAnd::New(imageType, mitk::NodePredicateNot::New(labelSetImageType)); - auto nonHelperObject = mitk::NodePredicateNot::New(mitk::NodePredicateOr::New( - mitk::NodePredicateProperty::New("helper object"), - mitk::NodePredicateProperty::New("hidden object"))); - auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); - auto isSegmentation = mitk::NodePredicateProperty::New("segmentation", mitk::BoolProperty::New(true)); - auto isBinaryOrSegmentation = mitk::NodePredicateOr::New(isBinary, isSegmentation); - - mitk::NodePredicateBase::Pointer returnValue; - - switch(predicate) - { - case QmitkDataSelectionWidget::ImagePredicate: - returnValue = mitk::NodePredicateAnd::New( - mitk::NodePredicateNot::New(isBinaryOrSegmentation), - nonLabelSetImageType).GetPointer(); - break; - - case QmitkDataSelectionWidget::SegmentationPredicate: - returnValue = mitk::NodePredicateOr::New( - mitk::NodePredicateAnd::New(imageType, isBinaryOrSegmentation), - labelSetImageType).GetPointer(); - break; - - case QmitkDataSelectionWidget::SurfacePredicate: - returnValue = surfaceType.GetPointer(); - break; - - case QmitkDataSelectionWidget::ImageAndSegmentationPredicate: - returnValue = imageType.GetPointer(); - break; - - case QmitkDataSelectionWidget::ContourModelPredicate: - returnValue = mitk::NodePredicateOr::New( - contourModelSetType, - contourModelSetType).GetPointer(); - break; - - case QmitkDataSelectionWidget::SegmentationOrSurfacePredicate: - returnValue = mitk::NodePredicateOr::New( - mitk::NodePredicateAnd::New(imageType, isBinaryOrSegmentation), - labelSetImageType).GetPointer(); - returnValue = mitk::NodePredicateOr::New(returnValue, surfaceType).GetPointer(); - break; - - default: - assert(false && "Unknown predefined predicate!"); - return nullptr; - } - - return mitk::NodePredicateAnd::New(returnValue, nonHelperObject).GetPointer(); -} - -QmitkDataSelectionWidget::QmitkDataSelectionWidget(QWidget* parent) - : QWidget(parent) -{ - m_Controls.setupUi(this); - m_Controls.helpLabel->hide(); -} - -QmitkDataSelectionWidget::~QmitkDataSelectionWidget() -{ -} - -unsigned int QmitkDataSelectionWidget::AddDataSelection(QmitkDataSelectionWidget::Predicate predicate) -{ - QString hint = "Select node"; - - switch (predicate) - { - case QmitkDataSelectionWidget::ImagePredicate: - hint = "Select an image"; - break; - - case QmitkDataSelectionWidget::SegmentationPredicate: - hint = "Select a segmentation"; - break; - - case QmitkDataSelectionWidget::SurfacePredicate: - hint = "Select a surface"; - break; - - case QmitkDataSelectionWidget::ImageAndSegmentationPredicate: - hint = "Select an image or segmentation"; - break; - - case QmitkDataSelectionWidget::ContourModelPredicate: - hint = "Select a contour model"; - break; - - case QmitkDataSelectionWidget::SegmentationOrSurfacePredicate: - hint = "Select a segmentation or surface"; - break; - } - - return this->AddDataSelection("", hint, hint, "", predicate); -} - -unsigned int QmitkDataSelectionWidget::AddDataSelection(mitk::NodePredicateBase* predicate) -{ - return this->AddDataSelection("", "Select a node", "Select a node", "", predicate); -} - -unsigned int QmitkDataSelectionWidget::AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, QmitkDataSelectionWidget::Predicate predicate) -{ - return this->AddDataSelection(labelText, info, popupHint, popupTitel, CreatePredicate(predicate)); -} - -unsigned int QmitkDataSelectionWidget::AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, mitk::NodePredicateBase* predicate) -{ - int row = m_Controls.gridLayout->rowCount(); - - if (!labelText.isEmpty()) - { - QLabel* label = new QLabel(labelText, m_Controls.dataSelectionWidget); - label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - m_Controls.gridLayout->addWidget(label, row, 0); - } - - QmitkSingleNodeSelectionWidget* nodeSelection = new QmitkSingleNodeSelectionWidget(m_Controls.dataSelectionWidget); - - nodeSelection->SetSelectionIsOptional(false); - nodeSelection->SetInvalidInfo(info); - nodeSelection->SetPopUpTitel(popupTitel); - nodeSelection->SetPopUpHint(popupHint); - nodeSelection->SetDataStorage(this->GetDataStorage()); - nodeSelection->SetNodePredicate(predicate); - nodeSelection->SetAutoSelectNewNodes(true); - nodeSelection->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - nodeSelection->setMinimumSize(0, 40); - - connect(nodeSelection, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkDataSelectionWidget::OnSelectionChanged); - - m_Controls.gridLayout->addWidget(nodeSelection, row, 1); - - m_NodeSelectionWidgets.push_back(nodeSelection); - return static_cast(m_NodeSelectionWidgets.size() - 1); - return row; -} - -void QmitkDataSelectionWidget::SetDataStorage(mitk::DataStorage* dataStorage) -{ - if (m_DataStorage == dataStorage) - { - return; - } - - m_DataStorage = dataStorage; -} - -mitk::DataStorage::Pointer QmitkDataSelectionWidget::GetDataStorage() const -{ - return m_DataStorage.Lock(); -} - -mitk::DataNode::Pointer QmitkDataSelectionWidget::GetSelection(unsigned int index) -{ - assert(index < m_NodeSelectionWidgets.size()); - return m_NodeSelectionWidgets[index]->GetSelectedNode(); -} - -void QmitkDataSelectionWidget::SetPredicate(unsigned int index, Predicate predicate) -{ - this->SetPredicate(index, CreatePredicate(predicate)); -} - -void QmitkDataSelectionWidget::SetPredicate(unsigned int index, const mitk::NodePredicateBase* predicate) -{ - assert(index < m_NodeSelectionWidgets.size()); - m_NodeSelectionWidgets[index]->SetNodePredicate(predicate); -} - -const mitk::NodePredicateBase *QmitkDataSelectionWidget::GetPredicate(unsigned int index) const -{ - assert(index < m_NodeSelectionWidgets.size()); - return m_NodeSelectionWidgets[index]->GetNodePredicate(); -} - -void QmitkDataSelectionWidget::SetHelpText(const QString& text) -{ - if (!text.isEmpty()) - { - m_Controls.helpLabel->setText(text); - - if (!m_Controls.helpLabel->isVisible()) - m_Controls.helpLabel->show(); - } - else - { - m_Controls.helpLabel->hide(); - } -} - -void QmitkDataSelectionWidget::OnSelectionChanged(QList selection) -{ - std::vector::iterator it = std::find(m_NodeSelectionWidgets.begin(), m_NodeSelectionWidgets.end(), sender()); - assert(it != m_NodeSelectionWidgets.end()); - - const mitk::DataNode* result = nullptr; - if (!selection.empty()) - { - result = selection.front(); - } - emit SelectionChanged(std::distance(m_NodeSelectionWidgets.begin(), it), result); -} diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidget.h b/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidget.h deleted file mode 100644 index 4195fd235d..0000000000 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidget.h +++ /dev/null @@ -1,75 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef QmitkDataSelectionWidget_h -#define QmitkDataSelectionWidget_h - -#include - -#include -#include -#include -#include - -#include - -namespace mitk -{ - class NodePredicateBase; -} - -class QmitkSingleNodeSelectionWidget; - -class MITKSEGMENTATIONUI_EXPORT QmitkDataSelectionWidget : public QWidget -{ - Q_OBJECT - -public: - enum Predicate - { - ImagePredicate, - SegmentationPredicate, - SurfacePredicate, - ImageAndSegmentationPredicate, - ContourModelPredicate, - SegmentationOrSurfacePredicate - }; - - explicit QmitkDataSelectionWidget(QWidget* parent = nullptr); - ~QmitkDataSelectionWidget() override; - - unsigned int AddDataSelection(Predicate predicate); - unsigned int AddDataSelection(mitk::NodePredicateBase* predicate = nullptr); - unsigned int AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, Predicate predicate); - unsigned int AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, mitk::NodePredicateBase* predicate = nullptr); - - void SetDataStorage(mitk::DataStorage* dataStorage); - mitk::DataStorage::Pointer GetDataStorage() const; - mitk::DataNode::Pointer GetSelection(unsigned int index); - void SetPredicate(unsigned int index, Predicate predicate); - void SetPredicate(unsigned int index, const mitk::NodePredicateBase* predicate); - const mitk::NodePredicateBase *GetPredicate(unsigned int index) const; - void SetHelpText(const QString& text); - -signals: - void SelectionChanged(unsigned int index, const mitk::DataNode* selection); - -private slots: - void OnSelectionChanged(QList selection); - -private: - Ui::QmitkDataSelectionWidgetControls m_Controls; - mitk::WeakPointer m_DataStorage; - std::vector m_NodeSelectionWidgets; -}; - -#endif diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidgetControls.ui b/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidgetControls.ui deleted file mode 100644 index 6d6fcf1369..0000000000 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkDataSelectionWidgetControls.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - QmitkDataSelectionWidgetControls - - - - 0 - 0 - 333 - 191 - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - Data Selection - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - 0 - 0 - - - - color: red - - - - - - true - - - - - - - - - - - diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp index 884c5a79cd..188eedead7 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp @@ -1,389 +1,286 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageMaskingWidget.h" #include #include #include #include #include #include #include -#include #include #include -#include #include #include +#include +#include +#include +#include #include #include namespace { - bool IsSurface(const mitk::DataNode* dataNode) + mitk::NodePredicateBase::Pointer GetInputPredicate() { - if (nullptr != dataNode) - { - if (nullptr != dynamic_cast(dataNode->GetData())) - return true; - } + auto isImage = mitk::TNodePredicateDataType::New(); + auto isNotSeg = mitk::NodePredicateNot::New(mitk::GetMultiLabelSegmentationPredicate()); - return false; + auto isValidInput = mitk::NodePredicateAnd::New(isNotSeg, isImage); + isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); + isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); + return isValidInput.GetPointer(); } } -static const char* const HelpText = "Select an image and a segmentation or surface"; - QmitkImageMaskingWidget::QmitkImageMaskingWidget(mitk::DataStorage* dataStorage, QWidget* parent) - : QWidget(parent) + : QWidget(parent), m_DataStorage(dataStorage) { m_Controls = new Ui::QmitkImageMaskingWidgetControls; m_Controls->setupUi(this); - m_Controls->dataSelectionWidget->SetDataStorage(dataStorage); - m_Controls->dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::ImagePredicate); - m_Controls->dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::SegmentationOrSurfacePredicate); - m_Controls->dataSelectionWidget->SetHelpText(HelpText); + m_Controls->imageNodeSelector->SetDataStorage(dataStorage); + m_Controls->imageNodeSelector->SetNodePredicate(GetInputPredicate()); + m_Controls->imageNodeSelector->SetSelectionIsOptional(false); + m_Controls->imageNodeSelector->SetInvalidInfo(QStringLiteral("Please select an image for masking")); + m_Controls->imageNodeSelector->SetPopUpTitel(QStringLiteral("Select image")); + m_Controls->imageNodeSelector->SetPopUpHint(QStringLiteral("Select an image that you want to mask.")); - // T28795: Disable 2-d reference images since they do not work yet (segmentations are at least 3-d images with a single slice) - m_Controls->dataSelectionWidget->SetPredicate(0, mitk::NodePredicateAnd::New( - mitk::NodePredicateNot::New(mitk::NodePredicateDimension::New(2)), - m_Controls->dataSelectionWidget->GetPredicate(0))); + m_Controls->segNodeSelector->SetDataStorage(dataStorage); + m_Controls->segNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate()); + m_Controls->segNodeSelector->SetSelectionIsOptional(false); + m_Controls->segNodeSelector->SetInvalidInfo(QStringLiteral("Please select a segmentation and its label")); + m_Controls->segNodeSelector->SetPopUpTitel(QStringLiteral("Select segmentation")); + m_Controls->segNodeSelector->SetPopUpHint(QStringLiteral("Select the segmentation that should be used for masking.\nThe segmentation must have the same geometry as the image that should be masked.")); - this->EnableButtons(false); + this->ConfigureWidgets(); - connect(m_Controls->btnMaskImage, SIGNAL(clicked()), this, SLOT(OnMaskImagePressed())); - connect(m_Controls->rbnCustom, SIGNAL(toggled(bool)), this, SLOT(OnCustomValueButtonToggled(bool))); - connect(m_Controls->dataSelectionWidget, SIGNAL(SelectionChanged(unsigned int, const mitk::DataNode*)), - this, SLOT(OnSelectionChanged(unsigned int, const mitk::DataNode*))); + connect(m_Controls->imageNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkImageMaskingWidget::OnImageSelectionChanged); + connect(m_Controls->segNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkImageMaskingWidget::OnSegSelectionChanged); - if( m_Controls->dataSelectionWidget->GetSelection(0).IsNotNull() && - m_Controls->dataSelectionWidget->GetSelection(1).IsNotNull() ) - { - this->OnSelectionChanged(0, m_Controls->dataSelectionWidget->GetSelection(0)); - } + connect(m_Controls->btnMaskImage, &QPushButton::clicked, this, &QmitkImageMaskingWidget::OnMaskImagePressed); + connect(m_Controls->rbnCustom, &QRadioButton::toggled, this, &QmitkImageMaskingWidget::OnCustomValueButtonToggled); + + m_Controls->imageNodeSelector->SetAutoSelectNewNodes(true); + m_Controls->segNodeSelector->SetAutoSelectNewNodes(true); } QmitkImageMaskingWidget::~QmitkImageMaskingWidget() { + m_Controls->labelInspector->SetMultiLabelNode(nullptr); } -void QmitkImageMaskingWidget::OnSelectionChanged(unsigned int index, const mitk::DataNode *selection) +void QmitkImageMaskingWidget::OnImageSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { - auto *dataSelectionWidget = m_Controls->dataSelectionWidget; - auto node0 = dataSelectionWidget->GetSelection(0); - - if (index == 0) + auto imageNode = m_Controls->imageNodeSelector->GetSelectedNode(); + const mitk::BaseGeometry* refGeometry = nullptr; + const mitk::BaseData* inputImage = (nullptr != imageNode) ? imageNode->GetData() : nullptr; + if (nullptr != inputImage) { - dataSelectionWidget->SetPredicate(1, QmitkDataSelectionWidget::SegmentationOrSurfacePredicate); - - if (node0.IsNotNull()) - { - dataSelectionWidget->SetPredicate(1, mitk::NodePredicateAnd::New( - mitk::NodePredicateGeometry::New(node0->GetData()->GetGeometry()), - dataSelectionWidget->GetPredicate(1))); - } + refGeometry = inputImage->GetGeometry(); } - auto node1 = dataSelectionWidget->GetSelection(1); - - if (node0.IsNull() || node1.IsNull()) - { - dataSelectionWidget->SetHelpText(HelpText); - this->EnableButtons(false); - } - else - { - this->SelectionControl(index, selection); - } + m_Controls->segNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate(refGeometry)); + this->ConfigureWidgets(); } -void QmitkImageMaskingWidget::SelectionControl(unsigned int index, const mitk::DataNode* selection) +void QmitkImageMaskingWidget::OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { - QmitkDataSelectionWidget* dataSelectionWidget = m_Controls->dataSelectionWidget; - mitk::DataNode::Pointer node = dataSelectionWidget->GetSelection(index); - - //if Image-Masking is enabled, check if image-dimension of reference and binary image is identical - if( !IsSurface(dataSelectionWidget->GetSelection(1)) ) + auto node = m_Controls->segNodeSelector->GetSelectedNode(); + m_Controls->labelInspector->SetMultiLabelNode(node); + if (node.IsNotNull()) { - if( dataSelectionWidget->GetSelection(0) == dataSelectionWidget->GetSelection(1) ) + auto labelValues = m_Controls->labelInspector->GetMultiLabelSegmentation()->GetAllLabelValues(); + if (!labelValues.empty()) { - dataSelectionWidget->SetHelpText("Select two different images above"); - this->EnableButtons(false); - return; + m_Controls->labelInspector->SetSelectedLabel(labelValues.front()); } + } + this->ConfigureWidgets(); +} - else if( node.IsNotNull() && selection ) - { - mitk::Image::Pointer referenceImage = dynamic_cast ( dataSelectionWidget->GetSelection(0)->GetData() ); - mitk::Image::Pointer maskImage = dynamic_cast ( dataSelectionWidget->GetSelection(1)->GetData() ); +void QmitkImageMaskingWidget::ConfigureWidgets() +{ + auto iNode = m_Controls->imageNodeSelector->GetSelectedNode(); + auto sNode = m_Controls->segNodeSelector->GetSelectedNode(); - if (maskImage.IsNull()) - { - dataSelectionWidget->SetHelpText("Different image sizes cannot be masked"); - this->EnableButtons(false); - return; - } - } + bool enable = iNode.IsNotNull() && sNode.IsNotNull() && !m_Controls->labelInspector->GetSelectedLabels().empty(); - else - { - dataSelectionWidget->SetHelpText(HelpText); - return; - } - } - - dataSelectionWidget->SetHelpText(""); - this->EnableButtons(); + this->EnableButtons(enable); } void QmitkImageMaskingWidget::EnableButtons(bool enable) { m_Controls->grpBackgroundValue->setEnabled(enable); m_Controls->btnMaskImage->setEnabled(enable); } template void GetRange(const itk::Image*, double& bottom, double& top) { bottom = std::numeric_limits::lowest(); top = std::numeric_limits::max(); } void QmitkImageMaskingWidget::OnCustomValueButtonToggled(bool checked) { m_Controls->txtCustom->setEnabled(checked); } void QmitkImageMaskingWidget::OnMaskImagePressed() { //Disable Buttons during calculation and initialize Progressbar this->EnableButtons(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(4); mitk::ProgressBar::GetInstance()->Progress(); - QmitkDataSelectionWidget* dataSelectionWidget = m_Controls->dataSelectionWidget; - //create result image, get mask node and reference image mitk::Image::Pointer resultImage(nullptr); - mitk::DataNode::Pointer maskingNode = dataSelectionWidget->GetSelection(1); - mitk::Image::Pointer referenceImage = static_cast(dataSelectionWidget->GetSelection(0)->GetData()); - - if(referenceImage.IsNull() || maskingNode.IsNull() ) - { - MITK_ERROR << "Selection does not contain an image"; - QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain an image", QMessageBox::Ok ); - m_Controls->btnMaskImage->setEnabled(true); - return; - } + mitk::LabelSetImage::Pointer segmentation = m_Controls->labelInspector->GetMultiLabelSegmentation(); + mitk::Image::Pointer referenceImage = static_cast(m_Controls->imageNodeSelector->GetSelectedNode()->GetData()); - //Do Image-Masking - if (!IsSurface(maskingNode)) - { - mitk::ProgressBar::GetInstance()->Progress(); - - mitk::Image::Pointer maskImage = dynamic_cast ( maskingNode->GetData() ); - - if(maskImage.IsNull() ) - { - MITK_ERROR << "Selection does not contain a segmentation"; - QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain a segmentation", QMessageBox::Ok ); - this->EnableButtons(); - return; - } - - resultImage = this->MaskImage(referenceImage, maskImage); - } - - //Do Surface-Masking - else - { - mitk::ProgressBar::GetInstance()->Progress(); - - //1. convert surface to image - mitk::Surface::Pointer surface = dynamic_cast ( maskingNode->GetData() ); - - //TODO Get 3D Surface of current time step - - if(surface.IsNull()) - { - MITK_ERROR << "Selection does not contain a surface"; - QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain a surface", QMessageBox::Ok ); - this->EnableButtons(); - return; - } + mitk::ProgressBar::GetInstance()->Progress(); - mitk::Image::Pointer maskImage = this->ConvertSurfaceToImage( referenceImage, surface ); - - //2. mask reference image with mask image - if(maskImage.IsNotNull() && - referenceImage->GetLargestPossibleRegion().GetSize() == maskImage->GetLargestPossibleRegion().GetSize() ) - { - resultImage = this->MaskImage( referenceImage, maskImage ); - } - } + auto labelImage = mitk::CreateLabelMask(segmentation, m_Controls->labelInspector->GetSelectedLabels().front(), true); + resultImage = this->MaskImage(referenceImage, labelImage); mitk::ProgressBar::GetInstance()->Progress(); if( resultImage.IsNull() ) { MITK_ERROR << "Masking failed"; - QMessageBox::information( this, "Image and Surface Masking", "Masking failed. For more information please see logging window.", QMessageBox::Ok ); - this->EnableButtons(); + QMessageBox::information( this, "Image Masking", "Masking failed. For more information please see logging window.", QMessageBox::Ok ); + this->EnableButtons(true); mitk::ProgressBar::GetInstance()->Progress(4); return; } //Add result to data storage - this->AddToDataStorage( - dataSelectionWidget->GetDataStorage(), + this->AddToDataStorage(m_DataStorage.Lock(), resultImage, - dataSelectionWidget->GetSelection(0)->GetName() + "_" + dataSelectionWidget->GetSelection(1)->GetName(), - dataSelectionWidget->GetSelection(0)); + m_Controls->imageNodeSelector->GetSelectedNode()->GetName() + "_" + m_Controls->segNodeSelector->GetSelectedNode()->GetName(), + m_Controls->imageNodeSelector->GetSelectedNode()); - this->EnableButtons(); + this->EnableButtons(true); mitk::ProgressBar::GetInstance()->Progress(); } mitk::Image::Pointer QmitkImageMaskingWidget::MaskImage(mitk::Image::Pointer referenceImage, mitk::Image::Pointer maskImage ) { mitk::ScalarType backgroundValue = 0.0; if (m_Controls->rbnMinimum->isChecked()) { backgroundValue = referenceImage->GetStatistics()->GetScalarValueMin(); } else if (m_Controls->rbnCustom->isChecked()) { auto warningTitle = QStringLiteral("Invalid custom pixel value"); bool ok = false; auto originalBackgroundValue = m_Controls->txtCustom->text().toDouble(&ok); if (!ok) { // Input is not even a number QMessageBox::warning(nullptr, warningTitle, "Please enter a valid number as custom pixel value."); return nullptr; } else { // Clamp to the numerical limits of the pixel/component type double bottom, top; if (referenceImage->GetDimension() == 4) { AccessFixedDimensionByItk_n(referenceImage, GetRange, 4, (bottom, top)); } else { AccessByItk_n(referenceImage, GetRange, (bottom, top)); } backgroundValue = std::max(bottom, std::min(originalBackgroundValue, top)); // Get rid of decimals for integral numbers auto type = referenceImage->GetPixelType().GetComponentType(); if (type != itk::IOComponentEnum::FLOAT && type != itk::IOComponentEnum::DOUBLE) backgroundValue = std::round(backgroundValue); } // Ask the user for permission before correcting their input if (std::abs(originalBackgroundValue - backgroundValue) > 1e-4) { auto warningText = QString( "

The custom pixel value %1 lies not within the range of valid pixel values for the selected image.

" "

Apply the closest valid pixel value %2 instead?

").arg(originalBackgroundValue).arg(backgroundValue); auto ret = QMessageBox::warning( nullptr, warningTitle, warningText, QMessageBox::StandardButton::Apply | QMessageBox::StandardButton::Cancel, QMessageBox::StandardButton::Apply); if (QMessageBox::StandardButton::Apply != ret) return nullptr; m_Controls->txtCustom->setText(QString("%1").arg(backgroundValue)); } } auto maskFilter = mitk::MaskImageFilter::New(); maskFilter->SetInput(referenceImage); maskFilter->SetMask(maskImage); maskFilter->OverrideOutsideValueOn(); maskFilter->SetOutsideValue(backgroundValue); try { maskFilter->Update(); } catch(const itk::ExceptionObject& e) { MITK_ERROR << e.GetDescription(); return nullptr; } return maskFilter->GetOutput(); } -mitk::Image::Pointer QmitkImageMaskingWidget::ConvertSurfaceToImage( mitk::Image::Pointer image, mitk::Surface::Pointer surface ) -{ - mitk::ProgressBar::GetInstance()->AddStepsToDo(2); - mitk::ProgressBar::GetInstance()->Progress(); - - mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); - surfaceToImageFilter->MakeOutputBinaryOn(); - surfaceToImageFilter->SetInput(surface); - surfaceToImageFilter->SetImage(image); - try - { - surfaceToImageFilter->Update(); - } - catch(itk::ExceptionObject& excpt) - { - MITK_ERROR << excpt.GetDescription(); - return nullptr; - } - - mitk::ProgressBar::GetInstance()->Progress(); - mitk::Image::Pointer resultImage = mitk::Image::New(); - resultImage = surfaceToImageFilter->GetOutput(); - - return resultImage; -} - void QmitkImageMaskingWidget::AddToDataStorage(mitk::DataStorage::Pointer dataStorage, mitk::Image::Pointer segmentation, const std::string& name, mitk::DataNode::Pointer parent ) { if (dataStorage.IsNull()) { std::string exception = "Cannot add result to the data storage. Data storage invalid."; MITK_ERROR << "Masking failed: " << exception; QMessageBox::information(nullptr, "Masking failed", QString::fromStdString(exception)); } auto dataNode = mitk::DataNode::New(); dataNode->SetName(name); dataNode->SetData(segmentation); if (parent.IsNotNull()) { mitk::LevelWindow levelWindow; parent->GetLevelWindow(levelWindow); dataNode->SetLevelWindow(levelWindow); } dataStorage->Add(dataNode, parent); } diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.h b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.h index 3f82fcd570..4c4fc1a9be 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.h +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.h @@ -1,85 +1,83 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageMaskingWidget_h #define QmitkImageMaskingWidget_h #include #include -#include +#include #include namespace Ui { class QmitkImageMaskingWidgetControls; } namespace mitk { class Image; + class DataStorage; } /*! \brief QmitkImageMaskingWidget Tool masks an image with a binary image or a surface. The Method requires an image and a binary image mask or a surface. The input image and the binary image mask must be of the same size. Masking with a surface creates first a binary image of the surface and then use this for the masking of the input image. */ class MITKSEGMENTATIONUI_EXPORT QmitkImageMaskingWidget : public QWidget { Q_OBJECT public: /** @brief Default constructor, including creation of GUI elements and signals/slots connections. */ explicit QmitkImageMaskingWidget(mitk::DataStorage* dataStorage, QWidget* parent = nullptr); /** @brief Default destructor. */ ~QmitkImageMaskingWidget() override; -private slots: +private: + + /** @brief This slot is called if the image selection changed.*/ + void OnImageSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/); - /** @brief This slot is called if the selection in the workbench is changed. */ - void OnSelectionChanged(unsigned int index, const mitk::DataNode* selection); + /** @brief This slot is called if the segmentation selection changed.*/ + void OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/); /** @brief This slot is called if user activates the button to mask an image. */ void OnMaskImagePressed(); /** @brief This slot is called if the user toggles the "Custom" radio button. */ void OnCustomValueButtonToggled(bool checked); -private: - - /** @brief Check if selections is valid. */ - void SelectionControl( unsigned int index, const mitk::DataNode* selection); - - /** @brief Enable buttons if data selection is valid. */ - void EnableButtons(bool enable = true); + /** @brief Configure the widgets according to the internal state. */ + void ConfigureWidgets(); + void EnableButtons(bool enable); /** @brief Mask an image with a given binary mask. Note that the input image and the mask image must be of the same size. */ itk::SmartPointer MaskImage(itk::SmartPointer referenceImage, itk::SmartPointer maskImage ); - /** @brief Convert a surface into an binary image. */ - itk::SmartPointer ConvertSurfaceToImage( itk::SmartPointer image, mitk::Surface::Pointer surface ); - /** @brief Adds a new data object to the DataStorage.*/ void AddToDataStorage(mitk::DataStorage::Pointer dataStorage, itk::SmartPointer segmentation, const std::string& name, mitk::DataNode::Pointer parent = nullptr); + mitk::WeakPointer m_DataStorage; Ui::QmitkImageMaskingWidgetControls* m_Controls; }; #endif diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidgetControls.ui b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidgetControls.ui index 6d0bfa0eaf..0d929be21e 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidgetControls.ui +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidgetControls.ui @@ -1,109 +1,114 @@ QmitkImageMaskingWidgetControls 0 0 238 329 - - - - 0 - 0 - - - + + + + + + + Background value Zero true Minimum 0 0 Custom: false 0 Mask Qt::Vertical 20 40 - QmitkDataSelectionWidget + QmitkSingleNodeSelectionWidget + QWidget +
QmitkSingleNodeSelectionWidget.h
+ 1 +
+ + QmitkMultiLabelInspector QWidget -
QmitkDataSelectionWidget.h
+
QmitkMultiLabelInspector.h
1
diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index 9a20396491..2d95513cb3 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,133 +1,130 @@ set(CPP_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.cpp Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkEditableContourToolGUIBase.cpp Qmitk/QmitkGrowCutToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkLassoToolGUI.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitknnUNetFolderParser.cpp Qmitk/QmitknnUNetToolGUI.cpp Qmitk/QmitknnUNetWorker.cpp Qmitk/QmitknnUNetGPU.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkStaticDynamicSegmentationDialog.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp Qmitk/QmitkSegmentationTaskListWidget.cpp Qmitk/QmitkTotalSegmentatorToolGUI.cpp Qmitk/QmitkSetupVirtualEnvUtil.cpp Qmitk/QmitkMultiLabelInspector.cpp Qmitk/QmitkMultiLabelManager.cpp Qmitk/QmitkMultiLabelTreeModel.cpp Qmitk/QmitkMultiLabelTreeView.cpp Qmitk/QmitkMultiLabelPresetHelper.cpp Qmitk/QmitkLabelColorItemDelegate.cpp Qmitk/QmitkLabelToggleItemDelegate.cpp Qmitk/QmitkFindSegmentationTaskDialog.cpp Qmitk/QmitkSegmentAnythingToolGUI.cpp Qmitk/QmitkMonaiLabelToolGUI.cpp Qmitk/QmitkMonaiLabel2DToolGUI.cpp Qmitk/QmitkMonaiLabel3DToolGUI.cpp SegmentationUtilities/QmitkBooleanOperationsWidget.cpp SegmentationUtilities/QmitkImageMaskingWidget.cpp SegmentationUtilities/QmitkMorphologicalOperationsWidget.cpp - SegmentationUtilities/QmitkDataSelectionWidget.cpp SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp SegmentationUtilities/QmitkExtractFromMultiLabelSegmentationWidget.cpp ) set(H_FILES Qmitk/QmitkMultiLabelPresetHelper.h ) set(MOC_H_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.h Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkEditableContourToolGUIBase.h Qmitk/QmitkGrowCutToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkLassoToolGUI.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitknnUNetFolderParser.h Qmitk/QmitknnUNetToolGUI.h Qmitk/QmitknnUNetGPU.h Qmitk/QmitknnUNetWorker.h Qmitk/QmitknnUNetEnsembleLayout.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkStaticDynamicSegmentationDialog.h Qmitk/QmitkSimpleLabelSetListWidget.h Qmitk/QmitkSegmentationTaskListWidget.h Qmitk/QmitkTotalSegmentatorToolGUI.h Qmitk/QmitkSetupVirtualEnvUtil.h Qmitk/QmitkMultiLabelInspector.h Qmitk/QmitkMultiLabelManager.h Qmitk/QmitkMultiLabelTreeModel.h Qmitk/QmitkMultiLabelTreeView.h Qmitk/QmitkLabelColorItemDelegate.h Qmitk/QmitkLabelToggleItemDelegate.h Qmitk/QmitkFindSegmentationTaskDialog.h Qmitk/QmitkSegmentAnythingToolGUI.h Qmitk/QmitkMonaiLabelToolGUI.h Qmitk/QmitkMonaiLabel2DToolGUI.h Qmitk/QmitkMonaiLabel3DToolGUI.h SegmentationUtilities/QmitkBooleanOperationsWidget.h SegmentationUtilities/QmitkImageMaskingWidget.h SegmentationUtilities/QmitkMorphologicalOperationsWidget.h - SegmentationUtilities/QmitkDataSelectionWidget.h SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.h SegmentationUtilities/QmitkExtractFromMultiLabelSegmentationWidget.h ) set(UI_FILES Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkGrowCutToolWidgetControls.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitknnUNetToolGUIControls.ui Qmitk/QmitkEditableContourToolGUIControls.ui Qmitk/QmitkSegmentationTaskListWidget.ui Qmitk/QmitkTotalSegmentatorGUIControls.ui Qmitk/QmitkMultiLabelInspectorControls.ui Qmitk/QmitkMultiLabelManagerControls.ui Qmitk/QmitkFindSegmentationTaskDialog.ui Qmitk/QmitkSegmentAnythingGUIControls.ui Qmitk/QmitkMonaiLabelToolGUIControls.ui SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui SegmentationUtilities/QmitkImageMaskingWidgetControls.ui SegmentationUtilities/QmitkMorphologicalOperationsWidgetControls.ui - SegmentationUtilities/QmitkDataSelectionWidgetControls.ui SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidgetControls.ui SegmentationUtilities/QmitkExtractFromMultiLabelSegmentationWidgetControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Modules/SurfaceInterpolation/Testing/files.cmake b/Modules/SurfaceInterpolation/Testing/files.cmake index f126978731..a7062411d5 100644 --- a/Modules/SurfaceInterpolation/Testing/files.cmake +++ b/Modules/SurfaceInterpolation/Testing/files.cmake @@ -1,8 +1,7 @@ set(MODULE_TESTS mitkComputeContourSetNormalsFilterTest.cpp mitkCreateDistanceImageFromSurfaceFilterTest.cpp mitkImageToPointCloudFilterTest.cpp - mitkPointCloudScoringFilterTest.cpp mitkReduceContourSetFilterTest.cpp mitkSurfaceInterpolationControllerTest.cpp ) diff --git a/Modules/SurfaceInterpolation/Testing/mitkPointCloudScoringFilterTest.cpp b/Modules/SurfaceInterpolation/Testing/mitkPointCloudScoringFilterTest.cpp deleted file mode 100644 index da1040dde7..0000000000 --- a/Modules/SurfaceInterpolation/Testing/mitkPointCloudScoringFilterTest.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkTestingMacros.h" -#include -#include -#include - -#include - -#include -#include -#include - -class mitkPointCloudScoringFilterTestSuite : public mitk::TestFixture -{ - CPPUNIT_TEST_SUITE(mitkPointCloudScoringFilterTestSuite); - MITK_TEST(testPointCloudScoringFilterInitialization); - MITK_TEST(testInputs); - MITK_TEST(testOutput); - MITK_TEST(testScoring); - CPPUNIT_TEST_SUITE_END(); - -private: - mitk::UnstructuredGrid::Pointer m_UnstructuredGrid1; - mitk::UnstructuredGrid::Pointer m_UnstructuredGrid2; - -public: - void setUp() override - { - m_UnstructuredGrid1 = mitk::UnstructuredGrid::New(); - m_UnstructuredGrid2 = mitk::UnstructuredGrid::New(); - - vtkSmartPointer vtkGrid1 = vtkSmartPointer::New(); - vtkSmartPointer vtkGrid2 = vtkSmartPointer::New(); - vtkSmartPointer points1 = vtkSmartPointer::New(); - vtkSmartPointer points2 = vtkSmartPointer::New(); - - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) - { - for (int k = 0; k < 3; k++) - { - mitk::Point3D point; - point[0] = i; - point[1] = j; - point[2] = k; - - points1->InsertNextPoint(point[0], point[1], point[2]); - points2->InsertNextPoint(point[0] + i, point[1] + i, point[2] + i); - } - } - } - - vtkGrid1->SetPoints(points1); - vtkGrid2->SetPoints(points2); - m_UnstructuredGrid1->SetVtkUnstructuredGrid(vtkGrid1); - m_UnstructuredGrid2->SetVtkUnstructuredGrid(vtkGrid2); - } - - void testPointCloudScoringFilterInitialization() - { - mitk::PointCloudScoringFilter::Pointer testFilter = mitk::PointCloudScoringFilter::New(); - CPPUNIT_ASSERT_MESSAGE("Testing instantiation of test object", testFilter.IsNotNull()); - } - - void testInputs() - { - mitk::PointCloudScoringFilter::Pointer testFilter = mitk::PointCloudScoringFilter::New(); - testFilter->SetInput(0, m_UnstructuredGrid1); - testFilter->SetInput(1, m_UnstructuredGrid2); - - mitk::UnstructuredGrid::Pointer uGrid1 = - dynamic_cast(testFilter->GetInputs().at(0).GetPointer()); - mitk::UnstructuredGrid::Pointer uGrid2 = - dynamic_cast(testFilter->GetInputs().at(1).GetPointer()); - - CPPUNIT_ASSERT_MESSAGE("Testing the first input", uGrid1 == m_UnstructuredGrid1); - CPPUNIT_ASSERT_MESSAGE("Testing the second input", uGrid2 == m_UnstructuredGrid2); - } - - void testOutput() - { - mitk::PointCloudScoringFilter::Pointer testFilter = mitk::PointCloudScoringFilter::New(); - testFilter->SetInput(0, m_UnstructuredGrid1); - testFilter->SetInput(1, m_UnstructuredGrid2); - testFilter->Update(); - - if (!testFilter->GetOutput()->GetVtkUnstructuredGrid()) - std::cout << "ITS EMPTY1!" << std::endl; - - CPPUNIT_ASSERT_MESSAGE("Testing mitkUnstructuredGrid generation!", - testFilter->GetOutput()->GetVtkUnstructuredGrid() != nullptr); - } - - void testScoring() - { - mitk::PointCloudScoringFilter::Pointer testFilter = mitk::PointCloudScoringFilter::New(); - testFilter->SetInput(0, m_UnstructuredGrid1); - testFilter->SetInput(1, m_UnstructuredGrid2); - testFilter->Update(); - - if (!testFilter->GetOutput()->GetVtkUnstructuredGrid()) - std::cout << "ITS EMPTY2!" << std::endl; - - mitk::UnstructuredGrid::Pointer outpGrid = testFilter->GetOutput(); - - int numBefore = m_UnstructuredGrid1->GetVtkUnstructuredGrid()->GetNumberOfPoints(); - int numAfter = outpGrid->GetVtkUnstructuredGrid()->GetNumberOfPoints(); - - CPPUNIT_ASSERT_MESSAGE("Testing grid scoring", numBefore > numAfter); - } -}; - -MITK_TEST_SUITE_REGISTRATION(mitkPointCloudScoringFilter) diff --git a/Modules/SurfaceInterpolation/files.cmake b/Modules/SurfaceInterpolation/files.cmake index dcd61f705e..b39a00e27a 100644 --- a/Modules/SurfaceInterpolation/files.cmake +++ b/Modules/SurfaceInterpolation/files.cmake @@ -1,9 +1,8 @@ set(CPP_FILES mitkComputeContourSetNormalsFilter.cpp mitkCreateDistanceImageFromSurfaceFilter.cpp mitkImageToPointCloudFilter.cpp mitkPlaneProposer.cpp - mitkPointCloudScoringFilter.cpp mitkReduceContourSetFilter.cpp mitkSurfaceInterpolationController.cpp ) diff --git a/Modules/SurfaceInterpolation/mitkPointCloudScoringFilter.cpp b/Modules/SurfaceInterpolation/mitkPointCloudScoringFilter.cpp deleted file mode 100644 index 3da5b53c42..0000000000 --- a/Modules/SurfaceInterpolation/mitkPointCloudScoringFilter.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkPointCloudScoringFilter.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -mitk::PointCloudScoringFilter::PointCloudScoringFilter() : m_NumberOfOutpPoints(0) -{ - this->SetNumberOfIndexedOutputs(1); -} - -mitk::PointCloudScoringFilter::~PointCloudScoringFilter() -{ -} - -void mitk::PointCloudScoringFilter::GenerateData() -{ - if (UnstructuredGridToUnstructuredGridFilter::GetNumberOfInputs() != 2) - { - MITK_ERROR << "Not enough input arguments for PointCloudScoringFilter" << std::endl; - return; - } - - DataObjectPointerArray inputs = UnstructuredGridToUnstructuredGridFilter::GetInputs(); - mitk::UnstructuredGrid::Pointer edgeGrid = dynamic_cast(inputs.at(0).GetPointer()); - mitk::UnstructuredGrid::Pointer segmGrid = dynamic_cast(inputs.at(1).GetPointer()); - - if (edgeGrid->IsEmpty() || segmGrid->IsEmpty()) - { - if (edgeGrid->IsEmpty()) - MITK_ERROR << "edgeGrid is empty" << std::endl; - if (segmGrid->IsEmpty()) - MITK_ERROR << "segmGrid is empty" << std::endl; - } - - if (m_FilteredScores.size() > 0) - m_FilteredScores.clear(); - - vtkSmartPointer edgevtkGrid = edgeGrid->GetVtkUnstructuredGrid(); - vtkSmartPointer segmvtkGrid = segmGrid->GetVtkUnstructuredGrid(); - - // KdTree from here - vtkSmartPointer kdPoints = vtkSmartPointer::New(); - vtkSmartPointer kdTree = vtkSmartPointer::New(); - - for (int i = 0; i < edgevtkGrid->GetNumberOfPoints(); i++) - { - kdPoints->InsertNextPoint(edgevtkGrid->GetPoint(i)); - } - - kdTree->BuildLocatorFromPoints(kdPoints); - // KdTree until here - - vtkSmartPointer points = vtkSmartPointer::New(); - - for (int i = 0; i < segmvtkGrid->GetNumberOfPoints(); i++) - { - points->InsertNextPoint(segmvtkGrid->GetPoint(i)); - } - - std::vector score; - std::vector distances; - - double dist_glob = 0.0; - double dist = 0.0; - - for (int i = 0; i < points->GetNumberOfPoints(); i++) - { - double point[3]; - points->GetPoint(i, point); - kdTree->FindClosestPoint(point[0], point[1], point[2], dist); - dist_glob += dist; - distances.push_back(dist); - score.push_back(std::make_pair(i, dist)); - } - - double avg = dist_glob / points->GetNumberOfPoints(); - - double tmpVar = 0.0; - double highest = 0.0; - - for (unsigned int i = 0; i < distances.size(); i++) - { - tmpVar = tmpVar + ((distances.at(i) - avg) * (distances.at(i) - avg)); - if (distances.at(i) > highest) - highest = distances.at(i); - } - - // CUBIC MEAN - double cubicAll = 0.0; - for (unsigned i = 0; i < score.size(); i++) - { - cubicAll = cubicAll + score.at(i).second * score.at(i).second * score.at(i).second; - } - double root2 = cubicAll / static_cast(score.size()); - double cubic = pow(root2, 1.0 / 3.0); - // CUBIC END - - double metricValue = cubic; - - for (unsigned int i = 0; i < score.size(); i++) - { - if (score.at(i).second > metricValue) - { - m_FilteredScores.push_back(std::make_pair(score.at(i).first, score.at(i).second)); - } - } - - m_NumberOfOutpPoints = m_FilteredScores.size(); - - vtkSmartPointer filteredPoints = vtkSmartPointer::New(); - - // storing the distances in the uGrid PointData - vtkSmartPointer pointDataDistances = vtkSmartPointer::New(); - pointDataDistances->SetNumberOfComponents(1); - pointDataDistances->SetNumberOfTuples(m_FilteredScores.size()); - pointDataDistances->SetName("Distances"); - - for (unsigned int i = 0; i < m_FilteredScores.size(); i++) - { - mitk::Point3D point; - point = segmvtkGrid->GetPoint(m_FilteredScores.at(i).first); - filteredPoints->InsertNextPoint(point[0], point[1], point[2]); - if (score.at(i).second > 0.001) - { - double dist[1] = {score.at(i).second}; - pointDataDistances->InsertTuple(i, dist); - } - else - { - double dist[1] = {0.0}; - pointDataDistances->InsertTuple(i, dist); - } - } - - unsigned int numPoints = filteredPoints->GetNumberOfPoints(); - - vtkSmartPointer verts = vtkSmartPointer::New(); - - verts->GetPointIds()->SetNumberOfIds(numPoints); - for (unsigned int i = 0; i < numPoints; i++) - { - verts->GetPointIds()->SetId(i, i); - } - - vtkSmartPointer uGrid = vtkSmartPointer::New(); - uGrid->Allocate(1); - - uGrid->InsertNextCell(verts->GetCellType(), verts->GetPointIds()); - uGrid->SetPoints(filteredPoints); - uGrid->GetPointData()->AddArray(pointDataDistances); - - mitk::UnstructuredGrid::Pointer outputGrid = mitk::UnstructuredGrid::New(); - outputGrid->SetVtkUnstructuredGrid(uGrid); - this->SetNthOutput(0, outputGrid); - - score.clear(); - distances.clear(); -} - -void mitk::PointCloudScoringFilter::GenerateOutputInformation() -{ - Superclass::GenerateOutputInformation(); -} diff --git a/Modules/SurfaceInterpolation/mitkPointCloudScoringFilter.h b/Modules/SurfaceInterpolation/mitkPointCloudScoringFilter.h deleted file mode 100644 index fc33e4819f..0000000000 --- a/Modules/SurfaceInterpolation/mitkPointCloudScoringFilter.h +++ /dev/null @@ -1,72 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkPointCloudScoringFilter_h -#define mitkPointCloudScoringFilter_h - -#include -#include - -namespace mitk -{ - class UnstructuredGrid; - - /** - * @brief Scores an UnstructuredGrid as good as one matches to the other. - * - * The result UnstructureGrid of the filter are the points where the distance to - * the closest point of the other UnstructuredGrid is higher than the average - * distance from all points to their closest neighbours of the other - * UnstructuredGrid. - * The second input is the UnstructuredGrid, which you want to score. All Points - * of the output UnstructuredGrid are from the second input. - */ - class MITKSURFACEINTERPOLATION_EXPORT PointCloudScoringFilter : public UnstructuredGridToUnstructuredGridFilter - { - public: - typedef std::pair ScorePair; - - mitkClassMacro(PointCloudScoringFilter, UnstructuredGridToUnstructuredGridFilter); - - itkFactorylessNewMacro(Self); - - /** Number of Points of the scored UnstructuredGrid. These points are far away - * from their neighbours */ - itkGetMacro(NumberOfOutpPoints, int); - - /** A vector in which the point IDs and their distance to their neighbours - * is stored */ - itkGetMacro(FilteredScores, std::vector); - - protected : - - /** is called by the Update() method */ - void GenerateData() override; - - /** Defines the output */ - void GenerateOutputInformation() override; - - /** Constructor */ - PointCloudScoringFilter(); - - /** Destructor */ - ~PointCloudScoringFilter() override; - - private: - /** The Point IDs and their distance to their neighbours */ - std::vector m_FilteredScores; - - /** The number of points which are far aways from their neighbours */ - int m_NumberOfOutpPoints; - }; -} -#endif diff --git a/Plugins/org.blueberry.ui.qt/resources/darkstyle.qss b/Plugins/org.blueberry.ui.qt/resources/darkstyle.qss index f2b1f818d3..c064d49846 100644 --- a/Plugins/org.blueberry.ui.qt/resources/darkstyle.qss +++ b/Plugins/org.blueberry.ui.qt/resources/darkstyle.qss @@ -1,614 +1,622 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ /* iconColor = #c7c7c7 <- This line is parsed by MITK iconAccentColor = #d4821e <- This line is parsed by MITK */ font.warning { color: #ff5c33; font-weight: bold; } font.normal { color: #f1f1f1; } font.highlight { color: #d4821e; } font.disabled { color: #656565; } QWidget { background-color: #2d2d30; border: none; color: #f1f1f1; } QWidget:disabled { background-color: #2d2d30; border-color: #434346; color: #656565; } QStackedWidget { background-color: transparent; } QAbstractButton:hover, QComboBox:hover, QLineEdit:hover, QAbstractSpinBox:hover { background-color: #3f3f46; } QAbstractButton:pressed, QAbstractButton:checked { background-color: #434346; } QPushButton { border: 1px solid #3f3f46; padding: 4px 8px; } QPushButton:pressed { border: 1px solid #434346; } QPushButton:checked, QToolButton:checked { border: 1px solid #007acc; } QToolButton { padding: 2px; } QToolBar QToolButton { padding: 4px; } QToolButton#qt_toolbar_ext_button { background-color: transparent; border: none; min-width: 12px; max-width: 12px; padding: 0; qproperty-icon: url(:/org.blueberry.ui.qt/dark/tight-right-arrow-textcolor.svg); } QToolBox::tab { border: 1px solid #3f3f46; } QToolBox::tab:hover { background-color: #434346; } QToolBox::tab:selected { background-color: #1c97ea; border: 1px solid #1c97ea; } QAbstractItemView { alternate-background-color: #1b1b1c; background-color: #252526; } +QAbstractItemView:disabled { + background-color: #1b1b1c; +} + QAbstractItemView::item { color: #f1f1f1; } QAbstractItemView::item:selected { background-color: #1c97ea; } +QAbstractItemView::item:disabled { + color: #656565; +} + QHeaderView::section { background-color: #2d2d30; border: 1px solid transparent; } QHeaderView::section:horizontal { border-right: 1px solid #3f3f46; } QHeaderView::section:vertical { border-bottom: 1px solid #3f3f46; } QHeaderView::section:vertical:checked { background-color: #1c97ea; } QHeaderView::section:vertical:pressed { background-color: #1c97ea; font-weight: bold; } QHeaderView::down-arrow { image: url(:/org.blueberry.ui.qt/dark/down-arrow.svg); height: 16px; width: 16px; } QHeaderView::down-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/down-arrow-hover.svg); } QHeaderView::down-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/down-arrow-pressed.svg); } QHeaderView::up-arrow { image: url(:/org.blueberry.ui.qt/dark/up-arrow.svg); height: 16px; width: 16px; } QHeaderView::up-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/up-arrow-hover.svg); } QHeaderView::up-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/up-arrow-pressed.svg); } QGroupBox { border: 1px solid #434346; margin-top: 8px; padding-top: 8px; } QGroupBox, QGroupBox:disabled { background-color: #252526; } QGroupBox::title { padding: 0 4px; subcontrol-origin: margin; subcontrol-position: top center; } QComboBox, QLineEdit, QAbstractSpinBox { background-color: #333337; border: 1px solid #434346; } QComboBox QAbstractItemView { border: 1px solid #333337; selection-background-color: #3f3f46; } QComboBox::drop-down { image: url(:/org.blueberry.ui.qt/dark/down-arrow.svg); subcontrol-origin: margin; subcontrol-position: right; width: 12px; } QComboBox::drop-down:hover { background-color: #1f1f20; border-left: 1px solid #007acc; image: url(:/org.blueberry.ui.qt/dark/down-arrow-pressed.svg); } QAbstractSpinBox::up-button, QAbstractSpinBox::down-button { background-color: transparent; border: none; height: 9px; } QAbstractSpinBox::up-button:hover, QAbstractSpinBox::down-button:hover { background-color: #1f1f20; } QAbstractSpinBox::up-arrow { image: url(:/org.blueberry.ui.qt/dark/tight-up-arrow.svg); } QAbstractSpinBox::up-arrow:disabled, QAbstractSpinBox::up-arrow:off { image: url(:/org.blueberry.ui.qt/dark/tight-up-arrow-disabled.svg); } QAbstractSpinBox::up-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/tight-up-arrow-hover.svg); } QAbstractSpinBox::up-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/tight-up-arrow-pressed.svg); } QAbstractSpinBox::down-arrow { image: url(:/org.blueberry.ui.qt/dark/tight-down-arrow.svg); } QAbstractSpinBox::down-arrow:disabled, QAbstractSpinBox::down-arrow:off { image: url(:/org.blueberry.ui.qt/dark/tight-down-arrow-disabled.svg); } QAbstractSpinBox::down-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/tight-down-arrow-hover.svg); } QAbstractSpinBox::down-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/tight-down-arrow-pressed.svg); } QCheckBox, QCheckBox:hover, QCheckBox:disabled, QCheckBox:checked, QRadioButton, QRadioButton:hover, QRadioButton:disabled, QRadioButton:checked { background-color: none; } QCheckBox::indicator, QRadioButton::indicator { height: 13px; width: 13px; } QCheckBox::indicator:unchecked { image: url(:/org.blueberry.ui.qt/dark/checkbox-unchecked.svg); } QCheckBox::indicator:unchecked:hover { image: url(:/org.blueberry.ui.qt/dark/checkbox-unchecked-hover.svg); } QCheckBox::indicator:unchecked:disabled { image: url(:/org.blueberry.ui.qt/dark/checkbox-unchecked-disabled.svg); } QCheckBox::indicator:checked { image: url(:/org.blueberry.ui.qt/dark/checkbox-checked.svg); } QCheckBox::indicator:checked:hover { image: url(:/org.blueberry.ui.qt/dark/checkbox-checked-hover.svg); } QCheckBox::indicator:checked:disabled { image: url(:/org.blueberry.ui.qt/dark/checkbox-checked-disabled.svg); } QRadioButton::indicator:unchecked { image: url(:/org.blueberry.ui.qt/dark/radiobutton-unchecked.svg); } QRadioButton::indicator:unchecked:hover { image: url(:/org.blueberry.ui.qt/dark/radiobutton-unchecked-hover.svg); } QRadioButton::indicator:unchecked:disabled { image: url(:/org.blueberry.ui.qt/dark/radiobutton-unchecked-disabled.svg); } QRadioButton::indicator:checked { image: url(:/org.blueberry.ui.qt/dark/radiobutton-checked.svg); } QRadioButton::indicator:checked:hover { image: url(:/org.blueberry.ui.qt/dark/radiobutton-checked-hover.svg); } QRadioButton::indicator:checked:disabled { image: url(:/org.blueberry.ui.qt/dark/radiobutton-checked-disabled.svg); } QSlider::groove { background-color: #686868; } QSlider::groove:hover { background-color: #9e9e9e; } QSlider::groove:horizontal { height: 3px; } QSlider::groove:vertical { width: 3px; } QSlider::handle { background-color: #686868; } QSlider::handle:hover { background-color: #1c97ea; } QSlider::handle:pressed { background-color: #007acc; } QSlider::handle:horizontal { margin: -8px 0; width: 8px; } QSlider::handle::vertical { margin: 0 -8px; height: 8px; } QLineEdit:hover { border: 1px solid #2b7de1; } QLabel, QLabel:disabled { background-color: none; } QMenu { border: 1px solid #3e3e40; } QMenu QWidget { background-color: #1b1b1c; } QMenu::item { background-color: #1b1b1c; } QMenu::item:selected { background-color: #333334; } QMenu::separator { height: 1px; background-color: #3e3e40; } QMenuBar::item:selected { background-color: #3e3e40; } QScrollBar { background-color: #3e3e42; } QScrollBar:horizontal { height: 18px; margin: 0 18px 0 18px; } QScrollBar:vertical { width: 18px; margin: 18px 0 18px 0; } QScrollBar::handle { background-color: #686868; } QScrollBar::handle:hover { background-color: #9e9e9e; } QScrollBar::handle:pressed { background-color: #efebef; } QScrollBar::handle:horizontal { min-width: 18px; margin: 4px 0 5px 0; } QScrollBar::handle:vertical { min-height: 18px; margin: 0 5px 0 4px; } QScrollBar::add-page, QScrollBar::sub-page { background-color: none; } QScrollBar::add-line, QScrollBar::sub-line { background-color: #3e3e42; subcontrol-origin: margin; } QScrollBar::add-line:horizontal { subcontrol-position: right; width: 18px; } QScrollBar::sub-line:horizontal { subcontrol-position: left; width: 18px; } QScrollBar::add-line:vertical { subcontrol-position: bottom; height: 18px; } QScrollBar::sub-line:vertical { subcontrol-position: top; height: 18px; } QScrollBar::up-arrow, QScrollBar::right-arrow, QScrollBar:down-arrow, QScrollBar:left-arrow { width: 18px; height: 18px; } QScrollBar::down-arrow { image: url(:/org.blueberry.ui.qt/dark/down-arrow.svg); } QScrollBar::down-arrow:disabled { image: url(:/org.blueberry.ui.qt/dark/down-arrow-disabled.svg); } QScrollBar::down-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/down-arrow-hover.svg); } QScrollBar::down-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/down-arrow-pressed.svg); } QScrollBar::left-arrow { image: url(:/org.blueberry.ui.qt/dark/left-arrow.svg); } QScrollBar::left-arrow:disabled { image: url(:/org.blueberry.ui.qt/dark/left-arrow-disabled.svg); } QScrollBar::left-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/left-arrow-hover.svg); } QScrollBar::left-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/left-arrow-pressed.svg); } QScrollBar::right-arrow { image: url(:/org.blueberry.ui.qt/dark/right-arrow.svg); } QScrollBar::right-arrow:disabled { image: url(:/org.blueberry.ui.qt/dark/right-arrow-disabled.svg); } QScrollBar::right-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/right-arrow-hover.svg); } QScrollBar::right-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/right-arrow-pressed.svg); } QScrollBar::up-arrow { image: url(:/org.blueberry.ui.qt/dark/up-arrow.svg); } QScrollBar::up-arrow:disabled { image: url(:/org.blueberry.ui.qt/dark/up-arrow-disabled.svg); } QScrollBar::up-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/up-arrow-hover.svg); } QScrollBar::up-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/up-arrow-pressed.svg); } QTabWidget::pane { border: 1px solid #434346; } QTabBar::tab { background-color: #434346; border: 1px solid #434346; border-bottom: none; padding: 4px; } QTabBar::tab:middle { border-left: none; } QTabBar::tab:last { border-left: none; } QTabBar::tab:next-selected { border-right: none; } QTabBar::tab:selected { border: 1px solid #434346; border-bottom: none; } QTabBar::tab:!selected { background-color: #2d2d30; } QTabBar::tab:!selected:hover { background-color: #434346; } #TabCloseButton { background-color: none; } QTabBar QToolButton::left-arrow { image: url(:/org.blueberry.ui.qt/dark/tight-left-arrow.svg); } QTabBar QToolButton::left-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/tight-left-arrow-hover.svg); } QTabBar QToolButton::left-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/tight-left-arrow-pressed.svg); } QTabBar QToolButton::left-arrow:disabled { image: url(:/org.blueberry.ui.qt/dark/tight-left-arrow-disabled.svg); } QTabBar QToolButton::right-arrow { image: url(:/org.blueberry.ui.qt/dark/tight-right-arrow.svg); } QTabBar QToolButton::right-arrow:hover { image: url(:/org.blueberry.ui.qt/dark/tight-right-arrow-hover.svg); } QTabBar QToolButton::right-arrow:pressed { image: url(:/org.blueberry.ui.qt/dark/tight-right-arrow-pressed.svg); } QTabBar QToolButton::right-arrow:disabled { image: url(:/org.blueberry.ui.qt/dark/tight-right-arrow-disabled.svg); } QTreeView::branch:closed:has-children:has-siblings, QTreeView::branch:closed:has-children:!has-siblings { image: url(:/org.blueberry.ui.qt/dark/right-arrow.svg); } QTreeView::branch:closed:has-children:has-siblings:hover, QTreeView::branch:closed:has-children:!has-siblings:hover { image: url(:/org.blueberry.ui.qt/dark/right-arrow-hover.svg); } QTreeView::branch:open:has-children:has-siblings, QTreeView::branch:open:has-children:!has-siblings { image: url(:/org.blueberry.ui.qt/dark/down-arrow.svg); } QTreeView::branch:open:has-children:has-siblings:hover, QTreeView::branch:open:has-children:!has-siblings:hover { image: url(:/org.blueberry.ui.qt/dark/down-arrow-hover.svg); } QTreeView::indicator:unchecked { image: url(:/org.blueberry.ui.qt/dark/checkbox-unchecked.svg); } QTreeView::indicator:unchecked:selected { image: url(:/org.blueberry.ui.qt/dark/checkbox-unchecked-selected.svg); } QTreeView::indicator:checked, QTreeView::indicator:indeterminate { image: url(:/org.blueberry.ui.qt/dark/checkbox-checked.svg); } QTreeView::indicator:checked:selected, QTreeView::indicator:indeterminate:selected { image: url(:/org.blueberry.ui.qt/dark/checkbox-checked-selected.svg); } diff --git a/Plugins/org.blueberry.ui.qt/src/berryIViewRegistry.h b/Plugins/org.blueberry.ui.qt/src/berryIViewRegistry.h index 0ee69890b1..7880a457ff 100644 --- a/Plugins/org.blueberry.ui.qt/src/berryIViewRegistry.h +++ b/Plugins/org.blueberry.ui.qt/src/berryIViewRegistry.h @@ -1,79 +1,90 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef BERRYIVIEWREGISTRY_H_ #define BERRYIVIEWREGISTRY_H_ #include #include "berryIViewDescriptor.h" #include "berryIViewCategory.h" #include "berryIStickyViewDescriptor.h" namespace berry { /** * \ingroup org_blueberry_ui_qt * * The view registry maintains a list of views explicitly registered * against the view extension point. *

* The description of a given view is kept in a IViewDescriptor. *

*

* This interface is not intended to be implemented by clients. *

* * @see IViewDescriptor * @see IStickyViewDescriptor */ struct BERRY_UI_QT IViewRegistry { /** * Return a view descriptor with the given extension id. If no view exists * with the id return null. * Will also return null if the view descriptor exists, but * is filtered by an expression-based activity. * * @param id the id to search for * @return the descriptor or null */ virtual IViewDescriptor::Pointer Find(const QString& id) const = 0; /** * Returns an array of view categories. * * @return the categories. */ virtual QList GetCategories() = 0; /** * Return a list of views defined in the registry. * * @return the views. */ virtual QList GetViews() const = 0; + /** + * @brief Get views as multi map with categories as keys. + * + * Exclude views without category and categories that contain a literal '.', e.g. + * "org.blueberry.ui" or "org.mitk.views.general", as they typically do not have + * a corresponding tool bar. + * + * @return the views by category as described above. + */ + virtual QMultiMap GetViewsByCategory() const = 0; + /** * Return a list of sticky views defined in the registry. * * @return the sticky views. Never null. */ virtual QList GetStickyViews() const = 0; virtual ~IViewRegistry(); }; } #endif /*BERRYIVIEWREGISTRY_H_*/ diff --git a/Plugins/org.blueberry.ui.qt/src/berryIWorkbenchWindow.h b/Plugins/org.blueberry.ui.qt/src/berryIWorkbenchWindow.h index 1a475db25a..33cb3c6ca9 100644 --- a/Plugins/org.blueberry.ui.qt/src/berryIWorkbenchWindow.h +++ b/Plugins/org.blueberry.ui.qt/src/berryIWorkbenchWindow.h @@ -1,196 +1,203 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef BERRYIWORKBENCHWINDOW_H_ #define BERRYIWORKBENCHWINDOW_H_ #include #include #include #include "berryIPageService.h" #include "berryShell.h" #include "services/berryIServiceLocator.h" +#include + namespace berry { struct IPartService; struct ISelectionService; struct IWorkbenchPage; struct IWorkbench; /** * \ingroup org_blueberry_ui_qt * * A workbench window is a top level window in a workbench. Visually, a * workbench window has a menubar, a toolbar, a status bar, and a main area for * displaying a single page consisting of a collection of views and editors. *

* Each workbench window has a collection of 0 or more pages; the active page is * the one that is being presented to the end user; at most one page is active * in a window at a time. *

*

* The workbench window supports a few services by default. * If these services are used to allocate resources, it is important to * remember to clean up those resources after you are done with them. Otherwise, * the resources will exist until the workbench window is closed. The supported * services are: *

*
    *
  • {@link ICommandService}
  • *
  • {@link IContextService}
  • *
*

* This interface is not intended to be implemented by clients. *

* * @see IWorkbenchPage * */ struct BERRY_UI_QT IWorkbenchWindow : public IPageService, public IServiceLocator /** \cond */, public virtual Object /** \endcond */ { berryObjectMacro(berry::IWorkbenchWindow, IPageService, IServiceLocator, Object); /** * Closes this workbench window. *

* If the window has an open editor with unsaved content, the user will be * given the opportunity to save it. *

* * @return true if the window was successfully closed, and * false if it is still open */ virtual bool Close() = 0; /** * Returns a list of the pages in this workbench window. *

* Note that each window has its own pages; pages are never shared between * different windows. *

* * @return a list of pages */ virtual QList > GetPages() const = 0; /** * Returns the currently active page for this workbench window. * * @return the active page, or null if none */ SmartPointer GetActivePage() const override = 0; /** * Sets or clears the currently active page for this workbench window. * * @param page * the new active page */ virtual void SetActivePage(SmartPointer page) = 0; /** * Returns the part service which tracks part activation within this * workbench window. * * @return the part service */ virtual IPartService* GetPartService() = 0; /** * Returns the selection service which tracks selection within this * workbench window. * * @return the selection service */ virtual ISelectionService* GetSelectionService() const = 0; /** * Returns this workbench window's shell. * * @return the shell containing this window's controls or null * if the shell has not been created yet or if the window has been closed */ virtual Shell::Pointer GetShell() const = 0; /** * Returns the workbench for this window. * * @return the workbench */ virtual IWorkbench* GetWorkbench() const = 0; + /** + * Returns all tool bars for this window. + */ + virtual QList GetToolBars() const = 0; + /** * Creates and opens a new workbench page. The perspective of the new page * is defined by the specified perspective ID. The new page become active. *

* Note: Since release 2.0, a window is limited to contain at most * one page. If a page exist in the window when this method is used, then * another window is created for the new page. Callers are strongly * recommended to use the IWorkbench.showPerspective APIs to * programmatically show a perspective. *

* * @param perspectiveId * the perspective id for the window's initial page * @param input * the page input, or null if there is no current * input. This is used to seed the input for the new page's * views. * @return the new workbench page * @exception WorkbenchException * if a page could not be opened * * @see IWorkbench#showPerspective(String, IWorkbenchWindow, IAdaptable) */ virtual SmartPointer OpenPage(const QString& perspectiveId, IAdaptable* input) = 0; /** * Creates and opens a new workbench page. The default perspective is used * as a template for creating the page. The page becomes active. *

* Note: Since release 2.0, a window is limited to contain at most * one page. If a page exist in the window when this method is used, then * another window is created for the new page. Callers are strongly * recommended to use the IWorkbench.showPerspective APIs to * programmatically show a perspective. *

* * @param input * the page input, or null if there is no current * input. This is used to seed the input for the new page's * views. * @return the new workbench window * @exception WorkbenchException * if a page could not be opened * * @see IWorkbench#showPerspective(String, IWorkbenchWindow, IAdaptable) */ virtual SmartPointer OpenPage(IAdaptable* input) = 0; //virtual void SetPerspectiveExcludeList(const QStringList& v) = 0; //virtual QStringList GetPerspectiveExcludeList() const = 0; //virtual void SetViewExcludeList(const QStringList& v) = 0; //virtual QStringList GetViewExcludeList() const = 0; ~IWorkbenchWindow() override; }; } #endif /*BERRYIWORKBENCHWINDOW_H_*/ diff --git a/Plugins/org.blueberry.ui.qt/src/internal/berryViewRegistry.cpp b/Plugins/org.blueberry.ui.qt/src/internal/berryViewRegistry.cpp index 309967e985..f2c7fa7e78 100644 --- a/Plugins/org.blueberry.ui.qt/src/internal/berryViewRegistry.cpp +++ b/Plugins/org.blueberry.ui.qt/src/internal/berryViewRegistry.cpp @@ -1,281 +1,301 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "berryViewRegistry.h" #include "berryPlatformUI.h" #include "berryWorkbenchPlugin.h" #include "berryWorkbenchRegistryConstants.h" #include "berryPlatform.h" namespace berry { ViewRegistry::ViewCategoryProxy::ViewCategoryProxy( IViewDescriptorCategoryPtr rawCategory) : rawCategory(rawCategory) { } QList ViewRegistry::ViewCategoryProxy::GetViews() const { return rawCategory->GetElements(); } QString ViewRegistry::ViewCategoryProxy::GetId() const { return rawCategory->GetId(); } QStringList ViewRegistry::ViewCategoryProxy::GetPath() const { QList path; QString rawParentPath = rawCategory->GetRawParentPath(); // nested categories are not supported yet // assume an empty raw parent path path.push_back(rawParentPath); return path; } QString ViewRegistry::ViewCategoryProxy::GetLabel() const { return rawCategory->GetLabel(); } QString ViewRegistry::ViewCategoryProxy::GetLocalId() const { return GetId(); } QString ViewRegistry::ViewCategoryProxy::GetPluginId() const { return rawCategory->GetPluginId(); } bool ViewRegistry::ViewCategoryProxy::operator==(const Object* o) const { if (const IViewCategory* other = dynamic_cast(o)) return this->GetId() == other->GetId(); return false; } uint ViewRegistry::ViewCategoryProxy::HashCode() const { return qHash(GetId()); } QString ViewRegistry::EXTENSIONPOINT_UNIQUE_ID = "org.blueberry.ui.views"; // PlatformUI::PLUGIN_ID + "." + WorkbenchRegistryConstants::PL_VIEWS; Category::Pointer ViewRegistry::InternalFindCategory(const QString& id) { for (QList::iterator itr = categories.begin(); itr != categories.end(); ++itr) { if (id == (*itr)->GetRootPath()) { return *itr; } } return IViewDescriptorCategoryPtr(nullptr); } IExtensionPoint::Pointer ViewRegistry::GetExtensionPointFilter() { return Platform::GetExtensionRegistry()->GetExtensionPoint(EXTENSIONPOINT_UNIQUE_ID); } const QString ViewRegistry::TAG_DESCRIPTION = "description"; ViewRegistry::ViewRegistry() : dirtyViewCategoryMappings(true) { miscCategory = new IViewDescriptorCategory(); this->Add(miscCategory); //PlatformUI.getWorkbench().getExtensionTracker().registerHandler(this, // ExtensionTracker.createExtensionPointFilter(getExtensionPointFilter())); reader.ReadViews(Platform::GetExtensionRegistry(), this); } void ViewRegistry::Add(IViewDescriptorCategoryPtr desc) { /* fix for 1877 */ if (this->InternalFindCategory(desc->GetId()).IsNull()) { dirtyViewCategoryMappings = true; // Mark categories list as dirty categories.push_back(desc); // IConfigurationElement::Pointer element( // dynamic_cast( // desc->GetAdapter(typeid(IConfigurationElement)) // )); // if (element.IsNull()) // { // return; // } // PlatformUI::GetWorkbench()->GetExtensionTracker() // .registerObject(element->GetDeclaringExtension(), desc, // IExtensionTracker::REF_WEAK); } } void ViewRegistry::Add(ViewDescriptor::Pointer desc) { for (QList::const_iterator itr = views.begin(); itr != views.end(); ++itr) { if (desc.GetPointer() == itr->GetPointer()) return; } views.push_back(desc); dirtyViewCategoryMappings = true; //desc.activateHandler(); } void ViewRegistry::Add(StickyViewDescriptor::Pointer desc) { if (std::find(sticky.begin(), sticky.end(), desc) == sticky.end()) { sticky.push_back(desc); // PlatformUI::GetWorkbench()->GetExtensionTracker() // .registerObject(desc.getConfigurationElement().getDeclaringExtension(), // desc, IExtensionTracker.REF_WEAK); } } IViewDescriptor::Pointer ViewRegistry::Find(const QString& id) const { for (QList::const_iterator itr = views.begin(); itr != views.end(); ++itr) { if (id == (*itr)->GetId()) { return *itr; } } return IViewDescriptor::Pointer(nullptr); } IViewCategory::Pointer ViewRegistry::FindCategory(const QString& id) { this->MapViewsToCategories(); IViewDescriptorCategoryPtr category(this->InternalFindCategory(id)); if (category.IsNull()) { return IViewCategory::Pointer(nullptr); } IViewCategory::Pointer cat(new ViewCategoryProxy(category)); return cat; } QList ViewRegistry::GetCategories() { this->MapViewsToCategories(); QList retArray; for (QList::iterator itr = categories.begin(); itr != categories.end(); ++itr) { retArray.push_back(IViewCategory::Pointer(new ViewCategoryProxy(*itr))); } return retArray; } QList ViewRegistry::GetStickyViews() const { return sticky; } Category::Pointer ViewRegistry::GetMiscCategory() const { return miscCategory; } QList ViewRegistry::GetViews() const { return views; } +QMultiMap ViewRegistry::GetViewsByCategory() const +{ + QMultiMap result; + + const auto views = this->GetViews(); + + for (auto view : views) + { + QString category; + + if (auto categoryPath = view->GetCategoryPath(); !categoryPath.isEmpty()) + category = categoryPath.back(); + + if (!category.isEmpty() && !category.contains('.') && !view->IsInternal()) + result.insert(category, view); + } + + return result; +} + void ViewRegistry::MapViewsToCategories() { if (dirtyViewCategoryMappings) { dirtyViewCategoryMappings = false; // clear all category mappings for (QList::iterator i = categories.begin(); i != categories.end(); ++i) { (*i)->Clear(); // this is bad } miscCategory->Clear(); for (QList::iterator i = views.begin(); i != views.end(); ++i) { IViewDescriptor::Pointer desc(*i); IViewDescriptorCategoryPtr cat(nullptr); const QList& catPath = desc->GetCategoryPath(); if (catPath.size() > 0) { cat = this->InternalFindCategory(catPath[0]); } if (cat.IsNotNull()) { if (!cat->HasElement(desc)) { cat->AddElement(desc); } } else { if (catPath.size() > 0) { // If we get here, this view specified a category which // does not exist. Add this view to the 'Other' category // but give out a message (to the log only) indicating // this has been done. QString fmt("Category %1 not found for view %2. This view added to ''%3'' category."); WorkbenchPlugin::Log(fmt.arg(catPath[0]).arg(desc->GetId()).arg(miscCategory->GetLabel())); } miscCategory->AddElement(desc); } } } } //void ViewRegistry::AddExtension(IExtensionTracker tracker, // IExtension addedExtension) //{ // IConfigurationElement[] addedElements = addedExtension.getConfigurationElements(); // for (int i = 0; i < addedElements.length; i++) // { // IConfigurationElement element = addedElements[i]; // if (element.getName().equals(IWorkbenchRegistryConstants.TAG_VIEW)) // { // reader.readView(element); // } // else if (element.getName().equals(IWorkbenchRegistryConstants.TAG_CATEGORY)) // { // reader.readCategory(element); // } // else if (element.getName().equals(IWorkbenchRegistryConstants.TAG_STICKYVIEW)) // { // reader.readSticky(element); // } // } //} } // namespace berry diff --git a/Plugins/org.blueberry.ui.qt/src/internal/berryViewRegistry.h b/Plugins/org.blueberry.ui.qt/src/internal/berryViewRegistry.h index 91e010688b..161df4674b 100644 --- a/Plugins/org.blueberry.ui.qt/src/internal/berryViewRegistry.h +++ b/Plugins/org.blueberry.ui.qt/src/internal/berryViewRegistry.h @@ -1,227 +1,229 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef BERRYVIEWREGISTRY_H_ #define BERRYVIEWREGISTRY_H_ #include "berryIViewRegistry.h" #include "berryIViewCategory.h" #include "berryIViewDescriptor.h" #include "berryCategory.h" #include "berryViewDescriptor.h" #include "berryViewRegistryReader.h" #include "berryStickyViewDescriptor.h" #include "berryIExtensionPoint.h" namespace berry { /** * \ingroup org_blueberry_ui_internal * * The central manager for view descriptors. */ class ViewRegistry : public IViewRegistry { public: typedef Category IViewDescriptorCategory; typedef IViewDescriptorCategory::Pointer IViewDescriptorCategoryPtr; private: /** * Proxies a Category implementation. * */ class ViewCategoryProxy : public IViewCategory, public IPluginContribution { private: typedef Category IViewDescriptorCategory; IViewDescriptorCategoryPtr rawCategory; /** * Create a new instance of this class * * @param rawCategory the category */ public: ViewCategoryProxy(IViewDescriptorCategoryPtr rawCategory); /* * @see IViewCategory#GetViews() */ QList GetViews() const override; /* * @see IViewCategory#GetId() */ QString GetId() const override; /* * @see IViewCategory#GetPath() */ QStringList GetPath() const override; /* * @see IViewCategory#GetLabel() */ QString GetLabel() const override; /* * @see IPluginContribution#GetLocalId() */ QString GetLocalId() const override; /* * @see IPluginContribution#GetPluginId() */ QString GetPluginId() const override; /* * @see Object#operator==(Object*) */ bool operator==(const Object* o) const override; /* * @see Object#HashCode() */ uint HashCode() const override; }; private: static QString EXTENSIONPOINT_UNIQUE_ID; /** * A set that will only ever contain ViewDescriptors. */ QList views; // = new TreeSet(new Comparator() { // public int compare(Object o1, Object o2) { // String id1 = ((ViewDescriptor) o1).getId(); // String id2 = ((ViewDescriptor) o2).getId(); // // return id1.compareTo(id2); // }}); QList sticky; QList categories; IViewDescriptorCategoryPtr miscCategory; ViewRegistryReader reader; bool dirtyViewCategoryMappings; /** * Returns the category with no updating of the view/category mappings. * * @param id the category id * @return the Category * @since 3.1 */ IViewDescriptorCategoryPtr InternalFindCategory(const QString& id); SmartPointer GetExtensionPointFilter(); protected: static const QString TAG_DESCRIPTION; // = "description"; public: ViewRegistry(); /** * Add a category to the registry. * * @param desc the descriptor to add */ void Add(IViewDescriptorCategoryPtr desc); /** * Add a descriptor to the registry. * * @param desc the descriptor to add */ void Add(ViewDescriptor::Pointer desc); /** * Add a sticky descriptor to the registry. * * @param desc the descriptor to add */ void Add(StickyViewDescriptor::Pointer desc); /** * Find a descriptor in the registry. */ IViewDescriptor::Pointer Find(const QString& id) const override; /** * Find a category with a given name. * * @param id the id to search for * @return the category or null */ IViewCategory::Pointer FindCategory(const QString& id); /** * Get the list of view categories. */ QList GetCategories() override; /** * Get the list of sticky views minus the sticky views which failed the * Expressions check. */ QList GetStickyViews() const override; /** * Returns the Misc category. This may be null if there are * no miscellaneous views. * * @return the misc category or null */ IViewDescriptorCategoryPtr GetMiscCategory() const; /** * Get an enumeration of view descriptors. */ QList GetViews() const override; + QMultiMap GetViewsByCategory() const override; + /** * Adds each view in the registry to a particular category. * The view category may be defined in xml. If not, the view is * added to the "misc" category. */ void MapViewsToCategories(); /* (non-Javadoc) * @see org.blueberry.core.runtime.dynamicHelpers.IExtensionChangeHandler#addExtension(org.blueberry.core.runtime.dynamicHelpers.IExtensionTracker, org.blueberry.core.runtime.IExtension) */ //void AddExtension(IExtensionTracker tracker, IExtension addedExtension); }; } // namespace berry #endif /*BERRYVIEWREGISTRY_H_*/ diff --git a/Plugins/org.blueberry.ui.qt/src/internal/berryWorkbenchWindow.cpp b/Plugins/org.blueberry.ui.qt/src/internal/berryWorkbenchWindow.cpp index 5ed0e2a5e6..33c0584845 100644 --- a/Plugins/org.blueberry.ui.qt/src/internal/berryWorkbenchWindow.cpp +++ b/Plugins/org.blueberry.ui.qt/src/internal/berryWorkbenchWindow.cpp @@ -1,1936 +1,1955 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "tweaklets/berryGuiWidgetsTweaklet.h" #include "tweaklets/berryWorkbenchTweaklet.h" #include "berryWorkbenchWindow.h" #include "berryIWorkbenchPage.h" #include "berryIPerspectiveDescriptor.h" #include "berryIContextService.h" #include "berryUIException.h" #include "berryConstants.h" #include "berryIMenuService.h" #include "berryMenuUtil.h" #include "intro/berryIntroConstants.h" #include "berryWorkbenchPlugin.h" #include "berryWorkbenchPage.h" #include "berryWorkbench.h" #include "berryWorkbenchConstants.h" #include "berryPartSite.h" #include "berryIServiceLocatorCreator.h" #include "berryMenuManager.h" #include "berryQActionProperties.h" #include "berryQtControlWidget.h" #include "berryQtPerspectiveSwitcher.h" #include "berryWWinActionBars.h" #include "berryWorkbenchLocationService.h" #include "berryIServiceFactory.h" #include "berryIServiceScopes.h" #include "berryIEvaluationReference.h" #include "berryPlatformUI.h" #include "berryDebugUtil.h" #include #include #include #include #include namespace berry { const QString WorkbenchWindow::PROP_TOOLBAR_VISIBLE = "toolbarVisible"; const QString WorkbenchWindow::PROP_PERSPECTIVEBAR_VISIBLE = "perspectiveBarVisible"; const QString WorkbenchWindow::PROP_STATUS_LINE_VISIBLE = "statusLineVisible"; const ActionBarAdvisor::FillFlags WorkbenchWindow::FILL_ALL_ACTION_BARS = ActionBarAdvisor::FILL_MENU_BAR | ActionBarAdvisor::FILL_TOOL_BAR | ActionBarAdvisor::FILL_STATUS_LINE; WorkbenchWindow::WorkbenchWindow(int number) : Window(Shell::Pointer(nullptr)) , pageComposite(nullptr) , windowAdvisor(nullptr) , actionBarAdvisor(nullptr) , number(number) , largeUpdates(0) , closing(false) , shellActivated(false) , updateDisabled(true) , toolBarVisible(true) , perspectiveBarVisible(true) , statusLineVisible(true) , emptyWindowContentsCreated(false) , emptyWindowContents(nullptr) , asMaximizedState(false) , partService(this) , serviceLocatorOwner(new ServiceLocatorOwner(this)) , resizeEventFilter(this) { this->Register(); // increase the reference count to avoid deleting // this object when temporary smart pointers // go out of scope // Make sure there is a workbench. This call will throw // an exception if workbench not created yet. IWorkbench* workbench = PlatformUI::GetWorkbench(); IServiceLocatorCreator* slc = workbench->GetService(); this->serviceLocator = slc->CreateServiceLocator( workbench, nullptr, IDisposable::WeakPtr(serviceLocatorOwner)).Cast(); InitializeDefaultServices(); // Add contribution managers that are exposed to other plugins. this->AddMenuBar(); //addCoolBar(SWT.NONE); // style is unused //addStatusLine(); this->FireWindowOpening(); // Fill the action bars this->FillActionBars(FILL_ALL_ACTION_BARS); this->UnRegister(false); // decrease reference count and avoid deleting // the window } WorkbenchWindow::~WorkbenchWindow() { // BERRY_INFO << "WorkbenchWindow::~WorkbenchWindow()"; } Object* WorkbenchWindow::GetService(const QString& key) { return serviceLocator->GetService(key); } bool WorkbenchWindow::HasService(const QString& key) const { return serviceLocator->HasService(key); } Shell::Pointer WorkbenchWindow::GetShell() const { return Window::GetShell(); } bool WorkbenchWindow::ClosePage(IWorkbenchPage::Pointer in, bool save) { // Validate the input. if (!pageList.Contains(in)) { return false; } WorkbenchPage::Pointer oldPage = in.Cast (); // Save old perspective. if (save && oldPage->IsSaveNeeded()) { if (!oldPage->SaveAllEditors(true)) { return false; } } // If old page is activate deactivate. bool oldIsActive = (oldPage == this->GetActivePage()); if (oldIsActive) { this->SetActivePage(IWorkbenchPage::Pointer(nullptr)); } // Close old page. pageList.Remove(oldPage); partService.PageClosed(oldPage); //this->FirePageClosed(oldPage); //oldPage->Dispose(); // Activate new page. if (oldIsActive) { IWorkbenchPage::Pointer newPage = pageList.GetNextActive(); if (newPage != 0) { this->SetActivePage(newPage); } } if (!closing && pageList.IsEmpty()) { this->ShowEmptyWindowContents(); } return true; } void WorkbenchWindow::AddPerspectiveListener(IPerspectiveListener* l) { perspectiveEvents.AddListener(l); } void WorkbenchWindow::RemovePerspectiveListener(IPerspectiveListener* l) { perspectiveEvents.RemoveListener(l); } IPerspectiveListener::Events& WorkbenchWindow::GetPerspectiveEvents() { return perspectiveEvents; } void WorkbenchWindow::FireWindowOpening() { // let the application do further configuration this->GetWindowAdvisor()->PreWindowOpen(); } void WorkbenchWindow::FireWindowRestored() { //StartupThreading.runWithWorkbenchExceptions(new StartupRunnable() { // public void runWithException() throws Throwable { this->GetWindowAdvisor()->PostWindowRestore(); // } //}); } void WorkbenchWindow::FireWindowCreated() { this->GetWindowAdvisor()->PostWindowCreate(); } void WorkbenchWindow::FireWindowOpened() { this->GetWorkbenchImpl()->FireWindowOpened(IWorkbenchWindow::Pointer(this)); this->GetWindowAdvisor()->PostWindowOpen(); } bool WorkbenchWindow::FireWindowShellClosing() { return this->GetWindowAdvisor()->PreWindowShellClose(); } void WorkbenchWindow::FireWindowClosed() { // let the application do further deconfiguration this->GetWindowAdvisor()->PostWindowClose(); this->GetWorkbenchImpl()->FireWindowClosed(IWorkbenchWindow::Pointer(this)); } ///** // * Fires page activated // */ //void WorkbenchWindow::FirePageActivated(IWorkbenchPage::Pointer page) { //// String label = null; // debugging only //// if (UIStats.isDebugging(UIStats.NOTIFY_PAGE_LISTENERS)) { //// label = "activated " + page.getLabel(); //$NON-NLS-1$ //// } //// try { //// UIStats.start(UIStats.NOTIFY_PAGE_LISTENERS, label); //// UIListenerLogging.logPageEvent(this, page, //// UIListenerLogging.WPE_PAGE_ACTIVATED); // pageEvents.FirePageActivated(page); // partService.pageActivated(page); //// } finally { //// UIStats.end(UIStats.NOTIFY_PAGE_LISTENERS, page.getLabel(), label); //// } //} // ///** // * Fires page closed // */ //void WorkbenchWindow::FirePageClosed(IWorkbenchPage::Pointer page) { // String label = null; // debugging only // if (UIStats.isDebugging(UIStats.NOTIFY_PAGE_LISTENERS)) { // label = "closed " + page.getLabel(); //$NON-NLS-1$ // } // try { // UIStats.start(UIStats.NOTIFY_PAGE_LISTENERS, label); // UIListenerLogging.logPageEvent(this, page, // UIListenerLogging.WPE_PAGE_CLOSED); // pageListeners.firePageClosed(page); // partService.pageClosed(page); // } finally { // UIStats.end(UIStats.NOTIFY_PAGE_LISTENERS, page.getLabel(), label); // } // //} // ///** // * Fires page opened // */ //void WorkbenchWindow::FirePageOpened(IWorkbenchPage::Pointer page) { // String label = null; // debugging only // if (UIStats.isDebugging(UIStats.NOTIFY_PAGE_LISTENERS)) { // label = "opened " + page.getLabel(); //$NON-NLS-1$ // } // try { // UIStats.start(UIStats.NOTIFY_PAGE_LISTENERS, label); // UIListenerLogging.logPageEvent(this, page, // UIListenerLogging.WPE_PAGE_OPENED); // pageListeners.firePageOpened(page); // partService.pageOpened(page); // } finally { // UIStats.end(UIStats.NOTIFY_PAGE_LISTENERS, page.getLabel(), label); // } //} void WorkbenchWindow::FirePerspectiveActivated(IWorkbenchPage::Pointer page, IPerspectiveDescriptor::Pointer perspective) { // UIListenerLogging.logPerspectiveEvent(this, page, perspective, // UIListenerLogging.PLE_PERSP_ACTIVATED); perspectiveEvents.perspectiveActivated(page, perspective); } void WorkbenchWindow::FirePerspectivePreDeactivate( IWorkbenchPage::Pointer page, IPerspectiveDescriptor::Pointer perspective) { // UIListenerLogging.logPerspectiveEvent(this, page, perspective, // UIListenerLogging.PLE_PERSP_PRE_DEACTIVATE); perspectiveEvents.perspectivePreDeactivate(page, perspective); } void WorkbenchWindow::FirePerspectiveDeactivated(IWorkbenchPage::Pointer page, IPerspectiveDescriptor::Pointer perspective) { // UIListenerLogging.logPerspectiveEvent(this, page, perspective, // UIListenerLogging.PLE_PERSP_DEACTIVATED); perspectiveEvents.perspectiveDeactivated(page, perspective); } void WorkbenchWindow::FirePerspectiveChanged(IWorkbenchPage::Pointer page, IPerspectiveDescriptor::Pointer perspective, const QString& changeId) { // Some callers call this even when there is no active perspective. // Just ignore this case. if (perspective != 0) { // UIListenerLogging.logPerspectiveChangedEvent(this, page, // perspective, null, changeId); perspectiveEvents.perspectiveChanged(page, perspective, changeId); } } void WorkbenchWindow::FirePerspectiveChanged(IWorkbenchPage::Pointer page, IPerspectiveDescriptor::Pointer perspective, IWorkbenchPartReference::Pointer partRef, const QString& changeId) { // Some callers call this even when there is no active perspective. // Just ignore this case. if (perspective != 0) { // UIListenerLogging.logPerspectiveChangedEvent(this, page, // perspective, partRef, changeId); perspectiveEvents.perspectivePartChanged(page, perspective, partRef, changeId); } } void WorkbenchWindow::FirePerspectiveClosed(IWorkbenchPage::Pointer page, IPerspectiveDescriptor::Pointer perspective) { // UIListenerLogging.logPerspectiveEvent(this, page, perspective, // UIListenerLogging.PLE_PERSP_CLOSED); perspectiveEvents.perspectiveClosed(page, perspective); } void WorkbenchWindow::FirePerspectiveOpened(IWorkbenchPage::Pointer page, IPerspectiveDescriptor::Pointer perspective) { // UIListenerLogging.logPerspectiveEvent(this, page, perspective, // UIListenerLogging.PLE_PERSP_OPENED); perspectiveEvents.perspectiveOpened(page, perspective); } void WorkbenchWindow::FirePerspectiveSavedAs(IWorkbenchPage::Pointer page, IPerspectiveDescriptor::Pointer oldPerspective, IPerspectiveDescriptor::Pointer newPerspective) { // UIListenerLogging.logPerspectiveSavedAs(this, page, oldPerspective, // newPerspective); perspectiveEvents.perspectiveSavedAs(page, oldPerspective, newPerspective); } void WorkbenchWindow::FillActionBars(ActionBarAdvisor::FillFlags flags) { // Workbench workbench = getWorkbenchImpl(); // workbench.largeUpdateStart(); //try { this->GetActionBarAdvisor()->FillActionBars(flags); IMenuService* menuService = serviceLocator->GetService(); menuService->PopulateContributionManager(dynamic_cast(GetActionBars()->GetMenuManager()), MenuUtil::MAIN_MENU); // ICoolBarManager coolbar = getActionBars().getCoolBarManager(); // if (coolbar != null) // { // menuService.populateContributionManager( // (ContributionManager) coolbar, // MenuUtil.MAIN_TOOLBAR); // } // } finally { // workbench.largeUpdateEnd(); // } } QPoint WorkbenchWindow::GetInitialSize() { return this->GetWindowConfigurer()->GetInitialSize(); } bool WorkbenchWindow::Close() { //BERRY_INFO << "WorkbenchWindow::Close()"; bool ret = false; //BusyIndicator.showWhile(null, new Runnable() { // public void run() { ret = this->BusyClose(); // } // }); return ret; } bool WorkbenchWindow::BusyClose() { // Whether the window was actually closed or not bool windowClosed = false; // Setup internal flags to indicate window is in // progress of closing and no update should be done. closing = true; updateDisabled = true; try { // Only do the check if it is OK to close if we are not closing // via the workbench as the workbench will check this itself. Workbench* workbench = this->GetWorkbenchImpl(); std::size_t count = workbench->GetWorkbenchWindowCount(); // also check for starting - if the first window dies on startup // then we'll need to open a default window. if (!workbench->IsStarting() && !workbench->IsClosing() && count <= 1 && workbench->GetWorkbenchConfigurer()->GetExitOnLastWindowClose()) { windowClosed = workbench->Close(); } else { if (this->OkToClose()) { windowClosed = this->HardClose(); } } } catch (std::exception& exc) { if (!windowClosed) { // Reset the internal flags if window was not closed. closing = false; updateDisabled = false; } throw exc; } // if (windowClosed && tracker != null) { // tracker.close(); // } return windowClosed; } void WorkbenchWindow::MakeVisible() { Shell::Pointer shell = GetShell(); if (shell) { // see bug 96700 and bug 4414 for a discussion on the use of open() // here shell->Open(); } } bool WorkbenchWindow::OkToClose() { // Save all of the editors. if (!this->GetWorkbenchImpl()->IsClosing()) { if (!this->SaveAllPages(true)) { return false; } } return true; } bool WorkbenchWindow::SaveAllPages(bool bConfirm) { bool bRet = true; PageList::iterator itr = pageList.Begin(); while (bRet && itr != pageList.End()) { bRet = (*itr)->SaveAllEditors(bConfirm); ++itr; } return bRet; } bool WorkbenchWindow::HardClose() { std::exception exc; bool exceptionOccured = false; try { // Clear the action sets, fix for bug 27416. //getActionPresentation().clearActionSets(); // Remove the handler submissions. Bug 64024. /* final IWorkbench workbench = getWorkbench(); final IHandlerService handlerService = (IHandlerService) workbench.getService(IHandlerService.class); handlerService.deactivateHandlers(handlerActivations); final Iterator activationItr = handlerActivations.iterator(); while (activationItr.hasNext()) { final IHandlerActivation activation = (IHandlerActivation) activationItr .next(); activation.getHandler().dispose(); } handlerActivations.clear(); globalActionHandlersByCommandId.clear(); */ // Remove the enabled submissions. Bug 64024. //IContextService* contextService = this->GetWorkbench()->GetService(); //contextService->UnregisterShell(this->GetShell()); this->CloseAllPages(); this->FireWindowClosed(); // time to wipe out our populate /* IMenuService menuService = (IMenuService) workbench .getService(IMenuService.class); menuService .releaseContributions(((ContributionManager) getActionBars() .getMenuManager())); ICoolBarManager coolbar = getActionBars().getCoolBarManager(); if (coolbar != null) { menuService .releaseContributions(((ContributionManager) coolbar)); } */ //getActionBarAdvisor().dispose(); //getWindowAdvisor().dispose(); //detachedWindowShells.dispose(); delete windowAdvisor; windowAdvisor = nullptr; // Null out the progress region. Bug 64024. //progressRegion = null; // Remove drop targets /* DragUtil.removeDragTarget(null, trimDropTarget); DragUtil.removeDragTarget(getShell(), trimDropTarget); trimDropTarget = null; if (trimMgr2 != null) { trimMgr2.dispose(); trimMgr2 = null; } if (trimContributionMgr != null) { trimContributionMgr.dispose(); trimContributionMgr = null; } */ } catch (std::exception& e) { exc = e; exceptionOccured = true; } bool result = Window::Close(); // Bring down all of the services ... after the window goes away serviceLocator->Dispose(); //menuRestrictions.clear(); if (exceptionOccured) throw exc; return result; } void WorkbenchWindow::CloseAllPages() { // Deactivate active page. this->SetActivePage(IWorkbenchPage::Pointer(nullptr)); // Clone and deref all so that calls to getPages() returns // empty list (if called by pageClosed event handlers) PageList oldList = pageList; pageList.Clear(); // Close all. for (PageList::iterator itr = oldList.Begin(); itr != oldList.End(); ++itr) { partService.PageClosed(*itr); //(*itr)->FirePageClosed(page); //page.dispose(); } if (!closing) { this->ShowEmptyWindowContents(); } } WWinActionBars* WorkbenchWindow::GetActionBars() { if (actionBars.IsNull()) { actionBars = new WWinActionBars(this); } return actionBars.GetPointer(); } void WorkbenchWindow::SetPerspectiveExcludeList(const QStringList& v) { perspectiveExcludeList = v; } QStringList WorkbenchWindow::GetPerspectiveExcludeList() const { return perspectiveExcludeList; } void WorkbenchWindow::SetViewExcludeList(const QStringList& v) { viewExcludeList = v; } QStringList WorkbenchWindow::GetViewExcludeList() const { return viewExcludeList; } QList WorkbenchWindow::GetPages() const { return pageList.GetPages(); } IWorkbenchPage::Pointer WorkbenchWindow::GetActivePage() const { return pageList.GetActive(); } IWorkbench* WorkbenchWindow::GetWorkbench() const { return PlatformUI::GetWorkbench(); } +QList WorkbenchWindow::GetToolBars() const +{ + QList result; + + if (auto shell = this->GetShell(); shell.IsNotNull()) + { + if (const auto* mainWindow = qobject_cast(shell->GetControl()); mainWindow != nullptr) + { + for (auto child : mainWindow->children()) + { + if (auto toolBar = qobject_cast(child); toolBar != nullptr) + result.append(toolBar); + } + } + } + + return result; +} + IPartService* WorkbenchWindow::GetPartService() { return &partService; } ISelectionService* WorkbenchWindow::GetSelectionService() const { return partService.GetSelectionService(); } bool WorkbenchWindow::GetToolBarVisible() const { return GetWindowConfigurer()->GetShowToolBar() && toolBarVisible; } bool WorkbenchWindow::GetPerspectiveBarVisible() const { return GetWindowConfigurer()->GetShowPerspectiveBar() && perspectiveBarVisible; } bool WorkbenchWindow::GetStatusLineVisible() const { return GetWindowConfigurer()->GetShowStatusLine() && statusLineVisible; } void WorkbenchWindow::AddPropertyChangeListener(IPropertyChangeListener *listener) { genericPropertyListeners.AddListener(listener); } void WorkbenchWindow::RemovePropertyChangeListener(IPropertyChangeListener *listener) { genericPropertyListeners.RemoveListener(listener); } bool WorkbenchWindow::IsClosing() { return closing || this->GetWorkbenchImpl()->IsClosing(); } int WorkbenchWindow::Open() { if (pageList.IsEmpty()) { this->ShowEmptyWindowContents(); } this->FireWindowCreated(); this->GetWindowAdvisor()->OpenIntro(); int result = Window::Open(); // It's time for a layout ... to insure that if TrimLayout // is in play, it updates all of the trim it's responsible // for. We have to do this before updating in order to get // the PerspectiveBar management correct...see defect 137334 //getShell().layout(); this->FireWindowOpened(); // if (perspectiveSwitcher != null) { // perspectiveSwitcher.updatePerspectiveBar(); // perspectiveSwitcher.updateBarParent(); // } return result; } QWidget* WorkbenchWindow::GetPageComposite() { return pageComposite; } QWidget *WorkbenchWindow::CreatePageComposite(QWidget *parent) { auto pageArea = new QtControlWidget(parent, nullptr); pageArea->setObjectName("Page Composite"); new QHBoxLayout(pageArea); if (qobject_cast (parent) != nullptr) qobject_cast (parent)->setCentralWidget(pageArea); else parent->layout()->addWidget(pageArea); // we have to enable visibility to get a proper layout (see bug #1654) pageArea->setVisible(true); parent->setVisible(true); pageComposite = pageArea; return pageArea; } QWidget* WorkbenchWindow::CreateContents(Shell::Pointer parent) { // we know from Window.create that the parent is a Shell. this->GetWindowAdvisor()->CreateWindowContents(parent); // the page composite must be set by createWindowContents poco_assert(pageComposite != nullptr) ; // "createWindowContents must call configurer.createPageComposite"); //$NON-NLS-1$ return pageComposite; } void WorkbenchWindow::CreateDefaultContents(Shell::Pointer shell) { QMainWindow* mainWindow = qobject_cast(shell->GetControl()); if (GetWindowConfigurer()->GetShowMenuBar() && mainWindow) { QMenuBar* menuBar = GetMenuBarManager()->CreateMenuBar(mainWindow); mainWindow->setMenuBar(menuBar); } if (GetWindowConfigurer()->GetShowPerspectiveBar() && mainWindow) { mainWindow->addToolBar(new QtPerspectiveSwitcher(IWorkbenchWindow::Pointer(this))); } // Create the client composite area (where page content goes). CreatePageComposite(shell->GetControl()); } void WorkbenchWindow::CreateTrimWidgets(SmartPointer /*shell*/) { // do nothing -- trim widgets are created in CreateDefaultContents } bool WorkbenchWindow::UnableToRestorePage(IMemento::Pointer pageMem) { QString pageName; pageMem->GetString(WorkbenchConstants::TAG_LABEL, pageName); // return new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, 0, NLS.bind( // WorkbenchMessages.WorkbenchWindow_unableToRestorePerspective, // pageName), null); WorkbenchPlugin::Log("Unable to restore perspective: " + pageName); return false; } bool WorkbenchWindow::RestoreState(IMemento::Pointer memento, IPerspectiveDescriptor::Pointer activeDescriptor) { //TODO WorkbenchWindow restore state poco_assert(GetShell()); // final MultiStatus result = new MultiStatus(PlatformUI.PLUGIN_ID, IStatus.OK, // WorkbenchMessages.WorkbenchWindow_problemsRestoringWindow, null); bool result = true; // Restore the window advisor state. IMemento::Pointer windowAdvisorState = memento ->GetChild(WorkbenchConstants::TAG_WORKBENCH_WINDOW_ADVISOR); if (windowAdvisorState) { //result.add(getWindowAdvisor().restoreState(windowAdvisorState)); result &= GetWindowAdvisor()->RestoreState(windowAdvisorState); } // Restore actionbar advisor state. IMemento::Pointer actionBarAdvisorState = memento ->GetChild(WorkbenchConstants::TAG_ACTION_BAR_ADVISOR); if (actionBarAdvisorState) { // result.add(getActionBarAdvisor() // .restoreState(actionBarAdvisorState)); result &= GetActionBarAdvisor() ->RestoreState(actionBarAdvisorState); } // Read window's bounds and state. QRect displayBounds; // StartupThreading.runWithoutExceptions(new StartupRunnable() { // // public void runWithException() { displayBounds = Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetScreenSize(); //displayBounds = GetShell()->GetDisplay()->GetBounds(); // }}); // final IMemento fastViewMem = memento // .getChild(IWorkbenchConstants.TAG_FAST_VIEW_DATA); // if (fastViewMem != null) { // if (fastViewBar != null) { // StartupThreading.runWithoutExceptions(new StartupRunnable() { // // public void runWithException() { // fastViewBar.restoreState(fastViewMem); // }}); // // } // } int x, y, w, h; memento->GetInteger(WorkbenchConstants::TAG_X, x); memento->GetInteger(WorkbenchConstants::TAG_Y, y); memento->GetInteger(WorkbenchConstants::TAG_WIDTH, w); memento->GetInteger(WorkbenchConstants::TAG_HEIGHT, h); QRect shellBounds(x, y, w, h); if (!shellBounds.isEmpty()) { // StartupThreading.runWithoutExceptions(new StartupRunnable() { // // public void runWithException() { if (!shellBounds.intersects(displayBounds)) { // Center on default screen QRect clientArea(Tweaklets::Get(GuiWidgetsTweaklet::KEY)->GetAvailableScreenSize()); shellBounds.setX(clientArea.width() * 0.05); shellBounds.setY(clientArea.height() * 0.05); shellBounds.setWidth(clientArea.width() * 0.9); shellBounds.setHeight(clientArea.height() * 0.9); } GetShell()->SetBounds(shellBounds); // }}); } QString maximized; memento->GetString(WorkbenchConstants::TAG_MAXIMIZED, maximized); if (maximized == "true") { // StartupThreading.runWithoutExceptions(new StartupRunnable() { // // public void runWithException() { GetShell()->SetMaximized(true); // }}); } QString minimized; memento->GetString(WorkbenchConstants::TAG_MINIMIZED, minimized); if (minimized == "true") { // getShell().setMinimized(true); } // // restore the width of the perspective bar // if (perspectiveSwitcher != null) { // perspectiveSwitcher.restoreState(memento); // } // // Restore the cool bar order by creating all the tool bar contribution // // items // // This needs to be done before pages are created to ensure proper // // canonical creation // // of cool items // final ICoolBarManager2 coolBarMgr = (ICoolBarManager2) getCoolBarManager2(); // if (coolBarMgr != null) { // IMemento coolBarMem = memento // .getChild(IWorkbenchConstants.TAG_COOLBAR_LAYOUT); // if (coolBarMem != null) { // // Check if the layout is locked // final Integer lockedInt = coolBarMem // .getInteger(IWorkbenchConstants.TAG_LOCKED); // StartupThreading.runWithoutExceptions(new StartupRunnable(){ // // public void runWithException() { // if ((lockedInt != null) && (lockedInt.intValue() == 1)) { // coolBarMgr.setLockLayout(true); // } else { // coolBarMgr.setLockLayout(false); // } // }}); // // // The new layout of the cool bar manager // ArrayList coolBarLayout = new ArrayList(); // // Traverse through all the cool item in the memento // IMemento contributionMems[] = coolBarMem // .getChildren(IWorkbenchConstants.TAG_COOLITEM); // for (int i = 0; i < contributionMems.length; i++) { // IMemento contributionMem = contributionMems[i]; // String type = contributionMem // .getString(IWorkbenchConstants.TAG_ITEM_TYPE); // if (type == null) { // // Do not recognize that type // continue; // } // String id = contributionMem // .getString(IWorkbenchConstants.TAG_ID); // // // Prevent duplicate items from being read back in. // IContributionItem existingItem = coolBarMgr.find(id); // if ((id != null) && (existingItem != null)) { // if (Policy.DEBUG_TOOLBAR_DISPOSAL) { // System.out // .println("Not loading duplicate cool bar item: " + id); //$NON-NLS-1$ // } // coolBarLayout.add(existingItem); // continue; // } // IContributionItem newItem = null; // if (type.equals(IWorkbenchConstants.TAG_TYPE_SEPARATOR)) { // if (id != null) { // newItem = new Separator(id); // } else { // newItem = new Separator(); // } // } else if (id != null) { // if (type // .equals(IWorkbenchConstants.TAG_TYPE_GROUPMARKER)) { // newItem = new GroupMarker(id); // // } else if (type // .equals(IWorkbenchConstants.TAG_TYPE_TOOLBARCONTRIBUTION) // || type // .equals(IWorkbenchConstants.TAG_TYPE_PLACEHOLDER)) { // // // Get Width and height // Integer width = contributionMem // .getInteger(IWorkbenchConstants.TAG_ITEM_X); // Integer height = contributionMem // .getInteger(IWorkbenchConstants.TAG_ITEM_Y); // // Look for the object in the current cool bar // // manager // IContributionItem oldItem = coolBarMgr.find(id); // // If a tool bar contribution item already exists // // for this id then use the old object // if (oldItem != null) { // newItem = oldItem; // } else { // IActionBarPresentationFactory actionBarPresentation = getActionBarPresentationFactory(); // newItem = actionBarPresentation.createToolBarContributionItem( // actionBarPresentation.createToolBarManager(), id); // if (type // .equals(IWorkbenchConstants.TAG_TYPE_PLACEHOLDER)) { // IToolBarContributionItem newToolBarItem = (IToolBarContributionItem) newItem; // if (height != null) { // newToolBarItem.setCurrentHeight(height // .intValue()); // } // if (width != null) { // newToolBarItem.setCurrentWidth(width // .intValue()); // } // newItem = new PlaceholderContributionItem( // newToolBarItem); // } // // make it invisible by default // newItem.setVisible(false); // // Need to add the item to the cool bar manager // // so that its canonical order can be preserved // IContributionItem refItem = findAlphabeticalOrder( // IWorkbenchActionConstants.MB_ADDITIONS, // id, coolBarMgr); // if (refItem != null) { // coolBarMgr.insertAfter(refItem.getId(), // newItem); // } else { // coolBarMgr.add(newItem); // } // } // // Set the current height and width // if ((width != null) // && (newItem instanceof IToolBarContributionItem)) { // ((IToolBarContributionItem) newItem) // .setCurrentWidth(width.intValue()); // } // if ((height != null) // && (newItem instanceof IToolBarContributionItem)) { // ((IToolBarContributionItem) newItem) // .setCurrentHeight(height.intValue()); // } // } // } // // Add new item into cool bar manager // if (newItem != null) { // coolBarLayout.add(newItem); // newItem.setParent(coolBarMgr); // coolBarMgr.markDirty(); // } // } // // // We need to check if we have everything we need in the layout. // boolean newlyAddedItems = false; // IContributionItem[] existingItems = coolBarMgr.getItems(); // for (int i = 0; i < existingItems.length && !newlyAddedItems; i++) { // IContributionItem existingItem = existingItems[i]; // // /* // * This line shouldn't be necessary, but is here for // * robustness. // */ // if (existingItem == null) { // continue; // } // // boolean found = false; // Iterator layoutItemItr = coolBarLayout.iterator(); // while (layoutItemItr.hasNext()) { // IContributionItem layoutItem = (IContributionItem) layoutItemItr // .next(); // if ((layoutItem != null) // && (layoutItem.equals(existingItem))) { // found = true; // break; // } // } // // if (!found) { // if (existingItem != null) { // newlyAddedItems = true; // } // } // } // // // Set the cool bar layout to the given layout. // if (!newlyAddedItems) { // final IContributionItem[] itemsToSet = new IContributionItem[coolBarLayout // .size()]; // coolBarLayout.toArray(itemsToSet); // StartupThreading // .runWithoutExceptions(new StartupRunnable() { // // public void runWithException() { // coolBarMgr.setItems(itemsToSet); // } // }); // } // // } else { // // For older workbenches // coolBarMem = memento // .getChild(IWorkbenchConstants.TAG_TOOLBAR_LAYOUT); // if (coolBarMem != null) { // // Restore an older layout // restoreOldCoolBar(coolBarMem); // } // } // } // Recreate each page in the window. IWorkbenchPage::Pointer newActivePage; QList pageArray = memento ->GetChildren(WorkbenchConstants::TAG_PAGE); for (int i = 0; i < pageArray.size(); i++) { IMemento::Pointer pageMem = pageArray[i]; QString strFocus; pageMem->GetString(WorkbenchConstants::TAG_FOCUS, strFocus); if (strFocus.isEmpty()) { continue; } // Get the input factory. IAdaptable* input = nullptr; IMemento::Pointer inputMem = pageMem->GetChild(WorkbenchConstants::TAG_INPUT); if (inputMem) { QString factoryID; inputMem->GetString(WorkbenchConstants::TAG_FACTORY_ID, factoryID); if (factoryID.isEmpty()) { WorkbenchPlugin ::Log("Unable to restore page - no input factory ID."); //result.add(unableToRestorePage(pageMem)); result &= UnableToRestorePage(pageMem); continue; } // try { // UIStats.start(UIStats.RESTORE_WORKBENCH, // "WorkbenchPageFactory"); //$NON-NLS-1$ // StartupThreading // .runWithoutExceptions(new StartupRunnable() { // // public void runWithException() throws Throwable { // IElementFactory factory = PlatformUI // .getWorkbench().getElementFactory( // factoryID); // if (factory == null) { // WorkbenchPlugin // .log("Unable to restore page - cannot instantiate input factory: " + factoryID); //$NON-NLS-1$ // result // .add(unableToRestorePage(pageMem)); // return; // } // // // Get the input element. // input[0] = factory.createElement(inputMem); // } // }); // // if (input[0] == null) { // WorkbenchPlugin // .log("Unable to restore page - cannot instantiate input element: " + factoryID); //$NON-NLS-1$ // result.add(unableToRestorePage(pageMem)); // continue; // } // } finally { // UIStats.end(UIStats.RESTORE_WORKBENCH, factoryID, // "WorkbenchPageFactory"); //$NON-NLS-1$ // } } // Open the perspective. IAdaptable* finalInput = input; WorkbenchPage::Pointer newPage; try { // StartupThreading.runWithWorkbenchExceptions(new StartupRunnable(){ // // public void runWithException() throws WorkbenchException { newPage = new WorkbenchPage(this, finalInput); // }}); //result.add(newPage[0].restoreState(pageMem, activeDescriptor)); result &= newPage->RestoreState(pageMem, activeDescriptor); pageList.Add(newPage); // StartupThreading.runWithoutExceptions(new StartupRunnable() { // // public void runWithException() throws Throwable { partService.PageOpened(newPage); // firePageOpened(newPage[0]); // }}); } catch (const WorkbenchException& e) { WorkbenchPlugin::Log( "Unable to restore perspective - constructor failed.", e); //$NON-NLS-1$ //result.add(e.getStatus()); continue; } if (!strFocus.isEmpty()) { newActivePage = newPage; } } // If there are no pages create a default. if (pageList.IsEmpty()) { try { const QString defPerspID = this->GetWorkbenchImpl()->GetPerspectiveRegistry() ->GetDefaultPerspective(); if (!defPerspID.isEmpty()) { WorkbenchPage::Pointer newPage; //StartupThreading.runWithWorkbenchExceptions(new StartupRunnable() { // public void runWithException() throws Throwable { newPage = new WorkbenchPage(this, defPerspID, this->GetDefaultPageInput()); // }}); pageList.Add(newPage); //StartupThreading.runWithoutExceptions(new StartupRunnable() { // public void runWithException() throws Throwable { // firePageOpened(newPage[0]); partService.PageOpened(newPage); // }}); } } catch (WorkbenchException& e) { WorkbenchPlugin ::Log( "Unable to create default perspective - constructor failed.", e); result = false; //TODO set product name // String productName = WorkbenchPlugin.getDefault() // .getProductName(); // if (productName == null) { // productName = ""; //$NON-NLS-1$ // } // getShell().setText(productName); } } // Set active page. if (newActivePage.IsNull()) { newActivePage = pageList.GetNextActive().Cast(); } //StartupThreading.runWithoutExceptions(new StartupRunnable() { // public void runWithException() throws Throwable { this->SetActivePage(newActivePage); // }}); IMemento::Pointer introMem = memento->GetChild(WorkbenchConstants::TAG_INTRO); if (introMem) { // StartupThreading.runWithoutExceptions(new StartupRunnable() { // // public void runWithException() throws Throwable { bool isStandby = false; introMem->GetBoolean(WorkbenchConstants::TAG_STANDBY, isStandby); GetWorkbench()->GetIntroManager()->ShowIntro( IWorkbenchWindow::Pointer(this), isStandby); // } // }); } // // // Only restore the trim state if we're using the default layout // if (defaultLayout != null) { // // Restore the trim state. We pass in the 'root' // // memento since we have to check for pre-3.2 // // state. // result.add(restoreTrimState(memento)); // } return result; } IAdaptable* WorkbenchWindow::GetDefaultPageInput() { return this->GetWorkbenchImpl()->GetDefaultPageInput(); } IWorkbenchPage::Pointer WorkbenchWindow::OpenPage( const QString& perspId, IAdaptable* input) { // Run op in busy cursor. IWorkbenchPage::Pointer result; //BusyIndicator.showWhile(null, new Runnable() { // public void run() { result = this->BusyOpenPage(perspId, input); // } return result; } SmartPointer WorkbenchWindow::OpenPage(IAdaptable* input) { QString perspId = this->GetWorkbenchImpl()->GetDefaultPerspectiveId(); return this->OpenPage(perspId, input); } IWorkbenchPage::Pointer WorkbenchWindow::BusyOpenPage( const QString& perspID, IAdaptable* input) { IWorkbenchPage::Pointer newPage; if (pageList.IsEmpty()) { newPage = new WorkbenchPage(this, perspID, input); pageList.Add(newPage); //this->FirePageOpened(newPage); partService.PageOpened(newPage); this->SetActivePage(newPage); } else { IWorkbenchWindow::Pointer window = this->GetWorkbench()->OpenWorkbenchWindow(perspID, input); newPage = window->GetActivePage(); } return newPage; } int WorkbenchWindow::GetNumber() { return number; } void WorkbenchWindow::UpdateActionBars() { if (updateDisabled || UpdatesDeferred()) { return; } // updateAll required in order to enable accelerators on pull-down menus GetMenuBarManager()->Update(false); //GetToolBarManager()->Update(false); //GetStatusLineManager()->Update(false); } void WorkbenchWindow::LargeUpdateStart() { largeUpdates++; } void WorkbenchWindow::LargeUpdateEnd() { if (--largeUpdates == 0) { this->UpdateActionBars(); } } void WorkbenchWindow::SetActivePage(IWorkbenchPage::Pointer in) { if (this->GetActivePage() == in) { return; } // 1FVGTNR: ITPUI:WINNT - busy cursor for switching perspectives //BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() { // public void run() { // Deactivate old persp. WorkbenchPage::Pointer currentPage = pageList.GetActive(); if (currentPage.IsNotNull()) { currentPage->OnDeactivate(); } // Activate new persp. if (in.IsNull() || pageList.Contains(in)) { pageList.SetActive(in); } WorkbenchPage::Pointer newPage = pageList.GetActive(); //Composite parent = getPageComposite(); //StackLayout layout = (StackLayout) parent.getLayout(); if (newPage.IsNotNull()) { //layout.topControl = newPage.getClientComposite(); //parent.layout(); this->HideEmptyWindowContents(); newPage->OnActivate(); //this->FirePageActivated(newPage); partService.PageActivated(newPage); //TODO perspective if (newPage->GetPerspective() != 0) { this->FirePerspectiveActivated(newPage, newPage->GetPerspective()); } } else { //layout.topControl = null; //parent.layout(); } //updateFastViewBar(); if (this->IsClosing()) { return; } updateDisabled = false; // Update action bars ( implicitly calls updateActionBars() ) //updateActionSets(); //submitGlobalActions(); //if (perspectiveSwitcher != null) { // perspectiveSwitcher.update(false); //} GetMenuManager()->Update(QActionProperties::TEXT); // } //}); } MenuManager *WorkbenchWindow::GetMenuManager() const { return this->GetMenuBarManager(); } bool WorkbenchWindow::SaveState(IMemento::Pointer memento) { // MultiStatus result = new MultiStatus(PlatformUI.PLUGIN_ID, IStatus.OK, // WorkbenchMessages.WorkbenchWindow_problemsSavingWindow, null); bool result = true; // Save the window's state and bounds. if (GetShell()->GetMaximized() || asMaximizedState) { memento->PutString(WorkbenchConstants::TAG_MAXIMIZED, "true"); } if (GetShell()->GetMinimized()) { memento->PutString(WorkbenchConstants::TAG_MINIMIZED, "true"); } if (normalBounds.isEmpty()) { normalBounds = GetShell()->GetBounds(); } // IMemento fastViewBarMem = memento // .createChild(IWorkbenchConstants.TAG_FAST_VIEW_DATA); // if (fastViewBar != null) { // fastViewBar.saveState(fastViewBarMem); // } memento->PutInteger(WorkbenchConstants::TAG_X, normalBounds.x()); memento->PutInteger(WorkbenchConstants::TAG_Y, normalBounds.y()); memento->PutInteger(WorkbenchConstants::TAG_WIDTH, normalBounds.width()); memento->PutInteger(WorkbenchConstants::TAG_HEIGHT, normalBounds.height()); IWorkbenchPage::Pointer activePage = GetActivePage(); if (activePage && activePage->FindView(IntroConstants::INTRO_VIEW_ID)) { IMemento::Pointer introMem = memento ->CreateChild(WorkbenchConstants::TAG_INTRO); bool isStandby = GetWorkbench()->GetIntroManager() ->IsIntroStandby(GetWorkbench()->GetIntroManager()->GetIntro()); introMem->PutBoolean(WorkbenchConstants::TAG_STANDBY, isStandby); } // // save the width of the perspective bar // IMemento persBarMem = memento // .createChild(IWorkbenchConstants.TAG_PERSPECTIVE_BAR); // if (perspectiveSwitcher != null) { // perspectiveSwitcher.saveState(persBarMem); // } // // / Save the order of the cool bar contribution items // ICoolBarManager2 coolBarMgr = (ICoolBarManager2) getCoolBarManager2(); // if (coolBarMgr != null) { // coolBarMgr.refresh(); // IMemento coolBarMem = memento // .createChild(IWorkbenchConstants.TAG_COOLBAR_LAYOUT); // if (coolBarMgr.getLockLayout() == true) { // coolBarMem.putInteger(IWorkbenchConstants.TAG_LOCKED, 1); // } else { // coolBarMem.putInteger(IWorkbenchConstants.TAG_LOCKED, 0); // } // IContributionItem[] items = coolBarMgr.getItems(); // for (int i = 0; i < items.length; i++) { // IMemento coolItemMem = coolBarMem // .createChild(IWorkbenchConstants.TAG_COOLITEM); // IContributionItem item = items[i]; // // The id of the contribution item // if (item.getId() != null) { // coolItemMem.putString(IWorkbenchConstants.TAG_ID, item // .getId()); // } // // Write out type and size if applicable // if (item.isSeparator()) { // coolItemMem.putString(IWorkbenchConstants.TAG_ITEM_TYPE, // IWorkbenchConstants.TAG_TYPE_SEPARATOR); // } else if (item.isGroupMarker() && !item.isSeparator()) { // coolItemMem.putString(IWorkbenchConstants.TAG_ITEM_TYPE, // IWorkbenchConstants.TAG_TYPE_GROUPMARKER); // } else { // if (item instanceof PlaceholderContributionItem) { // coolItemMem.putString( // IWorkbenchConstants.TAG_ITEM_TYPE, // IWorkbenchConstants.TAG_TYPE_PLACEHOLDER); // } else { // // Store the identifier. // coolItemMem // .putString( // IWorkbenchConstants.TAG_ITEM_TYPE, // IWorkbenchConstants.TAG_TYPE_TOOLBARCONTRIBUTION); // } // // /* // * Retrieve a reasonable approximation of the height and // * width, if possible. // */ // final int height; // final int width; // if (item instanceof IToolBarContributionItem) { // IToolBarContributionItem toolBarItem = (IToolBarContributionItem) item; // toolBarItem.saveWidgetState(); // height = toolBarItem.getCurrentHeight(); // width = toolBarItem.getCurrentWidth(); // } else if (item instanceof PlaceholderContributionItem) { // PlaceholderContributionItem placeholder = (PlaceholderContributionItem) item; // height = placeholder.getHeight(); // width = placeholder.getWidth(); // } else { // height = -1; // width = -1; // } // // // Store the height and width. // coolItemMem.putInteger(IWorkbenchConstants.TAG_ITEM_X, // width); // coolItemMem.putInteger(IWorkbenchConstants.TAG_ITEM_Y, // height); // } // } // } // Save each page. for (PageList::iterator itr = pageList.Begin(); itr != pageList.End(); ++itr) { WorkbenchPage::Pointer page = itr->Cast(); // Save perspective. IMemento::Pointer pageMem = memento ->CreateChild(WorkbenchConstants::TAG_PAGE); pageMem->PutString(WorkbenchConstants::TAG_LABEL, page->GetLabel()); //result.add(page.saveState(pageMem)); result &= page->SaveState(pageMem); if (page == GetActivePage().Cast()) { pageMem->PutString(WorkbenchConstants::TAG_FOCUS, "true"); } // // Get the input. // IAdaptable* input = page->GetInput(); // if (input != 0) { // IPersistableElement persistable = (IPersistableElement) Util.getAdapter(input, // IPersistableElement.class); // if (persistable == null) { // WorkbenchPlugin // .log("Unable to save page input: " //$NON-NLS-1$ // + input // + ", because it does not adapt to IPersistableElement"); //$NON-NLS-1$ // } else { // // Save input. // IMemento inputMem = pageMem // .createChild(IWorkbenchConstants.TAG_INPUT); // inputMem.putString(IWorkbenchConstants.TAG_FACTORY_ID, // persistable.getFactoryId()); // persistable.saveState(inputMem); // } // } } // Save window advisor state. IMemento::Pointer windowAdvisorState = memento ->CreateChild(WorkbenchConstants::TAG_WORKBENCH_WINDOW_ADVISOR); //result.add(getWindowAdvisor().saveState(windowAdvisorState)); result &= GetWindowAdvisor()->SaveState(windowAdvisorState); // Save actionbar advisor state. IMemento::Pointer actionBarAdvisorState = memento ->CreateChild(WorkbenchConstants::TAG_ACTION_BAR_ADVISOR); //result.add(getActionBarAdvisor().saveState(actionBarAdvisorState)); result &= GetActionBarAdvisor()->SaveState(actionBarAdvisorState); // // Only save the trim state if we're using the default layout // if (defaultLayout != null) { // IMemento trimState = memento.createChild(IWorkbenchConstants.TAG_TRIM); // result.add(saveTrimState(trimState)); // } return result; } WorkbenchWindowConfigurer::Pointer WorkbenchWindow::GetWindowConfigurer() const { if (windowConfigurer.IsNull()) { // lazy initialize windowConfigurer = new WorkbenchWindowConfigurer(WorkbenchWindow::Pointer(const_cast(this))); } return windowConfigurer; } bool WorkbenchWindow::CanHandleShellCloseEvent() { if (!Window::CanHandleShellCloseEvent()) { return false; } // let the advisor or other interested parties // veto the user's explicit request to close the window return FireWindowShellClosing(); } void WorkbenchWindow::ConfigureShell(Shell::Pointer shell) { Window::ConfigureShell(shell); detachedWindowShells = new ShellPool(shell, Constants::TITLE | Constants::MAX | Constants::CLOSE | Constants::RESIZE | Constants::BORDER ); QString title = this->GetWindowConfigurer()->BasicGetTitle(); if (!title.isEmpty()) { shell->SetText(title); } //IWorkbench* workbench = this->GetWorkbench(); // workbench.getHelpSystem().setHelp(shell, // IWorkbenchHelpContextIds.WORKBENCH_WINDOW); //IContextService* contextService = workbench->GetService(); //contextService->RegisterShell(shell, IContextService::TYPE_WINDOW); shell->GetControl()->installEventFilter(&resizeEventFilter); } ShellPool::Pointer WorkbenchWindow::GetDetachedWindowPool() { return detachedWindowShells; } WorkbenchAdvisor* WorkbenchWindow::GetAdvisor() { return this->GetWorkbenchImpl()->GetAdvisor(); } WorkbenchWindowAdvisor* WorkbenchWindow::GetWindowAdvisor() { if (windowAdvisor == nullptr) { windowAdvisor = this->GetAdvisor()->CreateWorkbenchWindowAdvisor(this->GetWindowConfigurer()); poco_check_ptr(windowAdvisor); } return windowAdvisor; } ActionBarAdvisor::Pointer WorkbenchWindow::GetActionBarAdvisor() { if (actionBarAdvisor.IsNull()) { actionBarAdvisor = this->GetWindowAdvisor()->CreateActionBarAdvisor(this->GetWindowConfigurer()->GetActionBarConfigurer()); poco_assert(actionBarAdvisor.IsNotNull()); } return actionBarAdvisor; } Workbench* WorkbenchWindow::GetWorkbenchImpl() { return dynamic_cast(this->GetWorkbench()); } void WorkbenchWindow::ShowEmptyWindowContents() { if (!emptyWindowContentsCreated) { QWidget* parent = this->GetPageComposite(); emptyWindowContents = this->GetWindowAdvisor()->CreateEmptyWindowContents( parent); emptyWindowContentsCreated = true; // // force the empty window composite to be layed out // ((StackLayout) parent.getLayout()).topControl = emptyWindowContents; // parent.layout(); } } void WorkbenchWindow::HideEmptyWindowContents() { if (emptyWindowContentsCreated) { if (emptyWindowContents != nullptr) { Tweaklets::Get(GuiWidgetsTweaklet::KEY)->Dispose(emptyWindowContents); emptyWindowContents = nullptr; //this->GetPageComposite().layout(); } emptyWindowContentsCreated = false; } } WorkbenchWindow::ServiceLocatorOwner::ServiceLocatorOwner(WorkbenchWindow* wnd) : window(wnd) { } void WorkbenchWindow::ServiceLocatorOwner::Dispose() { Shell::Pointer shell = window->GetShell(); if (shell != 0) { window->Close(); } } bool WorkbenchWindow::PageList::Add(IWorkbenchPage::Pointer object) { pagesInCreationOrder.push_back(object); pagesInActivationOrder.push_front(object); // It will be moved to top only when activated. return true; } WorkbenchWindow::PageList::iterator WorkbenchWindow::PageList::Begin() { return pagesInCreationOrder.begin(); } WorkbenchWindow::PageList::iterator WorkbenchWindow::PageList::End() { return pagesInCreationOrder.end(); } bool WorkbenchWindow::PageList::Contains(IWorkbenchPage::Pointer object) { return std::find(pagesInCreationOrder.begin(), pagesInCreationOrder.end(), object) != pagesInCreationOrder.end(); } bool WorkbenchWindow::PageList::Remove(IWorkbenchPage::Pointer object) { if (active == object) { active = nullptr; } pagesInActivationOrder.removeAll(object); const int origSize = pagesInCreationOrder.size(); pagesInCreationOrder.removeAll(object); return origSize != pagesInCreationOrder.size(); } void WorkbenchWindow::PageList::Clear() { pagesInCreationOrder.clear(); pagesInActivationOrder.clear(); active = nullptr; } bool WorkbenchWindow::PageList::IsEmpty() { return pagesInCreationOrder.empty(); } QList WorkbenchWindow::PageList::GetPages() const { return pagesInCreationOrder; } void WorkbenchWindow::PageList::SetActive(IWorkbenchPage::Pointer page) { if (active == page) { return; } active = page; if (page.IsNotNull()) { pagesInActivationOrder.removeAll(page); pagesInActivationOrder.push_back(page); } } WorkbenchPage::Pointer WorkbenchWindow::PageList::GetActive() const { return active.Cast(); } WorkbenchPage::Pointer WorkbenchWindow::PageList::GetNextActive() { if (active.IsNull()) { if (pagesInActivationOrder.empty()) { return WorkbenchPage::Pointer(nullptr); } return pagesInActivationOrder.back().Cast(); } if (pagesInActivationOrder.size() < 2) { return WorkbenchPage::Pointer(nullptr); } return pagesInActivationOrder.at(pagesInActivationOrder.size()-2).Cast(); } bool WorkbenchWindow::UpdatesDeferred() const { return largeUpdates > 0; } void WorkbenchWindow::InitializeDefaultServices() { workbenchLocationService.reset( new WorkbenchLocationService(IServiceScopes::WINDOW_SCOPE, GetWorkbench(), this, nullptr, 1)); workbenchLocationService->Register(); serviceLocator->RegisterService(workbenchLocationService.data()); //ActionCommandMappingService* mappingService = new ActionCommandMappingService(); //serviceLocator->RegisterService(IActionCommandMappingService, mappingService); } QSet > WorkbenchWindow::GetMenuRestrictions() const { return QSet >(); } void WorkbenchWindow::FirePropertyChanged(const QString& property, const Object::Pointer& oldValue, const Object::Pointer& newValue) { PropertyChangeEvent::Pointer event(new PropertyChangeEvent(Object::Pointer(this), property, oldValue, newValue)); genericPropertyListeners.propertyChange(event); } WorkbenchWindow::ShellEventFilter::ShellEventFilter(WorkbenchWindow* window) : window(window) { } bool WorkbenchWindow::ShellEventFilter::eventFilter(QObject* watched, QEvent* event) { QEvent::Type eventType = event->type(); if (eventType == QEvent::Move || eventType == QEvent::Resize) { QWidget* widget = static_cast(watched); QRect newBounds = widget->geometry(); if (eventType == QEvent::Move) { newBounds.setTopLeft(static_cast(event)->pos()); } else if(eventType == QEvent::Resize) { newBounds.setSize(static_cast(event)->size()); } this->SaveBounds(newBounds); } else if (eventType == QEvent::WindowActivate) { this->ShellActivated(); } else if (eventType == QEvent::WindowDeactivate) { this->ShellDeactivated(); } return false; } void WorkbenchWindow::ShellEventFilter::SaveBounds(const QRect& newBounds) { Shell::Pointer shell = window->GetShell(); if (shell == 0) { return; } // if (shell->IsDisposed()) // { // return; // } if (shell->GetMinimized()) { return; } if (shell->GetMaximized()) { window->asMaximizedState = true; return; } window->asMaximizedState = false; window->normalBounds = newBounds; } void WorkbenchWindow::ShellEventFilter::ShellActivated() { WorkbenchWindow::Pointer wnd(window); wnd->shellActivated = true; wnd->serviceLocator->Activate(); wnd->GetWorkbenchImpl()->SetActivatedWindow(wnd); WorkbenchPage::Pointer currentPage = wnd->GetActivePage().Cast(); if (currentPage != 0) { IWorkbenchPart::Pointer part = currentPage->GetActivePart(); if (part != 0) { PartSite::Pointer site = part->GetSite().Cast(); site->GetPane()->ShellActivated(); } IEditorPart::Pointer editor = currentPage->GetActiveEditor(); if (editor != 0) { PartSite::Pointer site = editor->GetSite().Cast(); site->GetPane()->ShellActivated(); } wnd->GetWorkbenchImpl()->FireWindowActivated(wnd); } //liftRestrictions(); } void WorkbenchWindow::ShellEventFilter::ShellDeactivated() { WorkbenchWindow::Pointer wnd(window); wnd->shellActivated = false; //imposeRestrictions(); wnd->serviceLocator->Deactivate(); WorkbenchPage::Pointer currentPage = wnd->GetActivePage().Cast(); if (currentPage != 0) { IWorkbenchPart::Pointer part = currentPage->GetActivePart(); if (part != 0) { PartSite::Pointer site = part->GetSite().Cast(); site->GetPane()->ShellDeactivated(); } IEditorPart::Pointer editor = currentPage->GetActiveEditor(); if (editor != 0) { PartSite::Pointer site = editor->GetSite().Cast(); site->GetPane()->ShellDeactivated(); } wnd->GetWorkbenchImpl()->FireWindowDeactivated(wnd); } } } diff --git a/Plugins/org.blueberry.ui.qt/src/internal/berryWorkbenchWindow.h b/Plugins/org.blueberry.ui.qt/src/internal/berryWorkbenchWindow.h index 4891d63dc2..d9f26c5d13 100644 --- a/Plugins/org.blueberry.ui.qt/src/internal/berryWorkbenchWindow.h +++ b/Plugins/org.blueberry.ui.qt/src/internal/berryWorkbenchWindow.h @@ -1,705 +1,707 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef BERRYWORKBENCHWINDOW_H_ #define BERRYWORKBENCHWINDOW_H_ #include "berryIWorkbenchWindow.h" #include "berryIPerspectiveListener.h" #include "berryWindow.h" #include "berryWorkbenchWindowConfigurer.h" #include "berryShellPool.h" #include "berryServiceLocator.h" #include "berryWWinPartService.h" #include "application/berryWorkbenchAdvisor.h" #include "application/berryWorkbenchWindowAdvisor.h" #include "application/berryActionBarAdvisor.h" #include #include namespace berry { struct IEvaluationReference; struct IWorkbench; struct IWorkbenchPage; struct IPartService; struct ISelectionService; struct IPerspectiveDescriptor; struct IWorkbenchLocationService; class Workbench; class WorkbenchPage; class WWinActionBars; /** * \ingroup org_blueberry_ui_qt * */ class BERRY_UI_QT WorkbenchWindow: public Window, public IWorkbenchWindow { public: /** * Toolbar visibility change property. */ static const QString PROP_TOOLBAR_VISIBLE; // = "toolbarVisible"; /** * Perspective bar visibility change property. */ static const QString PROP_PERSPECTIVEBAR_VISIBLE; // = "perspectiveBarVisible"; /** * The status line visibility change property. for internal use only. */ static const QString PROP_STATUS_LINE_VISIBLE; // = "statusLineVisible"; public: berryObjectMacro(WorkbenchWindow, Window, IWorkbenchWindow); WorkbenchWindow(int number); ~WorkbenchWindow() override; Object* GetService(const QString& key) override; bool HasService(const QString& key) const override; int Open(); bool Close() override; Shell::Pointer GetShell() const override; /** * @see org.blueberry.ui.IPageService */ void AddPerspectiveListener(IPerspectiveListener* l) override; /** * @see org.blueberry.ui.IPageService */ void RemovePerspectiveListener(IPerspectiveListener* l) override; /** * @see org.blueberry.ui.IPageService */ IPerspectiveListener::Events& GetPerspectiveEvents() override; /** * Returns the action bars for this window. */ WWinActionBars* GetActionBars(); SmartPointer GetActivePage() const override; QList > GetPages() const override; void SetPerspectiveExcludeList(const QStringList& v); QStringList GetPerspectiveExcludeList() const; void SetViewExcludeList(const QStringList& v); QStringList GetViewExcludeList() const; /** * Sets the active page within the window. * * @param in * identifies the new active page, or null for no * active page */ void SetActivePage(SmartPointer in) override; /** * Answer the menu manager for this window. */ MenuManager* GetMenuManager() const; IWorkbench* GetWorkbench() const override; + QList GetToolBars() const override; + IPartService* GetPartService() override; ISelectionService* GetSelectionService() const override; /** * @return whether the tool bar should be shown. This is only applicable if * the window configurer also wishes the cool bar to be visible. */ bool GetToolBarVisible() const; /** * @return whether the perspective bar should be shown. This is only * applicable if the window configurer also wishes the perspective * bar to be visible. */ bool GetPerspectiveBarVisible() const; /** * @return whether the status line should be shown. This is only applicable if * the window configurer also wishes status line to be visible. */ bool GetStatusLineVisible() const; /** * Add a generic property listener. * * @param listener the listener to add */ void AddPropertyChangeListener(IPropertyChangeListener* listener); /** * Removes a generic property listener. * * @param listener the listener to remove */ void RemovePropertyChangeListener(IPropertyChangeListener* listener); SmartPointer OpenPage(const QString& perspectiveId, IAdaptable* input) override; SmartPointer OpenPage(IAdaptable* input) override; //TODO menu manager //virtual QWidget* GetMenuManager() = 0; virtual bool SaveState(IMemento::Pointer memento); /** * Called when this window is about to be closed. * * Subclasses may override to add code that returns false to * prevent closing under certain conditions. */ virtual bool OkToClose(); bool RestoreState(IMemento::Pointer memento, SmartPointer< IPerspectiveDescriptor> activeDescriptor); /** * Returns the number. This corresponds to a page number in a window or a * window number in the workbench. */ int GetNumber(); /** * update the action bars. */ void UpdateActionBars(); /** *

* Indicates the start of a large update within this window. This is used to * disable CPU-intensive, change-sensitive services that were temporarily * disabled in the midst of large changes. This method should always be * called in tandem with largeUpdateEnd, and the event loop * should not be allowed to spin before that method is called. *

*

* Important: always use with largeUpdateEnd! *

* * @since 3.1 */ void LargeUpdateStart(); /** *

* Indicates the end of a large update within this window. This is used to * re-enable services that were temporarily disabled in the midst of large * changes. This method should always be called in tandem with * largeUpdateStart, and the event loop should not be * allowed to spin before this method is called. *

*

* Important: always protect this call by using finally! *

* * @since 3.1 */ void LargeUpdateEnd(); QSet > GetMenuRestrictions() const; protected: friend class WorkbenchConfigurer; friend class WorkbenchWindowConfigurer; friend class WorkbenchWindowConfigurer::WindowActionBarConfigurer; friend class Workbench; friend class LayoutPartSash; friend class EditorSashContainer; friend class WorkbenchPage; friend class DetachedWindow; /** * Returns the GUI dependent page composite, under which the window's * pages create their controls. */ QWidget* GetPageComposite(); /** * Creates and remembers the client composite, under which workbench pages * create their controls. */ QWidget* CreatePageComposite(QWidget* parent); /** * Creates the contents of the workbench window, including trim controls and * the client composite. This MUST create the client composite via a call to * createClientComposite. * * @since 3.0 */ QWidget* CreateContents(Shell::Pointer parent) override; /** * Creates the default contents and layout of the shell. * * @param shell * the shell */ virtual void CreateDefaultContents(Shell::Pointer shell); void CreateTrimWidgets(SmartPointer shell) override; /** * Returns the unique object that applications use to configure this window. *

* IMPORTANT This method is declared package-private to prevent regular * plug-ins from downcasting IWorkbenchWindow to WorkbenchWindow and getting * hold of the workbench window configurer that would allow them to tamper * with the workbench window. The workbench window configurer is available * only to the application. *

*/ WorkbenchWindowConfigurer::Pointer GetWindowConfigurer() const; bool CanHandleShellCloseEvent() override; /* * @see berry::Window#configureShell(Shell::Pointer) */ void ConfigureShell(Shell::Pointer shell) override; ShellPool::Pointer GetDetachedWindowPool(); /** * Fills the window's real action bars. * * @param flags * indicate which bars to fill */ void FillActionBars(ActionBarAdvisor::FillFlags flags); /** * The WorkbenchWindow implementation of this method * delegates to the window configurer. * * @since 3.0 */ QPoint GetInitialSize() override; /** * Returns the default page input for workbench pages opened in this window. * * @return the default page input or null if none * @since 3.1 */ IAdaptable* GetDefaultPageInput(); bool IsClosing(); /** * Opens a new page. Assumes that busy cursor is active. *

* Note: Since release 2.0, a window is limited to contain at most * one page. If a page exist in the window when this method is used, then * another window is created for the new page. Callers are strongly * recommended to use the IWorkbench.openPerspective APIs to * programmatically show a perspective. *

*/ SmartPointer BusyOpenPage(const QString& perspID, IAdaptable* input); bool ClosePage(SmartPointer in, bool save); /** * Makes the window visible and frontmost. */ void MakeVisible(); /** * The composite under which workbench pages create their controls. */ QWidget* pageComposite; private: /** * Constant indicating that all the actions bars should be filled. */ static const ActionBarAdvisor::FillFlags FILL_ALL_ACTION_BARS; ShellPool::Pointer detachedWindowShells; /** * Object for configuring this workbench window. Lazily initialized to an * instance unique to this window. */ mutable WorkbenchWindowConfigurer::Pointer windowConfigurer; WorkbenchWindowAdvisor* windowAdvisor; ActionBarAdvisor::Pointer actionBarAdvisor; SmartPointer actionBars; IPropertyChangeListener::Events genericPropertyListeners; int number; /** * The number of large updates that are currently going on. If this is * number is greater than zero, then UI updateActionBars is a no-op. */ int largeUpdates; bool closing; bool shellActivated; bool updateDisabled; bool toolBarVisible; bool perspectiveBarVisible; bool statusLineVisible; /** * The map of services maintained by the workbench window. These services * are initialized during workbench window during the * {@link #configureShell(Shell)}. */ ServiceLocator::Pointer serviceLocator; QScopedPointer workbenchLocationService; bool emptyWindowContentsCreated; QWidget* emptyWindowContents; QRect normalBounds; bool asMaximizedState; IPerspectiveListener::Events perspectiveEvents; WWinPartService partService; QStringList perspectiveExcludeList; QStringList viewExcludeList; struct ServiceLocatorOwner: public IDisposable { ServiceLocatorOwner(WorkbenchWindow* wnd); WorkbenchWindow* window; void Dispose() override; }; IDisposable::Pointer serviceLocatorOwner; class PageList { private: // List of pages in the order they were created; QList > pagesInCreationOrder; // List of pages where the top is the last activated. QList > pagesInActivationOrder; // The page explicitly activated SmartPointer active; public: typedef QList >::iterator iterator; bool Add(SmartPointer object); iterator Begin(); iterator End(); void Clear(); bool Contains(SmartPointer object); bool Remove(SmartPointer object); bool IsEmpty(); QList > GetPages() const; void SetActive(SmartPointer page); SmartPointer GetActive() const; SmartPointer GetNextActive(); }; PageList pageList; /** * Notifies interested parties (namely the advisor) that the window is about * to be opened. * * @since 3.1 */ void FireWindowOpening(); /** * Notifies interested parties (namely the advisor) that the window has been * restored from a previously saved state. * * @throws WorkbenchException * passed through from the advisor * @since 3.1 */ void FireWindowRestored(); /** * Notifies interested parties (namely the advisor) that the window has been * created. * * @since 3.1 */ void FireWindowCreated(); /** * Notifies interested parties (namely the advisor and the window listeners) * that the window has been opened. * * @since 3.1 */ void FireWindowOpened(); /** * Notifies interested parties (namely the advisor) that the window's shell * is closing. Allows the close to be vetoed. * * @return true if the close should proceed, * false if it should be canceled * @since 3.1 */ bool FireWindowShellClosing(); /** * Notifies interested parties (namely the advisor and the window listeners) * that the window has been closed. * * @since 3.1 */ void FireWindowClosed(); // /** // * Fires page activated // */ // void FirePageActivated(IWorkbenchPage::Pointer page); // // /** // * Fires page closed // */ // void FirePageClosed(IWorkbenchPage::Pointer page); // // /** // * Fires page opened // */ // void FirePageOpened(IWorkbenchPage::Pointer page); /** * Fires perspective activated */ void FirePerspectiveActivated(SmartPointer page, IPerspectiveDescriptor::Pointer perspective); /** * Fires perspective deactivated. * * @since 3.2 */ void FirePerspectivePreDeactivate(SmartPointer page, IPerspectiveDescriptor::Pointer perspective); /** * Fires perspective deactivated. * * @since 3.1 */ void FirePerspectiveDeactivated(SmartPointer page, IPerspectiveDescriptor::Pointer perspective); /** * Fires perspective changed */ void FirePerspectiveChanged(SmartPointer , IPerspectiveDescriptor::Pointer perspective, const QString& changeId); /** * Fires perspective changed for an affected part */ void FirePerspectiveChanged(SmartPointer , IPerspectiveDescriptor::Pointer perspective, IWorkbenchPartReference::Pointer partRef, const QString& changeId); /** * Fires perspective closed */ void FirePerspectiveClosed(SmartPointer , IPerspectiveDescriptor::Pointer perspective); /** * Fires perspective opened */ void FirePerspectiveOpened(SmartPointer , IPerspectiveDescriptor::Pointer perspective); /** * Fires perspective saved as. * * @since 3.1 */ void FirePerspectiveSavedAs(SmartPointer , IPerspectiveDescriptor::Pointer oldPerspective, IPerspectiveDescriptor::Pointer newPerspective); /** * Returns the workbench advisor. Assumes the workbench has been created * already. *

* IMPORTANT This method is declared private to prevent regular plug-ins * from downcasting IWorkbenchWindow to WorkbenchWindow and getting hold of * the workbench advisor that would allow them to tamper with the workbench. * The workbench advisor is internal to the application. *

*/ /* private - DO NOT CHANGE */ WorkbenchAdvisor* GetAdvisor(); /** * Returns the window advisor, creating a new one for this window if needed. *

* IMPORTANT This method is declared private to prevent regular plug-ins * from downcasting IWorkbenchWindow to WorkbenchWindow and getting hold of * the window advisor that would allow them to tamper with the window. The * window advisor is internal to the application. *

*/ /* private - DO NOT CHANGE */ WorkbenchWindowAdvisor* GetWindowAdvisor(); /** * Returns the action bar advisor, creating a new one for this window if * needed. *

* IMPORTANT This method is declared private to prevent regular plug-ins * from downcasting IWorkbenchWindow to WorkbenchWindow and getting hold of * the action bar advisor that would allow them to tamper with the window's * action bars. The action bar advisor is internal to the application. *

*/ /* private - DO NOT CHANGE */ ActionBarAdvisor::Pointer GetActionBarAdvisor(); /* * Returns the IWorkbench implementation. */ Workbench* GetWorkbenchImpl(); bool UnableToRestorePage(IMemento::Pointer pageMem); /** * Close the window. * * Assumes that busy cursor is active. */ bool BusyClose(); /** * Unconditionally close this window. Assumes the proper flags have been set * correctly (e.i. closing and updateDisabled) */ bool HardClose(); /** * Close all of the pages. */ void CloseAllPages(); /** * Save all of the pages. Returns true if the operation succeeded. */ bool SaveAllPages(bool bConfirm); void ShowEmptyWindowContents(); void HideEmptyWindowContents(); struct ShellEventFilter : public QObject { ShellEventFilter(WorkbenchWindow* window); bool eventFilter(QObject* watched, QEvent* event) override; private: void SaveBounds(const QRect& newBounds); void ShellActivated(); void ShellDeactivated(); WorkbenchWindow* window; }; ShellEventFilter resizeEventFilter; /** * Hooks a listener to track the resize of the window's shell. Stores the * new bounds if in normal state - that is, not in minimized or maximized * state) */ void TrackShellResize(Shell::Pointer newShell); /** * Returns true iff we are currently deferring UI processing due to a large * update * * @return true iff we are deferring UI updates. */ bool UpdatesDeferred() const; /** * Initializes all of the default command-based services for the workbench * window. */ void InitializeDefaultServices(); void FirePropertyChanged(const QString& property, const Object::Pointer& oldValue, const Object::Pointer& newValue); }; } #endif /*BERRYWORKBENCHWINDOW_H_*/ diff --git a/Plugins/org.mitk.gui.qt.application/plugin.xml b/Plugins/org.mitk.gui.qt.application/plugin.xml index 13a35d3932..e96ccfb5d2 100644 --- a/Plugins/org.mitk.gui.qt.application/plugin.xml +++ b/Plugins/org.mitk.gui.qt.application/plugin.xml @@ -1,23 +1,23 @@ - + - + diff --git a/Plugins/org.mitk.gui.qt.application/src/internal/QmitkToolBarsPreferencePage.cpp b/Plugins/org.mitk.gui.qt.application/src/internal/QmitkToolBarsPreferencePage.cpp index 4d69aee495..69d1f6d073 100644 --- a/Plugins/org.mitk.gui.qt.application/src/internal/QmitkToolBarsPreferencePage.cpp +++ b/Plugins/org.mitk.gui.qt.application/src/internal/QmitkToolBarsPreferencePage.cpp @@ -1,208 +1,153 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkToolBarsPreferencePage.h" #include #include #include #include #include #include -#include -#include - namespace { mitk::IPreferences* GetPreferences() { auto prefService = mitk::CoreServices::GetPreferencesService(); return prefService->GetSystemPreferences()->Node(QmitkApplicationConstants::TOOL_BARS_PREFERENCES); } - // Get views as multimap with categories as keys. - // - // Exclude views without category and categories that contain a literal '.', e.g. - // "org.blueberry.ui" or "org.mitk.views.general", as they typically do not have - // a corresponding tool bar. - std::multimap GetViews() - { - std::multimap result; - - const auto workbench = berry::PlatformUI::GetWorkbench(); - const auto viewRegistry = workbench->GetViewRegistry(); - const auto views = viewRegistry->GetViews(); - - for (auto view : views) - { - QString category; - - if (auto categoryPath = view->GetCategoryPath(); !categoryPath.isEmpty()) - category = categoryPath.back(); - - if (!category.isEmpty() && !category.contains('.') && !view->IsInternal()) - result.emplace(category, view); - } - - return result; - } - - // Get all toolbars of all (typically one) Workbench windows. - std::vector GetToolBars() - { - std::vector result; - - const auto* workbench = berry::PlatformUI::GetWorkbench(); - auto workbenchWindows = workbench->GetWorkbenchWindows(); - - for (auto workbenchWindow : workbenchWindows) - { - if (auto shell = workbenchWindow->GetShell(); shell.IsNotNull()) - { - if (const auto* mainWindow = qobject_cast(shell->GetControl()); mainWindow != nullptr) - { - for (auto child : mainWindow->children()) - { - if (auto toolBar = qobject_cast(child); toolBar != nullptr) - result.push_back(toolBar); - } - } - } - } - - return result; - } - // Find a toolbar by object name and apply preferences. - bool ApplyPreferences(const std::vector& toolBars, const QString& name, bool isVisible, bool showCategory) + bool ApplyPreferences(const QList& toolBars, const QString& name, bool isVisible, bool showCategory) { auto it = std::find_if(toolBars.cbegin(), toolBars.cend(), [&name](const QToolBar* toolBar) { return toolBar->objectName() == name; - }); + }); if (it != toolBars.cend()) { auto toolBar = *it; toolBar->setVisible(isVisible); for (auto action : toolBar->actions()) { if (action->objectName() == "category") { action->setVisible(showCategory); break; } } return true; } return false; } } QmitkToolBarsPreferencePage::QmitkToolBarsPreferencePage() : m_Ui(new Ui::QmitkToolBarsPreferencePage), m_Control(nullptr) { } QmitkToolBarsPreferencePage::~QmitkToolBarsPreferencePage() { } void QmitkToolBarsPreferencePage::Init(berry::IWorkbench::Pointer) { } void QmitkToolBarsPreferencePage::CreateQtControl(QWidget* parent) { m_Control = new QWidget(parent); m_Ui->setupUi(m_Control); - const auto views = GetViews(); + const auto views = berry::PlatformUI::GetWorkbench()->GetViewRegistry()->GetViewsByCategory(); - for (auto category = views.cbegin(), end = views.cend(); category != end; category = views.upper_bound(category->first)) + for(const auto& category : views.uniqueKeys()) { auto categoryItem = new QTreeWidgetItem; - categoryItem->setText(0, category->first); + categoryItem->setText(0, category); categoryItem->setCheckState(0, Qt::Checked); - const auto range = views.equal_range(category->first); + auto [viewIter, end] = views.equal_range(category); - for (auto view = range.first; view != range.second; ++view) + while (viewIter != end) { auto viewItem = new QTreeWidgetItem; - viewItem->setText(0, view->second->GetLabel()); - viewItem->setIcon(0, view->second->GetImageDescriptor()); + viewItem->setText(0, (*viewIter)->GetLabel()); + viewItem->setIcon(0, (*viewIter)->GetImageDescriptor()); categoryItem->addChild(viewItem); + ++viewIter; } m_Ui->treeWidget->addTopLevelItem(categoryItem); } this->Update(); } QWidget* QmitkToolBarsPreferencePage::GetQtControl() const { return m_Control; } bool QmitkToolBarsPreferencePage::PerformOk() { auto prefs = GetPreferences(); bool showCategories = m_Ui->showCategoriesCheckBox->isChecked(); prefs->PutBool(QmitkApplicationConstants::TOOL_BARS_SHOW_CATEGORIES, showCategories); - const auto toolBars = GetToolBars(); + const auto toolBars = berry::PlatformUI::GetWorkbench()->GetWorkbenchWindows().first()->GetToolBars(); for (int i = 0, count = m_Ui->treeWidget->topLevelItemCount(); i < count; ++i) { const auto* item = m_Ui->treeWidget->topLevelItem(i); const auto category = item->text(0); const bool isVisible = item->checkState(0) == Qt::Checked; prefs->PutBool(category.toStdString(), isVisible); if (!ApplyPreferences(toolBars, category, isVisible, showCategories)) MITK_WARN << "Could not find tool bar for category \"" << category << "\" to set its visibility!"; } return true; } void QmitkToolBarsPreferencePage::PerformCancel() { } void QmitkToolBarsPreferencePage::Update() { const auto prefs = GetPreferences(); m_Ui->showCategoriesCheckBox->setChecked(prefs->GetBool(QmitkApplicationConstants::TOOL_BARS_SHOW_CATEGORIES, true)); for (int i = 0, count = m_Ui->treeWidget->topLevelItemCount(); i < count; ++i) { auto item = m_Ui->treeWidget->topLevelItem(i); const auto category = item->text(0).toStdString(); const bool isVisible = prefs->GetBool(category, true); item->setCheckState(0, isVisible ? Qt::Checked : Qt::Unchecked); } } diff --git a/Plugins/org.mitk.gui.qt.ext/files.cmake b/Plugins/org.mitk.gui.qt.ext/files.cmake index d00f357e6c..26872bf821 100644 --- a/Plugins/org.mitk.gui.qt.ext/files.cmake +++ b/Plugins/org.mitk.gui.qt.ext/files.cmake @@ -1,61 +1,65 @@ set(SRC_CPP_FILES QmitkExtActionBarAdvisor.cpp QmitkExtWorkbenchWindowAdvisor.cpp QmitkExtFileSaveProjectAction.cpp QmitkOpenDicomEditorAction.cpp QmitkOpenMxNMultiWidgetEditorAction.cpp QmitkOpenStdMultiWidgetEditorAction.cpp + QmitkStartupDialog.cpp ) set(INTERNAL_CPP_FILES QmitkAboutHandler.cpp QmitkAppInstancesPreferencePage.cpp - QmitkExternalProgramsPreferencePage.cpp + QmitkStartupPreferencePage.cpp QmitkCommonExtPlugin.cpp QmitkModuleView.cpp ) set(UI_FILES src/internal/QmitkAppInstancesPreferencePage.ui - src/internal/QmitkExternalProgramsPreferencePage.ui + src/internal/QmitkStartupPreferencePage.ui + src/QmitkStartupDialog.ui ) set(MOC_H_FILES src/QmitkExtFileSaveProjectAction.h src/QmitkExtWorkbenchWindowAdvisor.h src/internal/QmitkAboutHandler.h src/internal/QmitkAppInstancesPreferencePage.h - src/internal/QmitkExternalProgramsPreferencePage.h + src/internal/QmitkStartupPreferencePage.h src/internal/QmitkCommonExtPlugin.h src/internal/QmitkExtWorkbenchWindowAdvisorHack.h src/internal/QmitkModuleView.h src/QmitkOpenDicomEditorAction.h src/QmitkOpenMxNMultiWidgetEditorAction.h src/QmitkOpenStdMultiWidgetEditorAction.h + src/QmitkStartupDialog.h ) set(CACHED_RESOURCE_FILES # list of resource files which can be used by the plug-in # system without loading the plug-ins shared library, # for example the icon used in the menu and tabs for the # plug-in views in the workbench plugin.xml resources/ModuleView.png ) set(QRC_FILES # uncomment the following line if you want to use Qt resources resources/org_mitk_gui_qt_ext.qrc resources/org_mitk_icons.qrc + resources/org_mitk_presets.qrc ) set(CPP_FILES ) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.ext/plugin.xml b/Plugins/org.mitk.gui.qt.ext/plugin.xml index 2c87bf9f64..d5ab903f46 100644 --- a/Plugins/org.mitk.gui.qt.ext/plugin.xml +++ b/Plugins/org.mitk.gui.qt.ext/plugin.xml @@ -1,37 +1,36 @@ - - + + - + diff --git a/Plugins/org.mitk.gui.qt.ext/resources/org_mitk_presets.qrc b/Plugins/org.mitk.gui.qt.ext/resources/org_mitk_presets.qrc new file mode 100644 index 0000000000..afd4787eb2 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/resources/org_mitk_presets.qrc @@ -0,0 +1,8 @@ + + + presets/classic.json + presets/segmentation_only.json + presets/modelfit.json + presets/custom.json + + diff --git a/Plugins/org.mitk.gui.qt.ext/resources/presets/classic.json b/Plugins/org.mitk.gui.qt.ext/resources/presets/classic.json new file mode 100644 index 0000000000..4c107129e4 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/resources/presets/classic.json @@ -0,0 +1,12 @@ +{ + "name": "Classic", + "info": "Display commonly used plugins, filtering out those that are less relevant to the majority of users.", + "categories": [ + "Data", + "General", + "Help", + "Quantification", + "Registration", + "Segmentation" + ] +} diff --git a/Plugins/org.mitk.gui.qt.ext/resources/presets/custom.json b/Plugins/org.mitk.gui.qt.ext/resources/presets/custom.json new file mode 100644 index 0000000000..4d8d3e507d --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/resources/presets/custom.json @@ -0,0 +1,4 @@ +{ + "name": "Custom", + "info": "Create your own preset in the \"Tool Bars\" page of the preferences." +} diff --git a/Plugins/org.mitk.gui.qt.ext/resources/presets/modelfit.json b/Plugins/org.mitk.gui.qt.ext/resources/presets/modelfit.json new file mode 100644 index 0000000000..51f518c38a --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/resources/presets/modelfit.json @@ -0,0 +1,9 @@ +{ + "name": "ModelFit", + "info": "Display plugins for model fits and their exploration, including:\n\n
    \n
  • A general purpose fitting tool using frequently used generic models and formula parsing
  • \n
  • Pharmacokinetic analysis for DCE MRI using compartment models
  • \n
  • Semi-quantitative analysis for dynamic images using non-compartmental approaches
  • \n
  • Voxel-wise fit visualization for evaluation of fit quality
  • \n
", + "ancestor": "Classic", + "categories": [ + "Fitting", + "Perfusion" + ] +} diff --git a/Plugins/org.mitk.gui.qt.ext/resources/presets/segmentation_only.json b/Plugins/org.mitk.gui.qt.ext/resources/presets/segmentation_only.json new file mode 100644 index 0000000000..398cdd7b8b --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/resources/presets/segmentation_only.json @@ -0,0 +1,9 @@ +{ + "name": "Segmentation only", + "info": "Display only essential plugins necessary for segmentation.", + "categories": [ + "Data", + "General", + "Segmentation" + ] +} diff --git a/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp b/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp index b7d3a96ce9..64bf26566a 100644 --- a/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp +++ b/Plugins/org.mitk.gui.qt.ext/src/QmitkExtWorkbenchWindowAdvisor.cpp @@ -1,1433 +1,1504 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkExtWorkbenchWindowAdvisor.h" #include "QmitkExtActionBarAdvisor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // UGLYYY #include "internal/QmitkExtWorkbenchWindowAdvisorHack.h" #include "internal/QmitkCommonExtPlugin.h" #include "mitkUndoController.h" #include "mitkVerboseLimitedLinearUndo.h" #include #include #include #include #include #include +#include QmitkExtWorkbenchWindowAdvisorHack* QmitkExtWorkbenchWindowAdvisorHack::undohack = new QmitkExtWorkbenchWindowAdvisorHack(); QString QmitkExtWorkbenchWindowAdvisor::QT_SETTINGS_FILENAME = "QtSettings.ini"; static bool USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS = false; +namespace +{ + void ExecuteStartupDialog() + { + QmitkStartupDialog startupDialog; + + if (startupDialog.SkipDialog()) + return; + + try + { + if (startupDialog.exec() != QDialog::Accepted) + return; + } + catch (const mitk::Exception& e) + { + MITK_ERROR << e.GetDescription(); + return; + } + + // Create two lists: one for all categories and one subset for visible categories. + + QStringList allCategories = berry::PlatformUI::GetWorkbench()->GetViewRegistry()->GetViewsByCategory().uniqueKeys(); + QStringList visibleCategories; + + if (startupDialog.UsePreset()) + { + visibleCategories = startupDialog.GetPresetCategories(); + + if (visibleCategories.isEmpty()) // "Custom" preset + { + // Early-out and show the "Tool Bars" preference page instead. + QmitkExtWorkbenchWindowAdvisorHack::undohack->onEditPreferences("org.mitk.ToolBarsPreferencePage"); + return; + } + } + else + { + visibleCategories = allCategories; + } + + // Now set the visibility preferences for all categories and apply them instantly. + + auto prefsService = mitk::CoreServices::GetPreferencesService(); + auto prefs = prefsService->GetSystemPreferences()->Node(QmitkApplicationConstants::TOOL_BARS_PREFERENCES); + const auto toolBars = berry::PlatformUI::GetWorkbench()->GetWorkbenchWindows().first()->GetToolBars(); + + for (const auto& category : allCategories) + { + bool isVisible = visibleCategories.contains(category); + prefs->PutBool(category.toStdString(), isVisible); + + auto toolBarIter = std::find_if(toolBars.cbegin(), toolBars.cend(), [&category](const QToolBar* toolBar) { + return toolBar->objectName() == category; + }); + + if (toolBarIter != toolBars.cend()) + (*toolBarIter)->setVisible(isVisible); + } + + prefs->Flush(); + } +} + class PartListenerForTitle: public berry::IPartListener { public: PartListenerForTitle(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPartEventTypes() const override { return Events::ACTIVATED | Events::BROUGHT_TO_TOP | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartActivated(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartBroughtToTop(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref.Cast ()) { windowAdvisor->UpdateTitle(false); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& /*ref*/) override { windowAdvisor->UpdateTitle(false); } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { auto lockedLastActiveEditor = windowAdvisor->lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull() && ref->GetPart(false) == lockedLastActiveEditor) { windowAdvisor->UpdateTitle(true); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { auto lockedLastActiveEditor = windowAdvisor->lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull() && ref->GetPart(false) == lockedLastActiveEditor) { windowAdvisor->UpdateTitle(false); } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; }; class PartListenerForViewNavigator: public berry::IPartListener { public: PartListenerForViewNavigator(QAction* act) : viewNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.viewnavigator") { viewNavigatorAction->setChecked(false); } } private: QAction* viewNavigatorAction; }; class PartListenerForImageNavigator: public berry::IPartListener { public: PartListenerForImageNavigator(QAction* act) : imageNavigatorAction(act) { } Events::Types GetPartEventTypes() const override { return Events::OPENED | Events::CLOSED | Events::HIDDEN | Events::VISIBLE; } void PartOpened(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartClosed(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } void PartVisible(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(true); } } void PartHidden(const berry::IWorkbenchPartReference::Pointer& ref) override { if (ref->GetId()=="org.mitk.views.imagenavigator") { imageNavigatorAction->setChecked(false); } } private: QAction* imageNavigatorAction; }; class PerspectiveListenerForTitle: public berry::IPerspectiveListener { public: PerspectiveListenerForTitle(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) , perspectivesClosed(false) { } Events::Types GetPerspectiveEventTypes() const override { if (USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED; } else { return Events::ACTIVATED | Events::SAVED_AS | Events::DEACTIVATED | Events::CLOSED | Events::OPENED; } } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveSavedAs(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*oldPerspective*/, const berry::IPerspectiveDescriptor::Pointer& /*newPerspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { windowAdvisor->UpdateTitle(false); } void PerspectiveOpened(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { if (perspectivesClosed) { QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(true); } //GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if(windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { windowAdvisor->openDicomEditorAction->setEnabled(true); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { windowAdvisor->openStdMultiWidgetEditorAction->setEnabled(true); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { windowAdvisor->openMxNMultiWidgetEditorAction->setEnabled(true); } windowAdvisor->fileSaveProjectAction->setEnabled(true); windowAdvisor->closeProjectAction->setEnabled(true); windowAdvisor->undoAction->setEnabled(true); windowAdvisor->redoAction->setEnabled(true); windowAdvisor->imageNavigatorAction->setEnabled(true); windowAdvisor->viewNavigatorAction->setEnabled(true); windowAdvisor->resetPerspAction->setEnabled(true); if( windowAdvisor->GetShowClosePerspectiveMenuItem() ) { windowAdvisor->closePerspAction->setEnabled(true); } } perspectivesClosed = false; } void PerspectiveClosed(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& /*perspective*/) override { berry::IWorkbenchWindow::Pointer wnd = windowAdvisor->GetWindowConfigurer()->GetWindow(); bool allClosed = true; if (wnd->GetActivePage()) { QList perspectives(wnd->GetActivePage()->GetOpenPerspectives()); allClosed = perspectives.empty(); } if (allClosed) { perspectivesClosed = true; QListIterator i(windowAdvisor->viewActions); while (i.hasNext()) { i.next()->setEnabled(false); } if(windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { windowAdvisor->openDicomEditorAction->setEnabled(false); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { windowAdvisor->openStdMultiWidgetEditorAction->setEnabled(false); } if (windowAdvisor->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { windowAdvisor->openMxNMultiWidgetEditorAction->setEnabled(false); } windowAdvisor->fileSaveProjectAction->setEnabled(false); windowAdvisor->closeProjectAction->setEnabled(false); windowAdvisor->undoAction->setEnabled(false); windowAdvisor->redoAction->setEnabled(false); windowAdvisor->imageNavigatorAction->setEnabled(false); windowAdvisor->viewNavigatorAction->setEnabled(false); windowAdvisor->resetPerspAction->setEnabled(false); if( windowAdvisor->GetShowClosePerspectiveMenuItem() ) { windowAdvisor->closePerspAction->setEnabled(false); } } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; bool perspectivesClosed; }; class PerspectiveListenerForMenu: public berry::IPerspectiveListener { public: PerspectiveListenerForMenu(QmitkExtWorkbenchWindowAdvisor* wa) : windowAdvisor(wa) { } Events::Types GetPerspectiveEventTypes() const override { return Events::ACTIVATED | Events::DEACTIVATED; } void PerspectiveActivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(true); } } void PerspectiveDeactivated(const berry::IWorkbenchPage::Pointer& /*page*/, const berry::IPerspectiveDescriptor::Pointer& perspective) override { QAction* action = windowAdvisor->mapPerspIdToAction[perspective->GetId()]; if (action) { action->setChecked(false); } } private: QmitkExtWorkbenchWindowAdvisor* windowAdvisor; }; QmitkExtWorkbenchWindowAdvisor::QmitkExtWorkbenchWindowAdvisor(berry::WorkbenchAdvisor* wbAdvisor, berry::IWorkbenchWindowConfigurer::Pointer configurer) : berry::WorkbenchWindowAdvisor(configurer) , lastInput(nullptr) , wbAdvisor(wbAdvisor) , showViewToolbar(true) , showPerspectiveToolbar(false) , showVersionInfo(true) , showMitkVersionInfo(true) , showViewMenuItem(true) , showNewWindowMenuItem(false) , showClosePerspectiveMenuItem(true) , viewNavigatorFound(false) , showMemoryIndicator(true) , dropTargetListener(new QmitkDefaultDropTargetListener) { productName = QCoreApplication::applicationName(); viewExcludeList.push_back("org.mitk.views.viewnavigator"); } QmitkExtWorkbenchWindowAdvisor::~QmitkExtWorkbenchWindowAdvisor() { } berry::ActionBarAdvisor::Pointer QmitkExtWorkbenchWindowAdvisor::CreateActionBarAdvisor(berry::IActionBarConfigurer::Pointer configurer) { if (USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { berry::ActionBarAdvisor::Pointer actionBarAdvisor(new QmitkExtActionBarAdvisor(configurer)); return actionBarAdvisor; } else { return berry::WorkbenchWindowAdvisor::CreateActionBarAdvisor(configurer); } } QWidget* QmitkExtWorkbenchWindowAdvisor::CreateEmptyWindowContents(QWidget* parent) { QWidget* parentWidget = static_cast(parent); auto label = new QLabel(parentWidget); label->setText("No perspectives are open. Open a perspective in the Window->Open Perspective menu."); label->setContentsMargins(10,10,10,10); label->setAlignment(Qt::AlignTop); label->setEnabled(false); parentWidget->layout()->addWidget(label); return label; } void QmitkExtWorkbenchWindowAdvisor::ShowClosePerspectiveMenuItem(bool show) { showClosePerspectiveMenuItem = show; } bool QmitkExtWorkbenchWindowAdvisor::GetShowClosePerspectiveMenuItem() { return showClosePerspectiveMenuItem; } void QmitkExtWorkbenchWindowAdvisor::ShowMemoryIndicator(bool show) { showMemoryIndicator = show; } bool QmitkExtWorkbenchWindowAdvisor::GetShowMemoryIndicator() { return showMemoryIndicator; } void QmitkExtWorkbenchWindowAdvisor::ShowNewWindowMenuItem(bool show) { showNewWindowMenuItem = show; } void QmitkExtWorkbenchWindowAdvisor::ShowViewToolbar(bool show) { showViewToolbar = show; } void QmitkExtWorkbenchWindowAdvisor::ShowViewMenuItem(bool show) { showViewMenuItem = show; } void QmitkExtWorkbenchWindowAdvisor::ShowPerspectiveToolbar(bool show) { showPerspectiveToolbar = show; } void QmitkExtWorkbenchWindowAdvisor::ShowVersionInfo(bool show) { showVersionInfo = show; } void QmitkExtWorkbenchWindowAdvisor::ShowMitkVersionInfo(bool show) { showMitkVersionInfo = show; } void QmitkExtWorkbenchWindowAdvisor::SetProductName(const QString& product) { productName = product; } void QmitkExtWorkbenchWindowAdvisor::SetWindowIcon(const QString& wndIcon) { windowIcon = wndIcon; } void QmitkExtWorkbenchWindowAdvisor::PostWindowCreate() { // very bad hack... berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = qobject_cast (window->GetShell()->GetControl()); if (!windowIcon.isEmpty()) { mainWindow->setWindowIcon(QIcon(windowIcon)); } mainWindow->setContextMenuPolicy(Qt::PreventContextMenu); // Load icon theme QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/org_mitk_icons/icons/")); QIcon::setThemeName(QStringLiteral("awesome")); // ==== Application menu ============================ QMenuBar* menuBar = mainWindow->menuBar(); menuBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ menuBar->setNativeMenuBar(true); #else menuBar->setNativeMenuBar(false); #endif auto basePath = QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/"); auto fileOpenAction = new QmitkFileOpenAction(berry::QtStyleManager::ThemeIcon(basePath + "document-open.svg"), window); fileOpenAction->setShortcut(QKeySequence::Open); auto fileSaveAction = new QmitkFileSaveAction(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg"), window); fileSaveAction->setShortcut(QKeySequence::Save); fileSaveProjectAction = new QmitkExtFileSaveProjectAction(window); fileSaveProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "document-save.svg")); closeProjectAction = new QmitkCloseProjectAction(window); closeProjectAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "edit-delete.svg")); auto perspGroup = new QActionGroup(menuBar); std::map VDMap; // sort elements (converting vector to map...) QList::const_iterator iter; berry::IViewRegistry* viewRegistry = berry::PlatformUI::GetWorkbench()->GetViewRegistry(); const QList viewDescriptors = viewRegistry->GetViews(); bool skip = false; for (iter = viewDescriptors.begin(); iter != viewDescriptors.end(); ++iter) { // if viewExcludeList is set, it contains the id-strings of view, which // should not appear as an menu-entry in the menu if (viewExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } if ((*iter)->GetId() == "org.blueberry.ui.internal.introview") continue; if ((*iter)->GetId() == "org.mitk.views.imagenavigator") continue; if ((*iter)->GetId() == "org.mitk.views.viewnavigator") continue; std::pair p((*iter)->GetLabel(), (*iter)); VDMap.insert(p); } std::map::const_iterator MapIter; for (MapIter = VDMap.begin(); MapIter != VDMap.end(); ++MapIter) { berry::QtShowViewAction* viewAction = new berry::QtShowViewAction(window, (*MapIter).second); viewActions.push_back(viewAction); } if (!USE_EXPERIMENTAL_COMMAND_CONTRIBUTIONS) { QMenu* fileMenu = menuBar->addMenu("&File"); fileMenu->setObjectName("FileMenu"); fileMenu->addAction(fileOpenAction); fileMenu->addAction(fileSaveAction); fileMenu->addAction(fileSaveProjectAction); fileMenu->addAction(closeProjectAction); fileMenu->addSeparator(); QAction* fileExitAction = new QmitkFileExitAction(window); fileExitAction->setIcon(berry::QtStyleManager::ThemeIcon(basePath + "system-log-out.svg")); fileExitAction->setShortcut(QKeySequence::Quit); fileExitAction->setObjectName("QmitkFileExitAction"); fileMenu->addAction(fileExitAction); // another bad hack to get an edit/undo menu... QMenu* editMenu = menuBar->addMenu("&Edit"); undoAction = editMenu->addAction( berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), "&Undo", QKeySequence("CTRL+Z"), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onUndo())); undoAction->setToolTip("Undo the last action (not supported by all modules)"); redoAction = editMenu->addAction( berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), "&Redo", QKeySequence("CTRL+Y"), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onRedo())); redoAction->setToolTip("execute the last action that was undone again (not supported by all modules)"); // ==== Window Menu ========================== QMenu* windowMenu = menuBar->addMenu("Window"); if (showNewWindowMenuItem) { windowMenu->addAction("&New Window", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onNewWindow())); windowMenu->addSeparator(); } QMenu* perspMenu = windowMenu->addMenu("&Open Perspective"); QMenu* viewMenu = nullptr; if (showViewMenuItem) { viewMenu = windowMenu->addMenu("Show &View"); viewMenu->setObjectName("Show View"); } windowMenu->addSeparator(); resetPerspAction = windowMenu->addAction("&Reset Perspective", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onResetPerspective())); if(showClosePerspectiveMenuItem) closePerspAction = windowMenu->addAction("&Close Perspective", QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onClosePerspective())); windowMenu->addSeparator(); windowMenu->addAction("&Preferences...", QKeySequence("CTRL+P"), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onEditPreferences())); // fill perspective menu berry::IPerspectiveRegistry* perspRegistry = window->GetWorkbench()->GetPerspectiveRegistry(); QList perspectives( perspRegistry->GetPerspectives()); skip = false; for (QList::iterator perspIt = perspectives.begin(); perspIt != perspectives.end(); ++perspIt) { // if perspectiveExcludeList is set, it contains the id-strings of perspectives, which // should not appear as an menu-entry in the perspective menu if (perspectiveExcludeList.size() > 0) { for (int i=0; iGetId()) { skip = true; break; } } if (skip) { skip = false; continue; } } QAction* perspAction = new berry::QtOpenPerspectiveAction(window, *perspIt, perspGroup); mapPerspIdToAction.insert((*perspIt)->GetId(), perspAction); } perspMenu->addActions(perspGroup->actions()); if (showViewMenuItem) { for (auto viewAction : std::as_const(viewActions)) { viewMenu->addAction(viewAction); } } // ===== Help menu ==================================== QMenu* helpMenu = menuBar->addMenu("&Help"); helpMenu->addAction("&Welcome",this, SLOT(onIntro())); helpMenu->addAction("&Open Help Perspective", this, SLOT(onHelpOpenHelpPerspective())); helpMenu->addAction("&Context Help", QKeySequence("F1"), this, SLOT(onHelp())); helpMenu->addAction("&About",this, SLOT(onAbout())); // ===================================================== } else { undoAction = new QmitkUndoAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-undo.svg"), nullptr); undoAction->setShortcut(QKeySequence::Undo); redoAction = new QmitkRedoAction(berry::QtStyleManager::ThemeIcon(basePath + "edit-redo.svg"), nullptr); redoAction->setShortcut(QKeySequence::Redo); } // toolbar for showing file open, undo, redo and other main actions auto mainActionsToolBar = new QToolBar; mainActionsToolBar->setObjectName("mainActionsToolBar"); mainActionsToolBar->setContextMenuPolicy(Qt::PreventContextMenu); #ifdef __APPLE__ mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextUnderIcon ); #else mainActionsToolBar->setToolButtonStyle ( Qt::ToolButtonTextBesideIcon ); #endif basePath = QStringLiteral(":/org.mitk.gui.qt.ext/"); imageNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(basePath + "image_navigator.svg"), "&Image Navigator", nullptr); bool imageNavigatorViewFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.imagenavigator"); if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { openDicomEditorAction = new QmitkOpenDicomEditorAction(berry::QtStyleManager::ThemeIcon(basePath + "dicom.svg"), window); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { openStdMultiWidgetEditorAction = new QmitkOpenStdMultiWidgetEditorAction(berry::QtStyleManager::ThemeIcon(basePath + "Editor.svg"), window); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { openMxNMultiWidgetEditorAction = new QmitkOpenMxNMultiWidgetEditorAction(berry::QtStyleManager::ThemeIcon(basePath + "Editor.svg"), window); } if (imageNavigatorViewFound) { QObject::connect(imageNavigatorAction, SIGNAL(triggered(bool)), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onImageNavigator())); imageNavigatorAction->setCheckable(true); // add part listener for image navigator imageNavigatorPartListener.reset(new PartListenerForImageNavigator(imageNavigatorAction)); window->GetPartService()->AddPartListener(imageNavigatorPartListener.data()); berry::IViewPart::Pointer imageNavigatorView = window->GetActivePage()->FindView("org.mitk.views.imagenavigator"); imageNavigatorAction->setChecked(false); if (imageNavigatorView) { bool isImageNavigatorVisible = window->GetActivePage()->IsPartVisible(imageNavigatorView); if (isImageNavigatorVisible) imageNavigatorAction->setChecked(true); } imageNavigatorAction->setToolTip("Toggle image navigator for navigating through image"); } viewNavigatorAction = new QAction(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org.mitk.gui.qt.ext/view-manager.svg")),"&View Navigator", nullptr); viewNavigatorFound = window->GetWorkbench()->GetViewRegistry()->Find("org.mitk.views.viewnavigator"); if (viewNavigatorFound) { QObject::connect(viewNavigatorAction, SIGNAL(triggered(bool)), QmitkExtWorkbenchWindowAdvisorHack::undohack, SLOT(onViewNavigator())); viewNavigatorAction->setCheckable(true); // add part listener for view navigator viewNavigatorPartListener.reset(new PartListenerForViewNavigator(viewNavigatorAction)); window->GetPartService()->AddPartListener(viewNavigatorPartListener.data()); berry::IViewPart::Pointer viewnavigatorview = window->GetActivePage()->FindView("org.mitk.views.viewnavigator"); viewNavigatorAction->setChecked(false); if (viewnavigatorview) { bool isViewNavigatorVisible = window->GetActivePage()->IsPartVisible(viewnavigatorview); if (isViewNavigatorVisible) viewNavigatorAction->setChecked(true); } viewNavigatorAction->setToolTip("Toggle View Navigator"); } mainActionsToolBar->addAction(fileOpenAction); mainActionsToolBar->addAction(fileSaveProjectAction); mainActionsToolBar->addAction(closeProjectAction); mainActionsToolBar->addAction(undoAction); mainActionsToolBar->addAction(redoAction); if(this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.dicombrowser")) { mainActionsToolBar->addAction(openDicomEditorAction); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.stdmultiwidget")) { mainActionsToolBar->addAction(openStdMultiWidgetEditorAction); } if (this->GetWindowConfigurer()->GetWindow()->GetWorkbench()->GetEditorRegistry()->FindEditor("org.mitk.editors.mxnmultiwidget")) { mainActionsToolBar->addAction(openMxNMultiWidgetEditorAction); } if (imageNavigatorViewFound) { mainActionsToolBar->addAction(imageNavigatorAction); } if (viewNavigatorFound) { mainActionsToolBar->addAction(viewNavigatorAction); } mainWindow->addToolBar(mainActionsToolBar); // ==== Perspective Toolbar ================================== auto qPerspectiveToolbar = new QToolBar; qPerspectiveToolbar->setObjectName("perspectiveToolBar"); if (showPerspectiveToolbar) { qPerspectiveToolbar->addActions(perspGroup->actions()); mainWindow->addToolBar(qPerspectiveToolbar); } else delete qPerspectiveToolbar; if (showViewToolbar) { auto* prefService = mitk::CoreServices::GetPreferencesService(); auto* toolBarsPrefs = prefService->GetSystemPreferences()->Node(QmitkApplicationConstants::TOOL_BARS_PREFERENCES); bool showCategories = toolBarsPrefs->GetBool(QmitkApplicationConstants::TOOL_BARS_SHOW_CATEGORIES, true); // Order view descriptors by category QMultiMap categoryViewDescriptorMap; for (const auto &labelViewDescriptorPair : VDMap) { auto viewDescriptor = labelViewDescriptorPair.second; auto category = !viewDescriptor->GetCategoryPath().isEmpty() ? viewDescriptor->GetCategoryPath().back() : QString(); categoryViewDescriptorMap.insert(category, viewDescriptor); } // Create a separate toolbar for each category for (const auto &category : categoryViewDescriptorMap.uniqueKeys()) { auto viewDescriptorsInCurrentCategory = categoryViewDescriptorMap.values(category); if (!viewDescriptorsInCurrentCategory.isEmpty()) { auto toolbar = new QToolBar; toolbar->setObjectName(category); mainWindow->addToolBar(toolbar); toolbar->setVisible(toolBarsPrefs->GetBool(category.toStdString(), true)); if (!category.isEmpty()) { auto categoryButton = new QToolButton; categoryButton->setToolButtonStyle(Qt::ToolButtonTextOnly); categoryButton->setText(category); categoryButton->setStyleSheet("background: transparent; margin: 0; padding: 0;"); auto action = toolbar->addWidget(categoryButton); action->setObjectName("category"); action->setVisible(showCategories); connect(categoryButton, &QToolButton::clicked, [toolbar]() { for (QWidget* widget : toolbar->findChildren()) { if (QStringLiteral("qt_toolbar_ext_button") == widget->objectName() && widget->isVisible()) { QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(0.0f, 0.0f), QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPointF(0.0f, 0.0f), QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(widget, &pressEvent); QApplication::sendEvent(widget, &releaseEvent); } } }); } for (const auto &viewDescriptor : std::as_const(viewDescriptorsInCurrentCategory)) { auto viewAction = new berry::QtShowViewAction(window, viewDescriptor); toolbar->addAction(viewAction); } } } } QSettings settings(GetQSettingsFile(), QSettings::IniFormat); mainWindow->restoreState(settings.value("ToolbarPosition").toByteArray()); auto qStatusBar = new QStatusBar(); //creating a QmitkStatusBar for Output on the QStatusBar and connecting it with the MainStatusBar auto statusBar = new QmitkStatusBar(qStatusBar); //disabling the SizeGrip in the lower right corner statusBar->SetSizeGripEnabled(false); auto progBar = new QmitkProgressBar(); qStatusBar->addPermanentWidget(progBar, 0); progBar->hide(); // progBar->AddStepsToDo(2); // progBar->Progress(1); mainWindow->setStatusBar(qStatusBar); if (showMemoryIndicator) { auto memoryIndicator = new QmitkMemoryUsageIndicatorView(); qStatusBar->addPermanentWidget(memoryIndicator, 0); } } void QmitkExtWorkbenchWindowAdvisor::PreWindowOpen() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); // show the shortcut bar and progress indicator, which are hidden by // default //configurer->SetShowPerspectiveBar(true); //configurer->SetShowFastViewBars(true); //configurer->SetShowProgressIndicator(true); // // add the drag and drop support for the editor area // configurer.addEditorAreaTransfer(EditorInputTransfer.getInstance()); // configurer.addEditorAreaTransfer(ResourceTransfer.getInstance()); // configurer.addEditorAreaTransfer(FileTransfer.getInstance()); // configurer.addEditorAreaTransfer(MarkerTransfer.getInstance()); // configurer.configureEditorAreaDropListener(new EditorAreaDropAdapter( // configurer.getWindow())); this->HookTitleUpdateListeners(configurer); menuPerspectiveListener.reset(new PerspectiveListenerForMenu(this)); configurer->GetWindow()->AddPerspectiveListener(menuPerspectiveListener.data()); configurer->AddEditorAreaTransfer(QStringList("text/uri-list")); configurer->ConfigureEditorAreaDropListener(dropTargetListener.data()); } void QmitkExtWorkbenchWindowAdvisor::PostWindowOpen() { berry::WorkbenchWindowAdvisor::PostWindowOpen(); // Force Rendering Window Creation on startup. berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); ctkPluginContext* context = QmitkCommonExtPlugin::getContext(); ctkServiceReference serviceRef = context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService *dsService = context->getService(serviceRef); if (dsService) { mitk::IDataStorageReference::Pointer dsRef = dsService->GetDataStorage(); mitk::DataStorageEditorInput::Pointer dsInput(new mitk::DataStorageEditorInput(dsRef)); mitk::WorkbenchUtil::OpenEditor(configurer->GetWindow()->GetActivePage(),dsInput); } } auto introPart = configurer->GetWindow()->GetWorkbench()->GetIntroManager()->GetIntro(); if (introPart.IsNotNull()) { configurer->GetWindow()->GetWorkbench()->GetIntroManager()->ShowIntro(GetWindowConfigurer()->GetWindow(), false); } + + ExecuteStartupDialog(); } void QmitkExtWorkbenchWindowAdvisor::onIntro() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onIntro(); } void QmitkExtWorkbenchWindowAdvisor::onHelp() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onHelp(); } void QmitkExtWorkbenchWindowAdvisor::onHelpOpenHelpPerspective() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onHelpOpenHelpPerspective(); } void QmitkExtWorkbenchWindowAdvisor::onAbout() { QmitkExtWorkbenchWindowAdvisorHack::undohack->onAbout(); } //-------------------------------------------------------------------------------- // Ugly hack from here on. Feel free to delete when command framework // and undo buttons are done. //-------------------------------------------------------------------------------- QmitkExtWorkbenchWindowAdvisorHack::QmitkExtWorkbenchWindowAdvisorHack() : QObject() { } QmitkExtWorkbenchWindowAdvisorHack::~QmitkExtWorkbenchWindowAdvisorHack() { } void QmitkExtWorkbenchWindowAdvisorHack::onUndo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast( model )) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetUndoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Undo " << descriptions.front().second; } } model->Undo(); } else { MITK_ERROR << "No undo model instantiated"; } } void QmitkExtWorkbenchWindowAdvisorHack::onRedo() { mitk::UndoModel* model = mitk::UndoController::GetCurrentUndoModel(); if (model) { if (mitk::VerboseLimitedLinearUndo* verboseundo = dynamic_cast( model )) { mitk::VerboseLimitedLinearUndo::StackDescription descriptions = verboseundo->GetRedoDescriptions(); if (descriptions.size() >= 1) { MITK_INFO << "Redo " << descriptions.front().second; } } model->Redo(); } else { MITK_ERROR << "No undo model instantiated"; } } // safe calls to the complete chain // berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.imagenavigator"); // to cover for all possible cases of closed pages etc. static void SafeHandleNavigatorView(QString view_query_name) { berry::IWorkbench* wbench = berry::PlatformUI::GetWorkbench(); if( wbench == nullptr ) return; berry::IWorkbenchWindow::Pointer wbench_window = wbench->GetActiveWorkbenchWindow(); if( wbench_window.IsNull() ) return; berry::IWorkbenchPage::Pointer wbench_page = wbench_window->GetActivePage(); if( wbench_page.IsNull() ) return; auto wbench_view = wbench_page->FindView( view_query_name ); if( wbench_view.IsNotNull() ) { bool isViewVisible = wbench_page->IsPartVisible( wbench_view ); if( isViewVisible ) { wbench_page->HideView( wbench_view ); return; } } wbench_page->ShowView( view_query_name ); } void QmitkExtWorkbenchWindowAdvisorHack::onImageNavigator() { // show/hide ImageNavigatorView SafeHandleNavigatorView("org.mitk.views.imagenavigator"); } void QmitkExtWorkbenchWindowAdvisorHack::onViewNavigator() { // show/hide viewnavigatorView SafeHandleNavigatorView("org.mitk.views.viewnavigator"); } -void QmitkExtWorkbenchWindowAdvisorHack::onEditPreferences() +void QmitkExtWorkbenchWindowAdvisorHack::onEditPreferences(const QString& selectedPage) { QmitkPreferencesDialog _PreferencesDialog(QApplication::activeWindow()); + + if (!selectedPage.isEmpty()) + _PreferencesDialog.SetSelectedPage(selectedPage); + _PreferencesDialog.exec(); } void QmitkExtWorkbenchWindowAdvisorHack::onQuit() { berry::PlatformUI::GetWorkbench()->Close(); } void QmitkExtWorkbenchWindowAdvisorHack::onResetPerspective() { berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage()->ResetPerspective(); } void QmitkExtWorkbenchWindowAdvisorHack::onClosePerspective() { berry::IWorkbenchPage::Pointer page = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()->GetActivePage(); page->ClosePerspective(page->GetPerspective(), true, true); } void QmitkExtWorkbenchWindowAdvisorHack::onNewWindow() { berry::PlatformUI::GetWorkbench()->OpenWorkbenchWindow(nullptr); } void QmitkExtWorkbenchWindowAdvisorHack::onIntro() { if (berry::PlatformUI::GetWorkbench()->GetIntroManager()->HasIntro()) { berry::PlatformUI::GetWorkbench()->GetIntroManager()->ShowIntro( berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(), false); } } void QmitkExtWorkbenchWindowAdvisorHack::onHelp() { ctkPluginContext* context = QmitkCommonExtPlugin::getContext(); if (context == nullptr) { MITK_WARN << "Plugin context not set, unable to open context help"; return; } // Check if the org.blueberry.ui.qt.help plug-in is installed and started QList > plugins = context->getPlugins(); foreach(QSharedPointer p, plugins) { if (p->getSymbolicName() == "org.blueberry.ui.qt.help") { if (p->getState() != ctkPlugin::ACTIVE) { // try to activate the plug-in explicitly try { p->start(ctkPlugin::START_TRANSIENT); } catch (const ctkPluginException& pe) { MITK_ERROR << "Activating org.blueberry.ui.qt.help failed: " << pe.what(); return; } } } } ctkServiceReference eventAdminRef = context->getServiceReference(); ctkEventAdmin* eventAdmin = nullptr; if (eventAdminRef) { eventAdmin = context->getService(eventAdminRef); } if (eventAdmin == nullptr) { MITK_WARN << "ctkEventAdmin service not found. Unable to open context help"; } else { ctkEvent ev("org/blueberry/ui/help/CONTEXTHELP_REQUESTED"); eventAdmin->postEvent(ev); } } void QmitkExtWorkbenchWindowAdvisorHack::onHelpOpenHelpPerspective() { berry::PlatformUI::GetWorkbench()->ShowPerspective("org.blueberry.perspectives.help", berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow()); } void QmitkExtWorkbenchWindowAdvisorHack::onAbout() { auto aboutDialog = new QmitkAboutDialog(QApplication::activeWindow(), {}); aboutDialog->open(); } void QmitkExtWorkbenchWindowAdvisor::HookTitleUpdateListeners(berry::IWorkbenchWindowConfigurer::Pointer configurer) { // hook up the listeners to update the window title titlePartListener.reset(new PartListenerForTitle(this)); titlePerspectiveListener.reset(new PerspectiveListenerForTitle(this)); editorPropertyListener.reset(new berry::PropertyChangeIntAdapter< QmitkExtWorkbenchWindowAdvisor>(this, &QmitkExtWorkbenchWindowAdvisor::PropertyChange)); // configurer.getWindow().addPageListener(new IPageListener() { // public void pageActivated(IWorkbenchPage page) { // updateTitle(false); // } // // public void pageClosed(IWorkbenchPage page) { // updateTitle(false); // } // // public void pageOpened(IWorkbenchPage page) { // // do nothing // } // }); configurer->GetWindow()->AddPerspectiveListener(titlePerspectiveListener.data()); configurer->GetWindow()->GetPartService()->AddPartListener(titlePartListener.data()); } QString QmitkExtWorkbenchWindowAdvisor::ComputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchPage::Pointer currentPage = configurer->GetWindow()->GetActivePage(); berry::IEditorPart::Pointer activeEditor; if (currentPage) { activeEditor = lastActiveEditor.Lock(); } QString title; berry::IProduct::Pointer product = berry::Platform::GetProduct(); if (product.IsNotNull()) { title = product->GetName(); } if (title.isEmpty()) { // instead of the product name, we use a custom variable for now title = productName; } if(showMitkVersionInfo) { QString mitkVersionInfo = MITK_REVISION_DESC; if(mitkVersionInfo.isEmpty()) mitkVersionInfo = MITK_VERSION_STRING; title += " " + mitkVersionInfo; } if (showVersionInfo) { // add version informatioin QString versions = QString(" (ITK %1.%2.%3 | VTK %4.%5.%6 | Qt %7)") .arg(ITK_VERSION_MAJOR).arg(ITK_VERSION_MINOR).arg(ITK_VERSION_PATCH) .arg(VTK_MAJOR_VERSION).arg(VTK_MINOR_VERSION).arg(VTK_BUILD_VERSION) .arg(QT_VERSION_STR); title += versions; } if (currentPage) { if (activeEditor) { lastEditorTitle = activeEditor->GetTitleToolTip(); if (!lastEditorTitle.isEmpty()) title = lastEditorTitle + " - " + title; } berry::IPerspectiveDescriptor::Pointer persp = currentPage->GetPerspective(); QString label = ""; if (persp) { label = persp->GetLabel(); } berry::IAdaptable* input = currentPage->GetInput(); if (input && input != wbAdvisor->GetDefaultPageInput()) { label = currentPage->GetLabel(); } if (!label.isEmpty()) { title = label + " - " + title; } } title += " (Not for use in diagnosis or treatment of patients)"; return title; } void QmitkExtWorkbenchWindowAdvisor::RecomputeTitle() { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); QString oldTitle = configurer->GetTitle(); QString newTitle = ComputeTitle(); if (newTitle != oldTitle) { configurer->SetTitle(newTitle); } } void QmitkExtWorkbenchWindowAdvisor::UpdateTitle(bool editorHidden) { berry::IWorkbenchWindowConfigurer::Pointer configurer = GetWindowConfigurer(); berry::IWorkbenchWindow::Pointer window = configurer->GetWindow(); berry::IEditorPart::Pointer activeEditor; berry::IWorkbenchPage::Pointer currentPage = window->GetActivePage(); berry::IPerspectiveDescriptor::Pointer persp; berry::IAdaptable* input = nullptr; if (currentPage) { activeEditor = currentPage->GetActiveEditor(); persp = currentPage->GetPerspective(); input = currentPage->GetInput(); } if (editorHidden) { activeEditor = nullptr; } // Nothing to do if the editor hasn't changed if (activeEditor == lastActiveEditor.Lock() && currentPage == lastActivePage.Lock() && persp == lastPerspective.Lock() && input == lastInput) { return; } auto lockedLastActiveEditor = lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull()) { lockedLastActiveEditor->RemovePropertyListener(editorPropertyListener.data()); } lastActiveEditor = activeEditor; lastActivePage = currentPage; lastPerspective = persp; lastInput = input; if (activeEditor) { activeEditor->AddPropertyListener(editorPropertyListener.data()); } RecomputeTitle(); } void QmitkExtWorkbenchWindowAdvisor::PropertyChange(const berry::Object::Pointer& /*source*/, int propId) { if (propId == berry::IWorkbenchPartConstants::PROP_TITLE) { auto lockedLastActiveEditor = lastActiveEditor.Lock(); if (lockedLastActiveEditor.IsNotNull()) { QString newTitle = lockedLastActiveEditor->GetPartName(); if (lastEditorTitle != newTitle) { RecomputeTitle(); } } } } void QmitkExtWorkbenchWindowAdvisor::SetPerspectiveExcludeList(const QList& v) { this->perspectiveExcludeList = v; } QList QmitkExtWorkbenchWindowAdvisor::GetPerspectiveExcludeList() { return this->perspectiveExcludeList; } void QmitkExtWorkbenchWindowAdvisor::SetViewExcludeList(const QList& v) { this->viewExcludeList = v; } QList QmitkExtWorkbenchWindowAdvisor::GetViewExcludeList() { return this->viewExcludeList; } void QmitkExtWorkbenchWindowAdvisor::PostWindowClose() { berry::IWorkbenchWindow::Pointer window = this->GetWindowConfigurer()->GetWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); auto fileName = this->GetQSettingsFile(); if (!fileName.isEmpty()) { QSettings settings(fileName, QSettings::IniFormat); settings.setValue("ToolbarPosition", mainWindow->saveState()); } } QString QmitkExtWorkbenchWindowAdvisor::GetQSettingsFile() const { QFileInfo settingsInfo = QmitkCommonExtPlugin::getContext()->getDataFile(QT_SETTINGS_FILENAME); return settingsInfo.canonicalFilePath(); } diff --git a/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.cpp b/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.cpp new file mode 100644 index 0000000000..aada1234db --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.cpp @@ -0,0 +1,276 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include +#include + +#include + +#include + +namespace +{ + mitk::IPreferences* GetPreferences() + { + auto* preferencesService = mitk::CoreServices::GetPreferencesService(); + return preferencesService->GetSystemPreferences()->Node("org.mitk.startupdialog"); + } +} + +QT_BEGIN_NAMESPACE + + // Make nlohmann_json understand QString for deserialization. + void from_json(const nlohmann::json& j, QString& result) + { + result = QString::fromStdString(j.get()); + } + +QT_END_NAMESPACE + +class Preset : public QListWidgetItem +{ +public: + static constexpr int NameRole = Qt::DisplayRole; + static constexpr int InfoRole = Qt::UserRole + 1; + static constexpr int CategoriesRole = Qt::UserRole + 2; + + // Load preset from file. See remarks on the Inherit() method below. + explicit Preset(const QString& filename) + { + QFile file(filename); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + mitkThrow() << "Couldn't open \"" << filename.toStdString() << "\"!"; + + auto fileContents = QTextStream(&file).readAll().toStdString(); + auto j = nlohmann::json::parse(fileContents, nullptr, false, true); + + if (j.is_discarded()) + mitkThrow() << "Couldn't parse \"" << filename.toStdString() << "\"!"; + + this->Deserialize(j); + } + + // When all presets are loaded, their inheritances have to be resolved by + // iterating through them once and calling Inherit() on each preset. + void Inherit(const QList& presets) + { + if (m_Ancestor.isEmpty()) + return; + + auto isAncestor = [this](const Preset* p) { return p->GetName() == m_Ancestor; }; + auto ancestor = std::find_if(presets.begin(), presets.end(), isAncestor); + + if (ancestor == presets.end()) + { + mitkThrow() << "Preset \"" << this->GetName().toStdString() << "\" cannot inherit from \"" + << m_Ancestor.toStdString() << "\" because it couldn't be found."; + } + + (*ancestor)->Inherit(presets); // Recursively resolve inheritance of ancestors + this->MergeCategories(*ancestor); // Inherit by merging categories + + m_Ancestor.clear(); // No need to resolve inheritance again for this preset + } + + QString GetName() const + { + return this->data(NameRole).toString(); + } + + QString GetInfo() const + { + return this->data(InfoRole).toString(); + } + + QStringList GetCategories() const + { + return this->data(CategoriesRole).toStringList(); + } + +private: + /* Disclaimer: + * This is an internal, probably only temporal file format. + * + * Example: + * { + * "name": "Mandatory name that is displayed in UI", + * "info": "Optional info text in Qt's RichText format that is displayed when the preset is selected", + * "ancestor": "Optional name of another preset of which the categories are merged with this preset", + * "categories": [ + * "List of categories (resp. tool bars)", + * "Strictly speaking it is optional but its absence is a special case for the custom preset", + * "Hence, think of it as mandatory", + * "BTW, only use

and smaller for headers, if any, in the info text above" + * ] + * } + */ + void Deserialize(const nlohmann::json& j) + { + this->setData(Preset::NameRole, j["name"].get()); + + // Prepend name as header to info text + this->setData(Preset::InfoRole, QString("

%1 preset

\n\n%2").arg(this->GetName()).arg(j.value("info", QString()))); + + this->m_Ancestor = j.value("ancestor", QString()); + + if (j.contains("categories")) + this->setData(Preset::CategoriesRole, j["categories"].get()); + } + + void MergeCategories(const Preset* other) + { + auto categories = this->GetCategories(); + categories.append(other->GetCategories()); + categories.removeDuplicates(); + this->setData(CategoriesRole, categories); + } + + QString m_Ancestor; +}; + +QmitkStartupDialog::QmitkStartupDialog(QWidget* parent) + : QDialog(parent), + m_Ui(new Ui::QmitkStartupDialog) +{ + m_Ui->setupUi(this); + + connect(m_Ui->presetRadioButton, &QAbstractButton::toggled, this, &QmitkStartupDialog::OnPresetRadioButtonToggled); + connect(m_Ui->presetListWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QmitkStartupDialog::OnSelectedPresetChanged); + connect(this, &QDialog::finished, this, &QmitkStartupDialog::OnFinished); +} + +QmitkStartupDialog::~QmitkStartupDialog() +{ + delete m_Ui; +} + +bool QmitkStartupDialog::UsePreset() const +{ + return m_Ui->presetRadioButton->isChecked() && m_Ui->presetListWidget->currentItem() != nullptr; +} + +QString QmitkStartupDialog::GetPresetName() const +{ + return m_Ui->presetListWidget->currentItem()->data(Preset::NameRole).toString(); +} + +QStringList QmitkStartupDialog::GetPresetCategories() const +{ + return m_Ui->presetListWidget->currentItem()->data(Preset::CategoriesRole).toStringList(); +} + +// Check if "Do not show this dialog again" is ticked off. +bool QmitkStartupDialog::SkipDialog() const +{ + auto prefs = GetPreferences(); + return prefs->GetBool("skip", false); +} + +// Load preset resources. +// See "resources/org_mitk_presets.qrc". +void QmitkStartupDialog::LoadPresets() +{ + QList presets; + QDirIterator it(":/org_mitk_presets"); + + while (it.hasNext()) + presets.append(new Preset(it.next())); + + // Only after all presets are loaded their inheritances can be resolved + // and the presets finally added to the list widget. + for (auto preset : presets) + { + preset->Inherit(presets); + m_Ui->presetListWidget->addItem(preset); + } +} + +// Load presets and restore previous settings. +void QmitkStartupDialog::showEvent(QShowEvent*) +{ + this->LoadPresets(); + + auto prefs = GetPreferences(); + bool usePreset = prefs->GetBool("use preset", false); + + if (usePreset) + { + m_Ui->presetRadioButton->setChecked(true); + + QString preset = QString::fromStdString(prefs->Get("preset", "")); + + if (preset.isEmpty()) + return; + + auto items = m_Ui->presetListWidget->findItems(preset, Qt::MatchExactly); + + if (!items.isEmpty()) + m_Ui->presetListWidget->setCurrentItem(items.first()); + } +} + +void QmitkStartupDialog::OnPresetRadioButtonToggled(bool checked) +{ + m_Ui->presetListWidget->setEnabled(checked); + + if (!checked) + { + m_Ui->presetListWidget->clearSelection(); + } + else if (m_Ui->presetListWidget->count() > 0) + { + m_Ui->presetListWidget->setCurrentRow(0); + } +} + +void QmitkStartupDialog::OnSelectedPresetChanged(const QItemSelection& selected, const QItemSelection&) +{ + if (selected.empty()) + { + m_Ui->presetLabel->clear(); + return; + } + + m_Ui->presetLabel->setText(m_Ui->presetListWidget->selectedItems().front()->data(Preset::InfoRole).toString()); +} + +// Save settings to restore them next time. +void QmitkStartupDialog::OnFinished(int result) +{ + auto prefs = GetPreferences(); + + bool skipDialog = m_Ui->skipDialogCheckBox->isChecked(); + prefs->PutBool("skip", skipDialog); + + if (result == QDialog::Accepted) + { + bool usePreset = m_Ui->presetRadioButton->isChecked(); + prefs->PutBool("use preset", usePreset); + + if (usePreset) + { + auto preset = m_Ui->presetListWidget->currentItem()->text().toStdString(); + prefs->Put("preset", preset); + } + else + { + prefs->Remove("preset"); + } + } + + prefs->Flush(); +} diff --git a/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.h b/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.h new file mode 100644 index 0000000000..ac78f25336 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.h @@ -0,0 +1,46 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include + +class QItemSelection; + +namespace Ui +{ + class QmitkStartupDialog; +} + +class QmitkStartupDialog : public QDialog +{ + Q_OBJECT + +public: + explicit QmitkStartupDialog(QWidget* parent = nullptr); + ~QmitkStartupDialog() override; + + bool SkipDialog() const; + + bool UsePreset() const; + QString GetPresetName() const; + QStringList GetPresetCategories() const; + +private: + void LoadPresets(); + + void showEvent(QShowEvent* event) override; + + void OnPresetRadioButtonToggled(bool checked); + void OnSelectedPresetChanged(const QItemSelection& selected, const QItemSelection& deselected); + void OnFinished(int result); + + Ui::QmitkStartupDialog* m_Ui; +}; diff --git a/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.ui b/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.ui new file mode 100644 index 0000000000..5c66f1087a --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/src/QmitkStartupDialog.ui @@ -0,0 +1,228 @@ + + + QmitkStartupDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 480 + 480 + + + + MITK Workbench + + + true + + + true + + + + + + <h2>Welcome to the MITK Workbench</h2> + + + Qt::RichText + + + 4 + + + + + + + + + Show all plugins + + + true + + + + + + + Use a plugin preset: + + + + + + + + + + + false + + + + 0 + 50 + + + + + 16777215 + 120 + + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 16 + 20 + + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 438 + 210 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + + + + + + Can be changed in Preferences (Ctrl+P) again + + + Do not show this dialog again + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + defaultRadioButton + presetRadioButton + presetListWidget + skipDialogCheckBox + + + + + buttonBox + accepted() + QmitkStartupDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QmitkStartupDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkCommonExtPlugin.cpp b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkCommonExtPlugin.cpp index 99394e3e56..32159fa505 100644 --- a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkCommonExtPlugin.cpp +++ b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkCommonExtPlugin.cpp @@ -1,238 +1,238 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkCommonExtPlugin.h" #include #include "QmitkAboutHandler.h" #include "QmitkAppInstancesPreferencePage.h" -#include "QmitkExternalProgramsPreferencePage.h" +#include "QmitkStartupPreferencePage.h" #include "QmitkModuleView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include US_INITIALIZE_MODULE ctkPluginContext* QmitkCommonExtPlugin::_context = nullptr; void QmitkCommonExtPlugin::start(ctkPluginContext* context) { this->_context = context; QtWidgetsExtRegisterClasses(); BERRY_REGISTER_EXTENSION_CLASS(QmitkAboutHandler, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkAppInstancesPreferencePage, context) - BERRY_REGISTER_EXTENSION_CLASS(QmitkExternalProgramsPreferencePage, context) + BERRY_REGISTER_EXTENSION_CLASS(QmitkStartupPreferencePage, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkModuleView, context) if (qApp->metaObject()->indexOfSignal("messageReceived(QByteArray)") > -1) { connect(qApp, SIGNAL(messageReceived(QByteArray)), this, SLOT(handleIPCMessage(QByteArray))); } // This is a potentially long running operation. loadDataFromDisk(berry::Platform::GetApplicationArgs(), true); } void QmitkCommonExtPlugin::stop(ctkPluginContext* context) { Q_UNUSED(context) this->_context = nullptr; } ctkPluginContext* QmitkCommonExtPlugin::getContext() { return _context; } void QmitkCommonExtPlugin::loadDataFromDisk(const QStringList &arguments, bool globalReinit) { if (!arguments.empty()) { ctkServiceReference serviceRef = _context->getServiceReference(); if (serviceRef) { mitk::IDataStorageService* dataStorageService = _context->getService(serviceRef); mitk::DataStorage::Pointer dataStorage = dataStorageService->GetDefaultDataStorage()->GetDataStorage(); int argumentsAdded = 0; for (int i = 0; i < arguments.size(); ++i) { if (arguments[i].right(5) == ".mitk") { mitk::SceneIO::Pointer sceneIO = mitk::SceneIO::New(); bool clearDataStorageFirst(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(2); dataStorage = sceneIO->LoadScene( arguments[i].toLocal8Bit().constData(), dataStorage, clearDataStorageFirst ); mitk::ProgressBar::GetInstance()->Progress(2); argumentsAdded++; } else if (arguments[i].right(15) == ".mitksceneindex") { mitk::SceneIO::Pointer sceneIO = mitk::SceneIO::New(); bool clearDataStorageFirst(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(2); dataStorage = sceneIO->LoadSceneUnzipped(arguments[i].toLocal8Bit().constData(), dataStorage, clearDataStorageFirst); mitk::ProgressBar::GetInstance()->Progress(2); argumentsAdded++; } else { try { const std::string path(arguments[i].toStdString()); auto addedNodes = mitk::IOUtil::Load(path, *dataStorage); for (const auto& node : *addedNodes ) { node->SetIntProperty("layer", argumentsAdded); } argumentsAdded++; } catch(...) { MITK_WARN << "Failed to load command line argument: " << arguments[i].toStdString(); } } } // end for each command line argument if (argumentsAdded > 0 && globalReinit) { // calculate bounding geometry mitk::RenderingManager::GetInstance()->InitializeViews(dataStorage->ComputeBoundingGeometry3D()); } } else { MITK_ERROR << "A service reference for mitk::IDataStorageService does not exist"; } } } void QmitkCommonExtPlugin::startNewInstance(const QStringList &args, const QStringList& files) { QStringList newArgs(args); #ifdef Q_OS_UNIX newArgs << QString("--") + mitk::BaseApplication::ARG_NEWINSTANCE; #else newArgs << QString("/") + mitk::BaseApplication::ARG_NEWINSTANCE; #endif newArgs << files; QProcess::startDetached(qApp->applicationFilePath(), newArgs); } void QmitkCommonExtPlugin::handleIPCMessage(const QByteArray& msg) { QDataStream ds(msg); QString msgType; ds >> msgType; // we only handle messages containing command line arguments if (msgType != "$cmdLineArgs") return; // activate the current workbench window berry::IWorkbenchWindow::Pointer window = berry::PlatformUI::GetWorkbench()->GetActiveWorkbenchWindow(); QMainWindow* mainWindow = static_cast (window->GetShell()->GetControl()); mainWindow->setWindowState(mainWindow->windowState() & ~Qt::WindowMinimized); mainWindow->raise(); mainWindow->activateWindow(); // Get the preferences for the instantiation behavior auto* prefService = mitk::CoreServices::GetPreferencesService(); auto* prefs = prefService->GetSystemPreferences()->Node("/General"); bool newInstanceAlways = prefs->GetBool("newInstance.always", false); bool newInstanceScene = prefs->GetBool("newInstance.scene", true); QStringList args; ds >> args; QStringList fileArgs; QStringList sceneArgs; foreach (QString arg, args) { if (arg.endsWith(".mitk")) { sceneArgs << arg; } else { fileArgs << arg; } } if (newInstanceAlways) { if (newInstanceScene) { startNewInstance(args, fileArgs); foreach(QString sceneFile, sceneArgs) { startNewInstance(args, QStringList(sceneFile)); } } else { fileArgs.append(sceneArgs); startNewInstance(args, fileArgs); } } else { loadDataFromDisk(fileArgs, false); if (newInstanceScene) { foreach(QString sceneFile, sceneArgs) { startNewInstance(args, QStringList(sceneFile)); } } else { loadDataFromDisk(sceneArgs, false); } } } diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExtWorkbenchWindowAdvisorHack.h b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExtWorkbenchWindowAdvisorHack.h index c5c190b5fa..6b9d740c7c 100644 --- a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExtWorkbenchWindowAdvisorHack.h +++ b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExtWorkbenchWindowAdvisorHack.h @@ -1,60 +1,60 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkExtWorkbenchWindowAdvisorHack_h #define QmitkExtWorkbenchWindowAdvisorHack_h #include class ctkPluginContext; class QmitkPreferencesDialog; class QmitkExtWorkbenchWindowAdvisorHack : public QObject { Q_OBJECT public slots: void onUndo(); void onRedo(); void onImageNavigator(); void onViewNavigator(); - void onEditPreferences(); + void onEditPreferences(const QString& selectedPage = ""); void onQuit(); void onResetPerspective(); void onClosePerspective(); void onNewWindow(); void onIntro(); /** * @brief This slot is called if the user clicks the menu item "help->context help" or presses F1. * The help page is shown in a workbench editor. */ void onHelp(); void onHelpOpenHelpPerspective(); /** * @brief This slot is called if the user clicks in help menu the about button */ void onAbout(); public: QmitkExtWorkbenchWindowAdvisorHack(); ~QmitkExtWorkbenchWindowAdvisorHack() override; static QmitkExtWorkbenchWindowAdvisorHack* undohack; }; #endif diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.cpp b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.cpp deleted file mode 100644 index c9d0fb7efa..0000000000 --- a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "QmitkExternalProgramsPreferencePage.h" - -namespace -{ - mitk::IPreferences* GetPreferences() - { - auto* preferencesService = mitk::CoreServices::GetPreferencesService(); - return preferencesService->GetSystemPreferences()->Node("org.mitk.gui.qt.ext.externalprograms"); - } -} - -QmitkExternalProgramsPreferencePage::QmitkExternalProgramsPreferencePage() - : m_Ui(new Ui::QmitkExternalProgramsPreferencePage), - m_Control(nullptr), - m_GnuplotProcess(nullptr) -{ -} - -QmitkExternalProgramsPreferencePage::~QmitkExternalProgramsPreferencePage() -{ -} - -void QmitkExternalProgramsPreferencePage::CreateQtControl(QWidget* parent) -{ - m_Control = new QWidget(parent); - m_GnuplotProcess = new QProcess(m_Control); - - m_Ui->setupUi(m_Control); - - connect(m_GnuplotProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(OnGnuplotProcessError(QProcess::ProcessError))); - connect(m_GnuplotProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(OnGnuplotProcessFinished(int, QProcess::ExitStatus))); - connect(m_Ui->gnuplotButton, SIGNAL(clicked()), this, SLOT(OnGnuplotButtonClicked())); - - this->Update(); -} - -void QmitkExternalProgramsPreferencePage::OnGnuplotButtonClicked() -{ - QString filter = "gnuplot executable "; - -#if defined(WIN32) - filter += "(gnuplot.exe)"; -#else - filter += "(gnuplot)"; -#endif - - QString gnuplotPath = QFileDialog::getOpenFileName(m_Control, "Gnuplot", "", filter); - - if (!gnuplotPath.isEmpty()) - { - m_GnuplotPath = gnuplotPath; - m_GnuplotProcess->start(gnuplotPath, QStringList() << "--version", QProcess::ReadOnly); - } -} - -void QmitkExternalProgramsPreferencePage::OnGnuplotProcessError(QProcess::ProcessError) -{ - m_GnuplotPath.clear(); - m_Ui->gnuplotLineEdit->clear(); -} - -void QmitkExternalProgramsPreferencePage::OnGnuplotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - if (exitStatus == QProcess::NormalExit && exitCode == 0) - { - QString output = QTextCodec::codecForName("UTF-8")->toUnicode(m_GnuplotProcess->readAllStandardOutput()); - - if (output.startsWith("gnuplot")) - { - m_Ui->gnuplotLineEdit->setText(m_GnuplotPath); - return; - } - } - - m_GnuplotPath.clear(); - m_Ui->gnuplotLineEdit->clear(); -} - -QWidget* QmitkExternalProgramsPreferencePage::GetQtControl() const -{ - return m_Control; -} - -void QmitkExternalProgramsPreferencePage::Init(berry::IWorkbench::Pointer) -{ -} - -void QmitkExternalProgramsPreferencePage::PerformCancel() -{ -} - -bool QmitkExternalProgramsPreferencePage::PerformOk() -{ - auto* prefs = GetPreferences(); - - prefs->Put("gnuplot", m_GnuplotPath.toStdString()); - - return true; -} - -void QmitkExternalProgramsPreferencePage::Update() -{ - auto* prefs = GetPreferences(); - - m_GnuplotPath = QString::fromStdString(prefs->Get("gnuplot", "")); - - if (!m_GnuplotPath.isEmpty()) - m_GnuplotProcess->start(m_GnuplotPath, QStringList() << "--version", QProcess::ReadOnly); -} diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.ui b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.ui deleted file mode 100644 index 69f491a9e4..0000000000 --- a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.ui +++ /dev/null @@ -1,52 +0,0 @@ - - - QmitkExternalProgramsPreferencePage - - - - 0 - 0 - 400 - 152 - - - - External Programs - - - - - - gnuplot: - - - Qt::PlainText - - - gnuplotButton - - - - - - - - - true - - - - - - - ... - - - - - - - - - - diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.cpp b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.cpp new file mode 100644 index 0000000000..145d0e5496 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.cpp @@ -0,0 +1,73 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkStartupPreferencePage.h" +#include + +#include +#include +#include + +namespace +{ + mitk::IPreferences* GetPreferences() + { + auto* preferencesService = mitk::CoreServices::GetPreferencesService(); + return preferencesService->GetSystemPreferences()->Node("org.mitk.startupdialog"); + } +} + +QmitkStartupPreferencePage::QmitkStartupPreferencePage() + : m_Ui(new Ui::QmitkStartupPreferencePage), + m_Control(nullptr) +{ +} + +QmitkStartupPreferencePage::~QmitkStartupPreferencePage() +{ + delete m_Ui; +} + +void QmitkStartupPreferencePage::CreateQtControl(QWidget* parent) +{ + m_Control = new QWidget(parent); + m_Ui->setupUi(m_Control); + + this->Update(); +} + +QWidget* QmitkStartupPreferencePage::GetQtControl() const +{ + return m_Control; +} + +void QmitkStartupPreferencePage::Init(berry::IWorkbench::Pointer) +{ +} + +void QmitkStartupPreferencePage::PerformCancel() +{ +} + +bool QmitkStartupPreferencePage::PerformOk() +{ + auto* prefs = GetPreferences(); + prefs->PutBool("skip", !m_Ui->showDialogCheckBox->isChecked()); + + return true; +} + +void QmitkStartupPreferencePage::Update() +{ + auto* prefs = GetPreferences(); + m_Ui->showDialogCheckBox->setChecked(!prefs->GetBool("skip", false)); +} diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.h b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.h similarity index 53% rename from Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.h rename to Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.h index 2457e3b42f..c736bc75f6 100644 --- a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkExternalProgramsPreferencePage.h +++ b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.h @@ -1,54 +1,44 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef QmitkExternalProgramsPreferencePage_h -#define QmitkExternalProgramsPreferencePage_h +#ifndef QmitkStartupPreferencePage_h +#define QmitkStartupPreferencePage_h #include -#include -#include namespace Ui { - class QmitkExternalProgramsPreferencePage; + class QmitkStartupPreferencePage; } -class QmitkExternalProgramsPreferencePage : public QObject, public berry::IQtPreferencePage +class QmitkStartupPreferencePage : public QObject, public berry::IQtPreferencePage { Q_OBJECT Q_INTERFACES(berry::IPreferencePage) public: - QmitkExternalProgramsPreferencePage(); - ~QmitkExternalProgramsPreferencePage() override; + QmitkStartupPreferencePage(); + ~QmitkStartupPreferencePage() override; void CreateQtControl(QWidget* parent) override; QWidget* GetQtControl() const override; void Init(berry::IWorkbench::Pointer) override; void PerformCancel() override; bool PerformOk() override; void Update() override; -private slots: - void OnGnuplotButtonClicked(); - void OnGnuplotProcessError(QProcess::ProcessError error); - void OnGnuplotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); - private: - QScopedPointer m_Ui; + Ui::QmitkStartupPreferencePage* m_Ui; QWidget* m_Control; - - QProcess* m_GnuplotProcess; - QString m_GnuplotPath; }; #endif diff --git a/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.ui b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.ui new file mode 100644 index 0000000000..08ea4a940a --- /dev/null +++ b/Plugins/org.mitk.gui.qt.ext/src/internal/QmitkStartupPreferencePage.ui @@ -0,0 +1,44 @@ + + + QmitkStartupPreferencePage + + + + 0 + 0 + 563 + 288 + + + + Application Startup + + + + + + Show preset dialog at application startup + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingView.cpp b/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingView.cpp index 51f148467d..edc9955f9a 100644 --- a/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingView.cpp +++ b/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingView.cpp @@ -1,718 +1,721 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "GenericDataFittingView.h" #include "mitkWorkbenchUtil.h" #include #include #include #include #include #include #include #include #include #include "mitkTwoStepLinearModelFactory.h" #include "mitkTwoStepLinearModelParameterizer.h" #include "mitkThreeStepLinearModelFactory.h" #include "mitkThreeStepLinearModelParameterizer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Includes for image casting between ITK and MITK #include #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include #include #include const std::string GenericDataFittingView::VIEW_ID = "org.mitk.views.fit.genericfitting"; void GenericDataFittingView::SetFocus() { m_Controls.btnModelling->setFocus(); } void GenericDataFittingView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnModelling->setEnabled(false); this->InitModelComboBox(); + m_Controls.labelMaskInfo->hide(); connect(m_Controls.btnModelling, SIGNAL(clicked()), this, SLOT(OnModellingButtonClicked())); connect(m_Controls.comboModel, SIGNAL(currentIndexChanged(int)), this, SLOT(OnModellSet(int))); connect(m_Controls.radioPixelBased, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); //Generic setting m_Controls.groupGeneric->hide(); m_Controls.labelFormulaInfo->hide(); connect(m_Controls.editFormula, SIGNAL(textChanged(const QString&)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkFormulaInfo, SIGNAL(toggled(bool)), m_Controls.labelFormulaInfo, SLOT(setVisible(bool))); connect(m_Controls.nrOfParams, SIGNAL(valueChanged(int)), this, SLOT(OnNrOfParamsChanged())); + connect(m_Controls.checkMaskInfo, SIGNAL(toggled(bool)), m_Controls.labelMaskInfo, + SLOT(setVisible(bool))); //Model fit configuration m_Controls.groupBox_FitConfiguration->hide(); m_Controls.checkBox_Constraints->setEnabled(false); m_Controls.constraintManager->setEnabled(false); m_Controls.initialValuesManager->setEnabled(false); m_Controls.initialValuesManager->setDataStorage(this->GetDataStorage()); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.initialValuesManager, SIGNAL(initialValuesChanged(void)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), m_Controls.initialValuesManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setVisible(bool))); UpdateGUIControls(); } void GenericDataFittingView::UpdateGUIControls() { m_Controls.lineFitName->setPlaceholderText(QString::fromStdString(this->GetDefaultFitName())); m_Controls.lineFitName->setEnabled(!m_FittingInProgress); m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); bool isGenericFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; m_Controls.groupGeneric->setVisible(isGenericFactory); m_Controls.groupBox_FitConfiguration->setVisible(m_selectedModelFactory); m_Controls.groupBox->setEnabled(!m_FittingInProgress); m_Controls.comboModel->setEnabled(!m_FittingInProgress); m_Controls.groupGeneric->setEnabled(!m_FittingInProgress); m_Controls.groupBox_FitConfiguration->setEnabled(!m_FittingInProgress); m_Controls.radioROIbased->setEnabled(m_selectedMask.IsNotNull()); m_Controls.btnModelling->setEnabled(m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings()); } std::string GenericDataFittingView::GetFitName() const { std::string fitName = m_Controls.lineFitName->text().toStdString(); if (fitName.empty()) { fitName = m_Controls.lineFitName->placeholderText().toStdString(); } return fitName; } std::string GenericDataFittingView::GetDefaultFitName() const { std::string defaultName = "undefined model"; if (this->m_selectedModelFactory.IsNotNull()) { defaultName = this->m_selectedModelFactory->GetClassID(); } if (this->m_Controls.radioPixelBased->isChecked()) { defaultName += "_pixel"; } else { defaultName += "_roi"; } return defaultName; } void GenericDataFittingView::OnNrOfParamsChanged() { PrepareFitConfiguration(); UpdateGUIControls(); } void GenericDataFittingView::OnModellSet(int index) { m_selectedModelFactory = nullptr; if (index > 0) { if (static_cast(index) <= m_FactoryStack.size() ) { m_selectedModelFactory = m_FactoryStack[index - 1]; } else { MITK_WARN << "Invalid model index. Index outside of the factory stack. Factory stack size: "<< m_FactoryStack.size() << "; invalid index: "<< index; } } if (m_selectedModelFactory) { this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.initialValuesManager->setInitialValues(m_selectedModelFactory->GetParameterNames(), m_selectedModelFactory->GetDefaultInitialParameterization()); m_Controls.constraintManager->setChecker(this->m_modelConstraints, this->m_selectedModelFactory->GetParameterNames()); } UpdateGUIControls(); } bool GenericDataFittingView::IsGenericParamFactorySelected() const { return dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; } void GenericDataFittingView::PrepareFitConfiguration() { if (m_selectedModelFactory) { mitk::ModelBase::ParameterNamesType paramNames = m_selectedModelFactory->GetParameterNames(); unsigned int nrOfPools = this->m_Controls.nrOfParams->value(); //init values if (this->IsGenericParamFactorySelected()) { mitk::modelFit::ModelFitInfo::Pointer fitInfo = mitk::modelFit::ModelFitInfo::New(); fitInfo->staticParamMap.Add(mitk::GenericParamModel::NAME_STATIC_PARAMETER_number, { static_cast(nrOfPools) }); auto parameterizer = m_selectedModelFactory->CreateParameterizer(fitInfo); paramNames = parameterizer->GetParameterNames(); m_Controls.initialValuesManager->setInitialValues(paramNames, parameterizer->GetDefaultInitialParameterization()); } else { m_Controls.initialValuesManager->setInitialValues(paramNames, this->m_selectedModelFactory->GetDefaultInitialParameterization()); } //constraints this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.constraintManager->setChecker(this->m_modelConstraints, paramNames); } }; void GenericDataFittingView::OnModellingButtonClicked() { //check if all static parameters set if (m_selectedModelFactory.IsNotNull() && CheckModelSettings()) { mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; bool isLinearFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isGenericFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExponentialDecayFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isTwoStepLinearFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isThreeStepLinearFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExponentialSaturationFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; if (isLinearFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isGenericFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isExponentialDecayFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isTwoStepLinearFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isThreeStepLinearFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isExponentialSaturationFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } //add other models with else if if (generator.IsNotNull() && fitSession.IsNotNull()) { m_FittingInProgress = true; UpdateGUIControls(); DoFit(fitSession, generator); } else { QMessageBox box; box.setText("Fitting error!"); box.setInformativeText("Could not establish fitting job. Error when setting ab generator, model parameterizer or session info."); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } else { QMessageBox box; box.setText("Static parameters for model are not set!"); box.setInformativeText("Some static parameters, that are needed for calculation are not set and equal to zero. Modeling not possible"); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } void GenericDataFittingView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList& selectedNodes) { m_selectedNode = nullptr; m_selectedImage = nullptr; m_selectedMaskNode = nullptr; m_selectedMask = nullptr; m_Controls.masklabel->setText("No (valid) mask selected."); m_Controls.timeserieslabel->setText("No (valid) series selected."); QList nodes = selectedNodes; mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isLegacyMask = mitk::NodePredicateAnd::New(isImage, isBinary); mitk::NodePredicateOr::Pointer maskPredicate = mitk::NodePredicateOr::New(isLegacyMask, isLabelSet); if (nodes.size() > 0 && isImage->CheckNode(nodes.front())) { this->m_selectedNode = nodes.front(); this->m_selectedImage = dynamic_cast(this->m_selectedNode->GetData()); m_Controls.timeserieslabel->setText((this->m_selectedNode->GetName()).c_str()); nodes.pop_front(); } if (nodes.size() > 0 && maskPredicate->CheckNode(nodes.front())) { this->m_selectedMaskNode = nodes.front(); this->m_selectedMask = dynamic_cast(this->m_selectedMaskNode->GetData()); if (this->m_selectedMask->GetTimeSteps() > 1) { MITK_INFO << "Selected mask has multiple timesteps. Only use first timestep to mask model fit. Mask name: " << m_selectedMaskNode->GetName(); mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedMask = maskedImageTimeSelector->GetOutput(); } m_Controls.masklabel->setText((this->m_selectedMaskNode->GetName()).c_str()); } if (m_selectedMask.IsNull()) { this->m_Controls.radioPixelBased->setChecked(true); } UpdateGUIControls(); } bool GenericDataFittingView::CheckModelSettings() const { bool ok = true; //check whether any model is set at all. Otherwise exit with false if (m_selectedModelFactory.IsNotNull()) { bool isGenericFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; if (isGenericFactory) { ok = !m_Controls.editFormula->text().isEmpty(); } } else { ok = false; } if (this->m_Controls.radioButton_StartParameters->isChecked() && !this->m_Controls.initialValuesManager->hasValidInitialValues()) { std::string warning = "Warning. Invalid start parameters. At least one parameter has an invalid image setting as source."; MITK_ERROR << warning; m_Controls.infoBox->append(QString("") + QString::fromStdString(warning) + QString("")); ok = false; }; return ok; } void GenericDataFittingView::ConfigureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) const { if (m_Controls.radioButton_StartParameters->isChecked()) { //use user defined initial parameters mitk::ValueBasedParameterizationDelegate::Pointer paramDelegate = mitk::ValueBasedParameterizationDelegate::New(); paramDelegate->SetInitialParameterization(m_Controls.initialValuesManager->getInitialValues()); parameterizer->SetInitialParameterizationDelegate(paramDelegate); } mitk::GenericParamModelParameterizer* genericParameterizer = dynamic_cast(parameterizer); if (genericParameterizer) { genericParameterizer->SetFunctionString(m_Controls.editFormula->text().toStdString()); } } template void GenericDataFittingView::GenerateModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); auto genericParameterizer = dynamic_cast(modelParameterizer.GetPointer()); if (genericParameterizer) { genericParameterizer->SetNumberOfParameters(this->m_Controls.nrOfParams->value()); } this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(this->m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); } template void GenericDataFittingView::GenerateModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); auto genericParameterizer = dynamic_cast(modelParameterizer.GetPointer()); if (genericParameterizer) { genericParameterizer->SetNumberOfParameters(this->m_Controls.nrOfParams->value()); } this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } void GenericDataFittingView::DoFit(const mitk::modelFit::ModelFitInfo* fitSession, mitk::ParameterFitImageGeneratorBase* generator) { QString message = "Fitting Data Set . . ."; m_Controls.infoBox->append(message); ///////////////////////// //create job and put it into the thread pool ParameterFitBackgroundJob* pJob = new ParameterFitBackgroundJob(generator, fitSession, this->m_selectedNode); pJob->setAutoDelete(true); connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnJobFinished())); connect(pJob, SIGNAL(ResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), this, SLOT(OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(JobProgress(double)), this, SLOT(OnJobProgress(double))); connect(pJob, SIGNAL(JobStatusChanged(QString)), this, SLOT(OnJobStatusChanged(QString))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } GenericDataFittingView::GenericDataFittingView() : m_FittingInProgress(false) { m_selectedImage = nullptr; m_selectedMask = nullptr; mitk::ModelFactoryBase::Pointer factory = mitk::LinearModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::GenericParamModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExponentialDecayModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExponentialSaturationModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoStepLinearModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ThreeStepLinearModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); this->m_IsNotABinaryImagePredicate = mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true))), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); this->m_IsBinaryImagePredicate = mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); } void GenericDataFittingView::OnJobFinished() { this->m_Controls.infoBox->append(QString("Fitting finished")); this->m_FittingInProgress = false; this->UpdateGUIControls(); }; void GenericDataFittingView::OnJobError(QString err) { MITK_ERROR << err.toStdString().c_str(); m_Controls.infoBox->append(QString("") + err + QString("")); }; void GenericDataFittingView::OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType results, const ParameterFitBackgroundJob* pJob) { //Store the resulting parameter fit image via convenience helper function in data storage //(handles the correct generation of the nodes and their properties) mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), results, pJob->GetParentNode()); }; void GenericDataFittingView::OnJobProgress(double progress) { QString report = QString("Progress. ") + QString::number(progress); this->m_Controls.infoBox->append(report); }; void GenericDataFittingView::OnJobStatusChanged(QString info) { this->m_Controls.infoBox->append(info); } void GenericDataFittingView::InitModelComboBox() const { this->m_Controls.comboModel->clear(); this->m_Controls.comboModel->addItem(tr("No model selected")); for (ModelFactoryStackType::const_iterator pos = m_FactoryStack.begin(); pos != m_FactoryStack.end(); ++pos) { this->m_Controls.comboModel->addItem(QString::fromStdString((*pos)->GetClassID())); } this->m_Controls.comboModel->setCurrentIndex(0); }; mitk::ModelFitFunctorBase::Pointer GenericDataFittingView::CreateDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer) const { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::Pointer chi2 = mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); if (m_Controls.checkBox_Constraints->isChecked()) { fitFunctor->SetConstraintChecker(m_modelConstraints); } mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); fitFunctor->SetDebugParameterMaps(m_Controls.checkDebug->isChecked()); return fitFunctor.GetPointer(); } diff --git a/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingViewControls.ui b/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingViewControls.ui index 69eafcff00..923d4e6988 100644 --- a/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingViewControls.ui +++ b/Plugins/org.mitk.gui.qt.fit.genericfitting/src/internal/GenericDataFittingViewControls.ui @@ -1,379 +1,393 @@ GeneralDataFittingViewControls 0 0 556 1124 0 0 QmitkTemplate Selected Time Series: No series selected. Selected Mask: No mask selected. + + + + Show mask info + + + + + + + <html><head/><body><p>Please only use static segmentation masks as input in order to get sensible parametric map results.</p></body></html> + + + Fitting strategy 5 5 5 5 5 Pixel based true ROI based Select fitting model... Generic model settings: 5 5 5 5 Formula: Number of parameters: -1 1 10 1 Show formula info <html><head/><body><p>You may evaluate/fit simple mathematical formulas (e.g. &quot;<span style=" font-style:italic;">3.5 + a * x * sin(x) - 1 / 2</span>&quot;).</p><p>The parser is able to recognize:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">sums, differences, products and divisions (a + b, 4 - 3, 2 * x, 9 / 3)</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">algebraic signs ( +5, -5)</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">exponentiation ( 2 ^ 4 )</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">parentheses (3 * (4 + 2))</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">following unary functions: <span style=" font-style:italic;">abs</span>, <span style=" font-style:italic;">exp</span>, <span style=" font-style:italic;">sin</span>, <span style=" font-style:italic;">cos</span>, <span style=" font-style:italic;">tan</span>, <span style=" font-style:italic;">sind</span> (sine in degrees), <span style=" font-style:italic;">cosd</span> (cosine in degrees), <span style=" font-style:italic;">tand</span> (tangent in degrees)</li><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">variables (x, a, b, c, ... , j)</li></ul><p>Remark: The variable &quot;x&quot; is reserved. It is the signal position / timepoint. Don't use it for a model parameter that should be deduced by fitting (these are represented by a..j).</p></body></html> true 0 0 Model Fit Configuration 5 5 5 5 0 0 0 0 - 518 - 105 + 528 + 155 Start parameter Enter Fit Starting Parameters manually 0 0 0 0 - 492 - 258 + 158 + 232 Constraints Enter Constraints for Fit Parameters 0 0 0 200 5 Fitting name: <html><head/><body><p>Name/prefix that should be used for the fitting results.</p><p>May be explicitly defined by the user.</p></body></html> default fit name Start Modelling Generate debug parameter images 0 0 true Qt::Vertical 20 40 QmitkSimpleBarrierManagerWidget QWidget
QmitkSimpleBarrierManagerWidget.h
QmitkInitialValuesManagerWidget QWidget
QmitkInitialValuesManagerWidget.h
diff --git a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp index fee5a07e50..968dd7a6a0 100644 --- a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp +++ b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp @@ -1,244 +1,247 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMxNMultiWidgetEditor.h" #include #include #include #include #include #include // mxn multi widget editor plugin #include "QmitkMultiWidgetDecorationManager.h" // mitk qt widgets module #include #include #include // qt #include const QString QmitkMxNMultiWidgetEditor::EDITOR_ID = "org.mitk.editors.mxnmultiwidget"; struct QmitkMxNMultiWidgetEditor::Impl final { Impl(); ~Impl() = default; QmitkInteractionSchemeToolBar* m_InteractionSchemeToolBar; QmitkMultiWidgetConfigurationToolBar* m_ConfigurationToolBar; }; QmitkMxNMultiWidgetEditor::Impl::Impl() : m_InteractionSchemeToolBar(nullptr) , m_ConfigurationToolBar(nullptr) { // nothing here } ////////////////////////////////////////////////////////////////////////// // QmitkMxNMultiWidgetEditor ////////////////////////////////////////////////////////////////////////// QmitkMxNMultiWidgetEditor::QmitkMxNMultiWidgetEditor() : QmitkAbstractMultiWidgetEditor() , m_Impl(std::make_unique()) { // nothing here } QmitkMxNMultiWidgetEditor::~QmitkMxNMultiWidgetEditor() { GetSite()->GetPage()->RemovePartListener(this); } berry::IPartListener::Events::Types QmitkMxNMultiWidgetEditor::GetPartEventTypes() const { return Events::CLOSED | Events::OPENED | Events::HIDDEN | Events::VISIBLE; } void QmitkMxNMultiWidgetEditor::PartClosed(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(false); } } } void QmitkMxNMultiWidgetEditor::PartOpened(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->EnableCrosshair(); multiWidget->ActivateMenuWidget(true); } } } void QmitkMxNMultiWidgetEditor::PartHidden(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(false); } } } void QmitkMxNMultiWidgetEditor::PartVisible(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(true); } } } void QmitkMxNMultiWidgetEditor::OnLayoutSet(int row, int column) { const auto &multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { QmitkAbstractMultiWidgetEditor::OnLayoutSet(row, column); multiWidget->EnableCrosshair(); } } void QmitkMxNMultiWidgetEditor::OnInteractionSchemeChanged(mitk::InteractionSchemeSwitcher::InteractionScheme scheme) { const auto &multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } if (mitk::InteractionSchemeSwitcher::PACSStandard == scheme) { m_Impl->m_InteractionSchemeToolBar->setVisible(true); } else { m_Impl->m_InteractionSchemeToolBar->setVisible(false); } QmitkAbstractMultiWidgetEditor::OnInteractionSchemeChanged(scheme); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidgetEditor::SetFocus() { const auto& multiWidget = GetMultiWidget(); if (nullptr != multiWidget) { multiWidget->setFocus(); } } void QmitkMxNMultiWidgetEditor::CreateQtPartControl(QWidget* parent) { QHBoxLayout *layout = new QHBoxLayout(parent); layout->setContentsMargins(0, 0, 0, 0); auto* preferences = this->GetPreferences(); auto multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { multiWidget = new QmitkMxNMultiWidget(parent); // create left toolbar: interaction scheme toolbar to switch how the render window navigation behaves in PACS mode if (nullptr == m_Impl->m_InteractionSchemeToolBar) { m_Impl->m_InteractionSchemeToolBar = new QmitkInteractionSchemeToolBar(parent); layout->addWidget(m_Impl->m_InteractionSchemeToolBar); } m_Impl->m_InteractionSchemeToolBar->SetInteractionEventHandler(multiWidget->GetInteractionEventHandler()); multiWidget->SetDataStorage(GetDataStorage()); multiWidget->InitializeMultiWidget(); SetMultiWidget(multiWidget); connect(static_cast(multiWidget), &QmitkMxNMultiWidget::LayoutChanged, this, &QmitkMxNMultiWidgetEditor::OnLayoutChanged); } layout->addWidget(multiWidget); // create right toolbar: configuration toolbar to change the render window widget layout if (nullptr == m_Impl->m_ConfigurationToolBar) { m_Impl->m_ConfigurationToolBar = new QmitkMultiWidgetConfigurationToolBar(multiWidget); + m_Impl->m_ConfigurationToolBar->SetDataStorage(GetDataStorage()); layout->addWidget(m_Impl->m_ConfigurationToolBar); } connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::LayoutSet, this, &QmitkMxNMultiWidgetEditor::OnLayoutSet); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::Synchronized, this, &QmitkMxNMultiWidgetEditor::OnSynchronize); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::InteractionSchemeChanged, this, &QmitkMxNMultiWidgetEditor::OnInteractionSchemeChanged); + connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::SetDataBasedLayout, + static_cast(GetMultiWidget()), &QmitkMxNMultiWidget::SetDataBasedLayout); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::SaveLayout, static_cast(GetMultiWidget()), &QmitkMxNMultiWidget::SaveLayout, Qt::DirectConnection); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::LoadLayout, static_cast(GetMultiWidget()), &QmitkMxNMultiWidget::LoadLayout); GetSite()->GetPage()->AddPartListener(this); OnPreferencesChanged(preferences); } void QmitkMxNMultiWidgetEditor::OnPreferencesChanged(const mitk::IPreferences* preferences) { const auto& multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } // update decoration preferences //m_Impl->m_MultiWidgetDecorationManager->DecorationPreferencesChanged(preferences); int crosshairGapSize = preferences->GetInt("crosshair gap size", 32); multiWidget->SetCrosshairGap(crosshairGapSize); // zooming and panning preferences bool constrainedZooming = preferences->GetBool("Use constrained zooming and panning", true); mitk::RenderingManager::GetInstance()->SetConstrainedPanningZooming(constrainedZooming); bool PACSInteractionScheme = preferences->GetBool("PACS like mouse interaction", false); OnInteractionSchemeChanged(PACSInteractionScheme ? mitk::InteractionSchemeSwitcher::PACSStandard : mitk::InteractionSchemeSwitcher::MITKStandard); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMxNMultiWidgetEditor::OnLayoutChanged() { FirePropertyChange(berry::IWorkbenchPartConstants::PROP_INPUT); } diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterViewControls.ui b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterViewControls.ui index b9382c9420..381ff838ce 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterViewControls.ui +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterViewControls.ui @@ -1,416 +1,416 @@ ConcentrationCurveConverterViewControls 0 0 475 866 0 0 QmitkTemplate 10 T1 weighted MRI T2 weighted MRI T1 weighted images 10 10 10 3D Image 4D Image 4D Image Selected Time Series: 3D Image Selected 3D Image: QFrame::NoFrame QFrame::Plain Selected Baseline Image: QLayout::SetDefaultConstraint 4 Absolute Signal Enhancement Relative Signal Enhancement Variable Flip Angle Qt::Vertical 20 40 Enhancement Parameters: Conversion Factor k: Variable Flip Angle Parameters: Repetition Time [ms] : Proton Density Weighted Image : Relaxivity [mM⁻¹ s⁻¹] : Flip Angle PDW Image [°] 10000.000000000000000 Flip Angle [ ° ] : Baseline Range Selection: Start Time Frame End Time Frame - - - - Convert To Concentration - - - 0 2 0 0 T2 weighted images Selected Time Series: Conversion Factor k Echo Time TE [ms] Baseline Range Selection: Start Time Frame End Time Frame + + + + Convert To Concentration + + + Qt::Vertical 20 40 QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp index 4de3092800..f44aef7012 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp @@ -1,1334 +1,1354 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "MRPerfusionView.h" #include "boost/tokenizer.hpp" #include "boost/math/constants/constants.hpp" #include #include "mitkWorkbenchUtil.h" #include "mitkAterialInputFunctionGenerator.h" #include "mitkConcentrationCurveGenerator.h" #include #include #include #include #include #include #include #include "mitkTwoCompartmentExchangeModelFactory.h" #include "mitkTwoCompartmentExchangeModelParameterizer.h" #include #include #include #include #include #include #include #include "mitkNodePredicateFunction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Includes for image casting between ITK and MITK #include #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include #include const std::string MRPerfusionView::VIEW_ID = "org.mitk.views.pharmacokinetics.mri"; inline double convertToDouble(const std::string& data) { std::istringstream stepStream(data); stepStream.imbue(std::locale("C")); double value = 0.0; if (!(stepStream >> value) || !(stepStream.eof())) { mitkThrow() << "Cannot convert string to double. String: " << data; } return value; } void MRPerfusionView::SetFocus() { m_Controls.btnModelling->setFocus(); } void MRPerfusionView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnModelling->setEnabled(false); this->InitModelComboBox(); + m_Controls.labelMaskInfo->hide(); m_Controls.timeSeriesNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.timeSeriesNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.timeSeriesNodeSelector->SetSelectionIsOptional(false); m_Controls.timeSeriesNodeSelector->SetInvalidInfo("Please select time series."); m_Controls.timeSeriesNodeSelector->SetAutoSelectNewNodes(true); m_Controls.maskNodeSelector->SetNodePredicate(this->m_IsMaskPredicate); m_Controls.maskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.maskNodeSelector->SetSelectionIsOptional(true); m_Controls.maskNodeSelector->SetEmptyInfo("Please select (optional) mask."); connect(m_Controls.btnModelling, SIGNAL(clicked()), this, SLOT(OnModellingButtonClicked())); connect(m_Controls.comboModel, SIGNAL(currentIndexChanged(int)), this, SLOT(OnModellSet(int))); connect(m_Controls.radioPixelBased, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); + connect(m_Controls.checkMaskInfo, SIGNAL(toggled(bool)), m_Controls.labelMaskInfo, + SLOT(setVisible(bool))); + connect(m_Controls.timeSeriesNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::OnNodeSelectionChanged); connect(m_Controls.maskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::OnNodeSelectionChanged); connect(m_Controls.AIFMaskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::UpdateGUIControls); connect(m_Controls.AIFImageNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::UpdateGUIControls); //AIF setting m_Controls.groupAIF->hide(); m_Controls.btnAIFFile->setEnabled(false); - m_Controls.btnAIFFile->setEnabled(false); + m_Controls.btnAIFFile->setVisible(false); + m_Controls.aifFilePath->setEnabled(false); + m_Controls.aifFilePath->setVisible(false); m_Controls.radioAIFImage->setChecked(true); m_Controls.AIFMaskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.AIFMaskNodeSelector->SetNodePredicate(m_IsMaskPredicate); m_Controls.AIFMaskNodeSelector->setVisible(true); m_Controls.AIFMaskNodeSelector->setEnabled(true); m_Controls.AIFMaskNodeSelector->SetAutoSelectNewNodes(true); m_Controls.AIFImageNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.AIFImageNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.AIFImageNodeSelector->setEnabled(false); m_Controls.AIFImageNodeSelector->setVisible(false); m_Controls.checkDedicatedAIFImage->setEnabled(true); m_Controls.HCLSpinBox->setValue(mitk::AterialInputFunctionGenerator::DEFAULT_HEMATOCRIT_LEVEL); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFMaskNodeSelector, SLOT(setVisible(bool))); + connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFMaskNodeSelector, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.labelAIFMask, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); - connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFMaskNodeSelector, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setEnabled(bool))); - connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); connect(m_Controls.checkDedicatedAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFImageNodeSelector, SLOT(setEnabled(bool))); connect(m_Controls.checkDedicatedAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFImageNodeSelector, SLOT(setVisible(bool))); - connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.btnAIFFile, SLOT(setEnabled(bool))); + connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.btnAIFFile, SLOT(setVisible(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.aifFilePath, SLOT(setEnabled(bool))); + connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.aifFilePath, SLOT(setVisible(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.btnAIFFile, SIGNAL(clicked()), this, SLOT(LoadAIFfromFile())); + + //Brix setting m_Controls.groupDescBrix->hide(); connect(m_Controls.injectiontime, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); //Model fit configuration m_Controls.groupBox_FitConfiguration->hide(); m_Controls.checkBox_Constraints->setEnabled(false); m_Controls.constraintManager->setEnabled(false); m_Controls.initialValuesManager->setEnabled(false); m_Controls.initialValuesManager->setDataStorage(this->GetDataStorage()); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.initialValuesManager, SIGNAL(initialValuesChanged(void)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), m_Controls.initialValuesManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setVisible(bool))); //Concentration m_Controls.groupConcentration->hide(); m_Controls.groupBoxEnhancement->hide(); m_Controls.radioButtonNoConversion->setChecked(true); m_Controls.groupBox_T1MapviaVFA->hide(); m_Controls.spinBox_baselineStartTimeStep->setValue(0); m_Controls.spinBox_baselineEndTimeStep->setValue(0); m_Controls.spinBox_baselineEndTimeStep->setMinimum(0); m_Controls.spinBox_baselineStartTimeStep->setMinimum(0); m_Controls.groupBox_baselineRangeSelection->hide(); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), m_Controls.groupBoxEnhancement, SLOT(setVisible(bool))); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), m_Controls.groupBoxEnhancement, SLOT(setVisible(bool))); connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.factorSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.spinBox_baselineStartTimeStep, SIGNAL(valueChanged(int)), this, SLOT(UpdateGUIControls())); connect(m_Controls.spinBox_baselineEndTimeStep, SIGNAL(valueChanged(int)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.groupBox_T1MapviaVFA, SLOT(setVisible(bool))); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.FlipangleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.RelaxivitySpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.TRSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); m_Controls.PDWImageNodeSelector->SetNodePredicate(m_isValidPDWImagePredicate); m_Controls.PDWImageNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.PDWImageNodeSelector->SetInvalidInfo("Please select PDW Image."); m_Controls.PDWImageNodeSelector->setEnabled(false); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.PDWImageNodeSelector, SLOT(setEnabled(bool))); UpdateGUIControls(); } void MRPerfusionView::UpdateGUIControls() { m_Controls.lineFitName->setPlaceholderText(QString::fromStdString(this->GetDefaultFitName())); m_Controls.lineFitName->setEnabled(!m_FittingInProgress); m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr || dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; + m_Controls.groupAIF->setVisible(isToftsFactory || is2CXMFactory); m_Controls.groupDescBrix->setVisible(isDescBrixFactory); + if (isDescBrixFactory) + { + m_Controls.toolboxConfiguration->setItemEnabled(2, false); + } + else + { + m_Controls.toolboxConfiguration->setItemEnabled(2, true); + } m_Controls.groupConcentration->setVisible(isToftsFactory || is2CXMFactory ); + m_Controls.AIFImageNodeSelector->setVisible(!m_Controls.radioAIFFile->isChecked()); + m_Controls.AIFImageNodeSelector->setVisible(m_Controls.radioAIFImage->isChecked() && m_Controls.checkDedicatedAIFImage->isChecked()); m_Controls.groupBox_FitConfiguration->setVisible(m_selectedModelFactory); m_Controls.groupBox->setEnabled(!m_FittingInProgress); m_Controls.comboModel->setEnabled(!m_FittingInProgress); m_Controls.groupAIF->setEnabled(!m_FittingInProgress); m_Controls.groupDescBrix->setEnabled(!m_FittingInProgress); m_Controls.groupConcentration->setEnabled(!m_FittingInProgress); m_Controls.groupBox_FitConfiguration->setEnabled(!m_FittingInProgress); + m_Controls.radioROIbased->setEnabled(m_selectedMask.IsNotNull()); m_Controls.btnModelling->setEnabled(m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings()); m_Controls.spinBox_baselineStartTimeStep->setEnabled( m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked()); m_Controls.spinBox_baselineEndTimeStep->setEnabled(m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked()); } void MRPerfusionView::OnModellSet(int index) { m_selectedModelFactory = nullptr; if (index > 0) { if (static_cast(index) <= m_FactoryStack.size() ) { m_selectedModelFactory = m_FactoryStack[index - 1]; } else { MITK_WARN << "Invalid model index. Index outside of the factory stack. Factory stack size: "<< m_FactoryStack.size() << "; invalid index: "<< index; } } if (m_selectedModelFactory) { this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); m_Controls.initialValuesManager->setInitialValues(m_selectedModelFactory->GetParameterNames(), m_selectedModelFactory->GetDefaultInitialParameterization()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.constraintManager->setChecker(this->m_modelConstraints, this->m_selectedModelFactory->GetParameterNames()); } UpdateGUIControls(); } std::string MRPerfusionView::GetFitName() const { std::string fitName = m_Controls.lineFitName->text().toStdString(); if (fitName.empty()) { fitName = m_Controls.lineFitName->placeholderText().toStdString(); } return fitName; } std::string MRPerfusionView::GetDefaultFitName() const { std::string defaultName = "undefined model"; if (this->m_selectedModelFactory.IsNotNull()) { defaultName = this->m_selectedModelFactory->GetClassID(); } if (this->m_Controls.radioPixelBased->isChecked()) { defaultName += "_pixel"; } else { defaultName += "_roi"; } return defaultName; } void MRPerfusionView::OnModellingButtonClicked() { //check if all static parameters set if (m_selectedModelFactory.IsNotNull() && CheckModelSettings()) { m_HasGeneratedNewInput = false; m_HasGeneratedNewInputAIF = false; mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExtToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isStanToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; if (isDescBrixFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateDescriptiveBrixModel_PixelBased(fitSession, generator); } else { GenerateDescriptiveBrixModel_ROIBased(fitSession, generator); } } else if (isStanToftsFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } else if (isExtToftsFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } else if (is2CXMFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } //add other models with else if if (generator.IsNotNull() && fitSession.IsNotNull()) { m_FittingInProgress = true; UpdateGUIControls(); DoFit(fitSession, generator); } else { QMessageBox box; box.setText("Fitting error!"); box.setInformativeText("Could not establish fitting job. Error when setting ab generator, model parameterizer or session info."); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } else { QMessageBox box; box.setText("Static parameters for model are not set!"); box.setInformativeText("Some static parameters, that are needed for calculation are not set and equal to zero. Modeling not possible"); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } void MRPerfusionView::OnNodeSelectionChanged(QList/*nodes*/) { m_selectedMaskNode = nullptr; m_selectedMask = nullptr; if (m_Controls.timeSeriesNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedNode = m_Controls.timeSeriesNodeSelector->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); if (m_selectedImage) { this->m_Controls.initialValuesManager->setReferenceImageGeometry(m_selectedImage->GetGeometry()); } else { this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } if (m_Controls.maskNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedMaskNode = m_Controls.maskNodeSelector->GetSelectedNode(); this->m_selectedMask = dynamic_cast(m_selectedMaskNode->GetData()); if (this->m_selectedMask.IsNotNull() && this->m_selectedMask->GetTimeSteps() > 1) { MITK_INFO << "Selected mask has multiple timesteps. Only use first timestep to mask model fit. Mask name: " << m_Controls.maskNodeSelector->GetSelectedNode()->GetName(); mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedMask = maskedImageTimeSelector->GetOutput(); } } if (m_selectedMask.IsNull()) { this->m_Controls.radioPixelBased->setChecked(true); } if (this->m_selectedImage.IsNotNull()) { m_Controls.spinBox_baselineStartTimeStep->setMaximum((this->m_selectedImage->GetDimension(3))-1); m_Controls.spinBox_baselineEndTimeStep->setMaximum((this->m_selectedImage->GetDimension(3)) - 1); } UpdateGUIControls(); } bool MRPerfusionView::CheckModelSettings() const { bool ok = true; //check whether any model is set at all. Otherwise exit with false if (m_selectedModelFactory.IsNotNull()) { bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr|| dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; if (isDescBrixFactory) { //if all static parameters for this model are set, exit with true, Otherwise exit with false ok = m_Controls.injectiontime->value() > 0; } else if (isToftsFactory || is2CXMFactory) { if (this->m_Controls.radioAIFImage->isChecked()) { ok = ok && m_Controls.AIFMaskNodeSelector->GetSelectedNode().IsNotNull(); if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { ok = ok && m_Controls.AIFImageNodeSelector->GetSelectedNode().IsNotNull(); } } else if (this->m_Controls.radioAIFFile->isChecked()) { ok = ok && (this->AIFinputGrid.size() != 0) && (this->AIFinputFunction.size() != 0); } else { ok = false; } if (this->m_Controls.radioButton_absoluteEnhancement->isChecked() || this->m_Controls.radioButton_relativeEnchancement->isChecked() ) { ok = ok && (m_Controls.factorSpinBox->value() > 0); ok = ok && CheckBaselineSelectionSettings(); } else if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked() ) { ok = ok && (m_Controls.FlipangleSpinBox->value() > 0); ok = ok && (m_Controls.TRSpinBox->value() > 0); ok = ok && (m_Controls.RelaxivitySpinBox->value() > 0); ok = ok && (m_Controls.PDWImageNodeSelector->GetSelectedNode().IsNotNull()); ok = ok && CheckBaselineSelectionSettings(); } else if (this->m_Controls.radioButtonNoConversion->isChecked()) { ok = ok && true; } else { ok = false; } } //add other models as else if and check whether all needed static parameters are set else { ok = false; } if (this->m_Controls.radioButton_StartParameters->isChecked() && !this->m_Controls.initialValuesManager->hasValidInitialValues()) { std::string warning = "Warning. Invalid start parameters. At least one parameter as an invalid image setting as source."; MITK_ERROR << warning; m_Controls.infoBox->append(QString("") + QString::fromStdString(warning) + QString("")); ok = false; }; } else { ok = false; } return ok; } bool MRPerfusionView::CheckBaselineSelectionSettings() const { return m_Controls.spinBox_baselineStartTimeStep->value() <= m_Controls.spinBox_baselineEndTimeStep->value(); } void MRPerfusionView::ConfigureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) const { if (m_Controls.radioButton_StartParameters->isChecked()) { //use user defined initial parameters mitk::InitialParameterizationDelegateBase::Pointer paramDelegate = m_Controls.initialValuesManager->getInitialParametrizationDelegate(); parameterizer->SetInitialParameterizationDelegate(paramDelegate); } } void MRPerfusionView::GenerateDescriptiveBrixModel_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); mitk::DescriptivePharmacokineticBrixModelParameterizer::Pointer modelParameterizer = mitk::DescriptivePharmacokineticBrixModelParameterizer::New(); //Model configuration (static parameters) can be done now modelParameterizer->SetTau(m_Controls.injectiontime->value()); mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(this->m_selectedImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::DescriptivePharmacokineticBrixModelParameterizer::BaseImageType::Pointer baseImage; mitk::CastToItkImage(imageTimeSelector->GetOutput(), baseImage); modelParameterizer->SetBaseImage(baseImage); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); } void MRPerfusionView::GenerateDescriptiveBrixModel_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); mitk::DescriptivePharmacokineticBrixModelValueBasedParameterizer::Pointer modelParameterizer = mitk::DescriptivePharmacokineticBrixModelValueBasedParameterizer::New(); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Model configuration (static parameters) can be done now modelParameterizer->SetTau(m_Controls.injectiontime->value()); modelParameterizer->SetBaseValue(roiSignal[0]); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } template void MRPerfusionView::GenerateLinearModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = this->m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); } template void MRPerfusionView::GenerateLinearModelFit_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Model configuration (static parameters) can be done now this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } template void MRPerfusionView::GenerateAIFbasedModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); PrepareConcentrationImage(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::AterialInputFunctionType aifTimeGrid; GetAIF(aif, aifTimeGrid); modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = this->m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(this->m_inputImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, this->m_inputImage, mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } template void MRPerfusionView::GenerateAIFbasedModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); PrepareConcentrationImage(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::AterialInputFunctionType aifTimeGrid; GetAIF(aif, aifTimeGrid); modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(this->m_inputImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(this->m_inputImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, this->m_inputImage, mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); infoSignal.clear(); for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } void MRPerfusionView::DoFit(const mitk::modelFit::ModelFitInfo* fitSession, mitk::ParameterFitImageGeneratorBase* generator) { this->m_Controls.infoBox->append(QString("" + QString("Fitting Data Set . . .") + QString (""))); ///////////////////////// //create job and put it into the thread pool mitk::modelFit::ModelFitResultNodeVectorType additionalNodes; if (m_HasGeneratedNewInput) { additionalNodes.push_back(m_inputNode); } if (m_HasGeneratedNewInputAIF) { additionalNodes.push_back(m_inputAIFNode); } ParameterFitBackgroundJob* pJob = new ParameterFitBackgroundJob(generator, fitSession, this->m_selectedNode, additionalNodes); pJob->setAutoDelete(true); connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnJobFinished())); connect(pJob, SIGNAL(ResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), this, SLOT(OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(JobProgress(double)), this, SLOT(OnJobProgress(double))); connect(pJob, SIGNAL(JobStatusChanged(QString)), this, SLOT(OnJobStatusChanged(QString))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } MRPerfusionView::MRPerfusionView() : m_FittingInProgress(false), m_HasGeneratedNewInput(false), m_HasGeneratedNewInputAIF(false) { m_selectedImage = nullptr; m_selectedMask = nullptr; mitk::ModelFactoryBase::Pointer factory = mitk::DescriptivePharmacokineticBrixModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::StandardToftsModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExtendedToftsModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoCompartmentExchangeModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isLegacyMask = mitk::NodePredicateAnd::New(isImage, isBinary); mitk::NodePredicateDimension::Pointer is3D = mitk::NodePredicateDimension::New(3); mitk::NodePredicateOr::Pointer isMask = mitk::NodePredicateOr::New(isLegacyMask, isLabelSet); mitk::NodePredicateAnd::Pointer isNoMask = mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isMask)); mitk::NodePredicateAnd::Pointer is3DImage = mitk::NodePredicateAnd::New(isImage, is3D, isNoMask); this->m_IsMaskPredicate = mitk::NodePredicateAnd::New(isMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); this->m_IsNoMaskImagePredicate = mitk::NodePredicateAnd::New(isNoMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); auto isDynamicData = mitk::NodePredicateFunction::New([](const mitk::DataNode* node) { return (node && node->GetData() && node->GetData()->GetTimeSteps() > 1); }); auto modelFitResultRelationRule = mitk::ModelFitResultRelationRule::New(); auto isNoModelFitNodePredicate = mitk::NodePredicateNot::New(modelFitResultRelationRule->GetConnectedSourcesDetector()); this->m_isValidPDWImagePredicate = mitk::NodePredicateAnd::New(is3DImage, isNoModelFitNodePredicate); this->m_isValidTimeSeriesImagePredicate = mitk::NodePredicateAnd::New(isDynamicData, isImage, isNoMask); } void MRPerfusionView::OnJobFinished() { this->m_Controls.infoBox->append(QString("Fitting finished.")); this->m_FittingInProgress = false; this->UpdateGUIControls(); }; void MRPerfusionView::OnJobError(QString err) { MITK_ERROR << err.toStdString().c_str(); m_Controls.infoBox->append(QString("") + err + QString("")); }; void MRPerfusionView::OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType results, const ParameterFitBackgroundJob* pJob) { //Store the resulting parameter fit image via convenience helper function in data storage //(handles the correct generation of the nodes and their properties) mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), results, pJob->GetParentNode()); //this stores the concentration image and AIF concentration image, if generated for this fit in the storage. //if not generated for this fit, relevant nodes are empty. mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), pJob->GetAdditionalRelevantNodes(), pJob->GetParentNode()); }; void MRPerfusionView::OnJobProgress(double progress) { QString report = QString("Progress. ") + QString::number(progress); this->m_Controls.infoBox->append(report); }; void MRPerfusionView::OnJobStatusChanged(QString info) { this->m_Controls.infoBox->append(info); } void MRPerfusionView::InitModelComboBox() const { this->m_Controls.comboModel->clear(); this->m_Controls.comboModel->addItem(tr("No model selected")); for (ModelFactoryStackType::const_iterator pos = m_FactoryStack.begin(); pos != m_FactoryStack.end(); ++pos) { this->m_Controls.comboModel->addItem(QString::fromStdString((*pos)->GetClassID())); } this->m_Controls.comboModel->setCurrentIndex(0); }; mitk::DataNode::Pointer MRPerfusionView::GenerateConcentrationNode(mitk::Image* image, const std::string& nodeName) const { if (!image) { mitkThrow() << "Cannot generate concentration node. Passed image is null. parameter name: "; } mitk::DataNode::Pointer result = mitk::DataNode::New(); result->SetData(image); result->SetName(nodeName); result->SetVisibility(true); return result; }; mitk::Image::Pointer MRPerfusionView::ConvertConcentrationImage(bool AIFMode) { //Compute Concentration image mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); if (m_Controls.checkDedicatedAIFImage->isChecked() && AIFMode) { concentrationGen->SetDynamicImage(this->m_selectedAIFImage); } else { concentrationGen->SetDynamicImage(this->m_selectedImage); } concentrationGen->SetAbsoluteSignalEnhancement(m_Controls.radioButton_absoluteEnhancement->isChecked()); concentrationGen->SetRelativeSignalEnhancement(m_Controls.radioButton_relativeEnchancement->isChecked()); concentrationGen->SetUsingT1Map(m_Controls.radioButtonUsingT1viaVFA->isChecked()); if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked()) { concentrationGen->SetRepetitionTime(m_Controls.TRSpinBox->value()); concentrationGen->SetRelaxivity(m_Controls.RelaxivitySpinBox->value()); concentrationGen->SetPDWImage(dynamic_cast(m_Controls.PDWImageNodeSelector->GetSelectedNode()->GetData())); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); //Convert Flipangle from degree to radiant double alpha = m_Controls.FlipangleSpinBox->value()/360*2* boost::math::constants::pi(); concentrationGen->SetFlipAngle(alpha); double alphaPDW = m_Controls.FlipanglePDWSpinBox->value() / 360 * 2 * boost::math::constants::pi(); concentrationGen->SetFlipAnglePDW(alphaPDW); } else { concentrationGen->SetFactor(m_Controls.factorSpinBox->value()); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); } mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); return concentrationImage; } void MRPerfusionView::GetAIF(mitk::AIFBasedModelBase::AterialInputFunctionType& aif, mitk::AIFBasedModelBase::AterialInputFunctionType& aifTimeGrid) { if (this->m_Controls.radioAIFFile->isChecked()) { aif.clear(); aifTimeGrid.clear(); aif.SetSize(AIFinputFunction.size()); aifTimeGrid.SetSize(AIFinputGrid.size()); aif.fill(0.0); aifTimeGrid.fill(0.0); itk::Array::iterator aifPos = aif.begin(); for (std::vector::const_iterator pos = AIFinputFunction.begin(); pos != AIFinputFunction.end(); ++pos, ++aifPos) { *aifPos = *pos; } itk::Array::iterator gridPos = aifTimeGrid.begin(); for (std::vector::const_iterator pos = AIFinputGrid.begin(); pos != AIFinputGrid.end(); ++pos, ++gridPos) { *gridPos = *pos; } } else if (this->m_Controls.radioAIFImage->isChecked()) { aif.clear(); aifTimeGrid.clear(); mitk::AterialInputFunctionGenerator::Pointer aifGenerator = mitk::AterialInputFunctionGenerator::New(); //Hematocrit level aifGenerator->SetHCL(this->m_Controls.HCLSpinBox->value()); //mask settings this->m_selectedAIFMaskNode = m_Controls.AIFMaskNodeSelector->GetSelectedNode(); this->m_selectedAIFMask = dynamic_cast(this->m_selectedAIFMaskNode->GetData()); if (this->m_selectedAIFMask->GetTimeSteps() > 1) { MITK_INFO << "Selected AIF mask has multiple timesteps. Only use first timestep to mask model fit. AIF Mask name: " << m_selectedAIFMaskNode->GetName() ; mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedAIFMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedAIFMask = maskedImageTimeSelector->GetOutput(); } if (this->m_selectedAIFMask.IsNotNull()) { aifGenerator->SetMask(this->m_selectedAIFMask); } //image settings if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { this->m_selectedAIFImageNode = m_Controls.AIFImageNodeSelector->GetSelectedNode(); this->m_selectedAIFImage = dynamic_cast(this->m_selectedAIFImageNode->GetData()); } else { this->m_selectedAIFImageNode = m_selectedNode; this->m_selectedAIFImage = m_selectedImage; } this->PrepareAIFConcentrationImage(); aifGenerator->SetDynamicImage(this->m_inputAIFImage); aif = aifGenerator->GetAterialInputFunction(); aifTimeGrid = aifGenerator->GetAterialInputFunctionTimeGrid(); } else { mitkThrow() << "Cannot generate AIF. View is in a invalid state. No AIF mode selected."; } } void MRPerfusionView::LoadAIFfromFile() { QFileDialog dialog; dialog.setNameFilter(tr("Images (*.csv")); QString fileName = dialog.getOpenFileName(); m_Controls.aifFilePath->setText(fileName); std::string m_aifFilePath = fileName.toStdString(); //Read Input typedef boost::tokenizer< boost::escaped_list_separator > Tokenizer; ///////////////////////////////////////////////////////////////////////////////////////////////// //AIF Data std::ifstream in1(m_aifFilePath.c_str()); if (!in1.is_open()) { this->m_Controls.infoBox->append(QString("Could not open AIF File!")); } std::vector< std::string > vec1; std::string line1; while (getline(in1, line1)) { Tokenizer tok(line1); vec1.assign(tok.begin(), tok.end()); this->AIFinputGrid.push_back(convertToDouble(vec1[0])); this->AIFinputFunction.push_back(convertToDouble(vec1[1])); } } void MRPerfusionView::PrepareConcentrationImage() { mitk::Image::Pointer concentrationImage = this->m_selectedImage; mitk::DataNode::Pointer concentrationNode = this->m_selectedNode; m_HasGeneratedNewInput = false; if (!this->m_Controls.radioButtonNoConversion->isChecked()) { concentrationImage = this->ConvertConcentrationImage(false); concentrationNode = GenerateConcentrationNode(concentrationImage, "Concentration"); m_HasGeneratedNewInput = true; } m_inputImage = concentrationImage; m_inputNode = concentrationNode; } void MRPerfusionView::PrepareAIFConcentrationImage() { mitk::Image::Pointer concentrationImage = this->m_selectedImage; mitk::DataNode::Pointer concentrationNode = this->m_selectedNode; m_HasGeneratedNewInputAIF = false; if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { concentrationImage = this->m_selectedAIFImage; concentrationNode = this->m_selectedAIFImageNode; } if (!this->m_Controls.radioButtonNoConversion->isChecked()) { if (!this->m_Controls.checkDedicatedAIFImage->isChecked()) { if (m_inputImage.IsNull()) { mitkThrow() << "Cannot get AIF concentration image. Invalid view state. Input image is not defined yet, but should be."; } //we can directly use the concentration input image/node (generated by GetConcentrationImage) also for the AIF concentrationImage = this->m_inputImage; concentrationNode = this->m_inputNode; } else { concentrationImage = this->ConvertConcentrationImage(true); concentrationNode = GenerateConcentrationNode(concentrationImage, "AIF Concentration"); m_HasGeneratedNewInputAIF = true; } } m_inputAIFImage = concentrationImage; m_inputAIFNode = concentrationNode; } mitk::ModelFitFunctorBase::Pointer MRPerfusionView::CreateDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer) const { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::Pointer chi2 = mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); if (m_Controls.checkBox_Constraints->isChecked()) { fitFunctor->SetConstraintChecker(m_modelConstraints); } mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); fitFunctor->SetDebugParameterMaps(m_Controls.checkDebug->isChecked()); return fitFunctor.GetPointer(); } diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionViewControls.ui b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionViewControls.ui index fcdc0cbdd8..e9d112821c 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionViewControls.ui +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionViewControls.ui @@ -1,662 +1,676 @@ MRPerfusionViewControls 0 0 - 489 + 526 1124 0 0 0 0 QmitkTemplate QLayout::SetDefaultConstraint 4 QLayout::SetDefaultConstraint Selected Time Series: Selected Mask: 0 40 0 40 + + + + Show mask info + + + + + + + <html><head/><body><p>A separate segmentation mask is required each for the tissue and AIF mask input. <br/>Multilabel images (e.g. one segmentation with a tissue and an AIF label) are currently not supported in this view yet.</p><p>Please only use static segmentation masks as input in order to get sensible parametric map results.</p><p><br/></p><p><br/></p></body></html> + + + 0 0 0 40 Fitting strategy 5 QLayout::SetMinAndMaxSize 5 5 5 5 0 0 Pixel based true 0 0 ROI based Select pharmacokinetic model... Arterial Input Function (AIF): Select AIF from Image: 20 AIF Mask: false Dedicated AIF Image: 0 40 false 0 40 0 Select AIF from File: false Browse 5 Hematocrit Level [ ]: 1.000000000000000 0.010000000000000 Descriptive Brix-Model Parameters: QFormLayout::AllNonFixedFieldsGrow Injection Time [min]: 0 0 Model Fit Configuration 0 0 0 0 - 459 - 112 + 496 + 49 Start parameter Enter Fit Starting Parameters 0 0 0 0 158 232 Constraints Enter Constraints for Fit Parameters 0 0 0 200 0 0 345 306 Conversion: Signal to Concentration 10 No Signal Conversion true Absolute Signal Enhancement Relative Signal Enhancement Variable Flip Angle Qt::Vertical 20 40 Enhancement Parameters: Conversion Factor k: Variable Flip Angle Parameters: Repetition Time TR [ms] : Flip Angle [ ° ] : Proton Density Weighted Image : Relaxivity [mM⁻¹ s⁻¹] : 10000.000000000000000 Flip Angle PDW Image [ ° ]: Baseline Range Selection: Start Time Frame End Time Frame 5 Fitting name: <html><head/><body><p>Name/prefix that should be used for the fitting results.</p><p>May be explicitly defined by the user.</p></body></html> default fit name Start Modelling Generate debug parameter images 0 0 true Qt::Vertical QSizePolicy::Expanding 20 40 QmitkSimpleBarrierManagerWidget QWidget
QmitkSimpleBarrierManagerWidget.h
QmitkInitialValuesManagerWidget QWidget
QmitkInitialValuesManagerWidget.h
QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicView.cpp b/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicView.cpp index 5b5eadb97b..b61cf96297 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicView.cpp +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicView.cpp @@ -1,923 +1,926 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "PETDynamicView.h" #include "mitkWorkbenchUtil.h" #include "mitkAterialInputFunctionGenerator.h" #include "mitkOneTissueCompartmentModelFactory.h" #include "mitkOneTissueCompartmentModelParameterizer.h" #include "mitkExtendedOneTissueCompartmentModelFactory.h" #include "mitkExtendedOneTissueCompartmentModelParameterizer.h" #include "mitkTwoTissueCompartmentFDGModelFactory.h" #include "mitkTwoTissueCompartmentFDGModelParameterizer.h" #include "mitkTwoTissueCompartmentModelFactory.h" #include "mitkTwoTissueCompartmentModelParameterizer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Includes for image casting between ITK and MITK #include #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include #include #include const std::string PETDynamicView::VIEW_ID = "org.mitk.views.pharmacokinetics.pet"; inline double convertToDouble(const std::string& data) { std::istringstream stepStream(data); double value = 0.0; stepStream >> value; return value; } void PETDynamicView::SetFocus() { m_Controls.btnModelling->setFocus(); } void PETDynamicView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnModelling->setEnabled(false); m_Controls.errorMessageLabel->hide(); this->InitModelComboBox(); + m_Controls.labelMaskInfo->hide(); connect(m_Controls.btnModelling, SIGNAL(clicked()), this, SLOT(OnModellingButtonClicked())); connect(m_Controls.comboModel, SIGNAL(currentIndexChanged(int)), this, SLOT(OnModellSet(int))); connect(m_Controls.radioPixelBased, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); //AIF setting m_Controls.groupAIF->hide(); m_Controls.btnAIFFile->setEnabled(false); m_Controls.btnAIFFile->setEnabled(false); m_Controls.radioAIFImage->setChecked(true); m_Controls.comboAIFMask->SetDataStorage(this->GetDataStorage()); m_Controls.comboAIFMask->SetPredicate(m_IsMaskPredicate); m_Controls.comboAIFMask->setVisible(true); m_Controls.comboAIFMask->setEnabled(true); m_Controls.comboAIFImage->SetDataStorage(this->GetDataStorage()); m_Controls.comboAIFImage->SetPredicate(m_IsNoMaskImagePredicate); m_Controls.comboAIFImage->setEnabled(false); m_Controls.checkDedicatedAIFImage->setEnabled(true); m_Controls.HCLSpinBox->setValue(0.0); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.comboAIFMask, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.labelAIFMask, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.comboAIFMask, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.comboAIFImage, SLOT(setVisible(bool))); connect(m_Controls.checkDedicatedAIFImage, SIGNAL(toggled(bool)), m_Controls.comboAIFImage, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.btnAIFFile, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.aifFilePath, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.btnAIFFile, SIGNAL(clicked()), this, SLOT(LoadAIFfromFile())); + connect(m_Controls.checkMaskInfo, SIGNAL(toggled(bool)), m_Controls.labelMaskInfo, + SLOT(setVisible(bool))); //Model fit configuration m_Controls.groupBox_FitConfiguration->hide(); m_Controls.checkBox_Constraints->setEnabled(false); m_Controls.constraintManager->setEnabled(false); m_Controls.initialValuesManager->setEnabled(false); m_Controls.initialValuesManager->setDataStorage(this->GetDataStorage()); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.initialValuesManager, SIGNAL(initialValuesChanged(void)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), m_Controls.initialValuesManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setVisible(bool))); UpdateGUIControls(); } void PETDynamicView::UpdateGUIControls() { m_Controls.lineFitName->setPlaceholderText(QString::fromStdString(this->GetDefaultFitName())); m_Controls.lineFitName->setEnabled(!m_FittingInProgress); m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); bool is1TCMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExt1TCMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isFDGCMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2TCMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; m_Controls.groupAIF->setVisible(is1TCMFactory || isExt1TCMFactory || isFDGCMFactory || is2TCMFactory); m_Controls.groupBox_FitConfiguration->setVisible(m_selectedModelFactory); m_Controls.groupBox->setEnabled(!m_FittingInProgress); m_Controls.comboModel->setEnabled(!m_FittingInProgress); m_Controls.groupAIF->setEnabled(!m_FittingInProgress); m_Controls.groupBox_FitConfiguration->setEnabled(!m_FittingInProgress); m_Controls.radioROIbased->setEnabled(m_selectedMask.IsNotNull()); m_Controls.btnModelling->setEnabled(m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings()); } //void PETDynamicView::OnModelSettingChanged() //{ // bool ok = m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings(); // m_Controls.btnModelling->setEnabled(ok); //} void PETDynamicView::OnModellSet(int index) { m_selectedModelFactory = nullptr; if (index > 0) { if (static_cast(index) <= m_FactoryStack.size() ) { m_selectedModelFactory = m_FactoryStack[index - 1]; } else { MITK_WARN << "Invalid model index. Index outside of the factory stack. Factory stack size: "<< m_FactoryStack.size() << "; invalid index: "<< index; } } if (m_selectedModelFactory) { this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); m_Controls.initialValuesManager->setInitialValues(m_selectedModelFactory->GetParameterNames(), m_selectedModelFactory->GetDefaultInitialParameterization()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.constraintManager->setChecker(this->m_modelConstraints, this->m_selectedModelFactory->GetParameterNames()); } m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); UpdateGUIControls(); } std::string PETDynamicView::GetFitName() const { std::string fitName = m_Controls.lineFitName->text().toStdString(); if (fitName.empty()) { fitName = m_Controls.lineFitName->placeholderText().toStdString(); } return fitName; } std::string PETDynamicView::GetDefaultFitName() const { std::string defaultName = "undefined model"; if (this->m_selectedModelFactory.IsNotNull()) { defaultName = this->m_selectedModelFactory->GetClassID(); } if (this->m_Controls.radioPixelBased->isChecked()) { defaultName += "_pixel"; } else { defaultName += "_roi"; } return defaultName; } void PETDynamicView::OnModellingButtonClicked() { //check if all static parameters set if (m_selectedModelFactory.IsNotNull() && CheckModelSettings()) { mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; bool isOTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isextOTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isFDGFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isTTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; if (isOTCFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isextOTCFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isFDGFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isTTCFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } //add other models with else if if (generator.IsNotNull() && fitSession.IsNotNull()) { m_FittingInProgress = true; DoFit(fitSession, generator); } else { QMessageBox box; box.setText("Fitting error!"); box.setInformativeText("Could not establish fitting job. Error when setting ab generator, model parameterizer or session info."); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } else { QMessageBox box; box.setText("Static parameters for model are not set!"); box.setInformativeText("Some static parameters, that are needed for calculation are not set and equal to zero. Modeling not possible"); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } void PETDynamicView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList& selectedNodes) { m_selectedMaskNode = nullptr; m_selectedMask = nullptr; m_Controls.errorMessageLabel->setText(""); m_Controls.masklabel->setText("No (valid) mask selected."); m_Controls.timeserieslabel->setText("No (valid) series selected."); QList nodes = selectedNodes; if (nodes.size() > 0 && this->m_IsNoMaskImagePredicate->CheckNode(nodes.front())) { this->m_selectedNode = nodes.front(); auto selectedImage = dynamic_cast(this->m_selectedNode->GetData()); m_Controls.timeserieslabel->setText((this->m_selectedNode->GetName()).c_str()); if (selectedImage != this->m_selectedImage) { if (selectedImage) { this->m_Controls.initialValuesManager->setReferenceImageGeometry(selectedImage->GetGeometry()); } else { this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } } this->m_selectedImage = selectedImage; nodes.pop_front(); } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } if (nodes.size() > 0 && this->m_IsMaskPredicate->CheckNode(nodes.front())) { this->m_selectedMaskNode = nodes.front(); this->m_selectedMask = dynamic_cast(this->m_selectedMaskNode->GetData()); if (this->m_selectedMask->GetTimeSteps() > 1) { MITK_INFO << "Selected mask has multiple timesteps. Only use first timestep to mask model fit. Mask name: " << m_selectedMaskNode->GetName(); mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedMask = maskedImageTimeSelector->GetOutput(); } m_Controls.masklabel->setText((this->m_selectedMaskNode->GetName()).c_str()); } if (m_selectedMask.IsNull()) { this->m_Controls.radioPixelBased->setChecked(true); } m_Controls.errorMessageLabel->show(); UpdateGUIControls(); } bool PETDynamicView::CheckModelSettings() const { bool ok = true; //check whether any model is set at all. Otherwise exit with false if (m_selectedModelFactory.IsNotNull()) { bool isOTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isextOTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isFDGFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isTTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; if (isOTCFactory || isextOTCFactory || isFDGFactory || isTTCFactory) { if (this->m_Controls.radioAIFImage->isChecked()) { ok = ok && m_Controls.comboAIFMask->GetSelectedNode().IsNotNull(); if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { ok = ok && m_Controls.comboAIFImage->GetSelectedNode().IsNotNull(); } } else if (this->m_Controls.radioAIFFile->isChecked()) { ok = ok && (this->AIFinputGrid.size() != 0) && (this->AIFinputFunction.size() != 0); } else { ok = false; } } //add other models as else if and check whether all needed static parameters are set else { ok = false; } if (this->m_Controls.radioButton_StartParameters->isChecked() && !this->m_Controls.initialValuesManager->hasValidInitialValues()) { std::string warning = "Warning. Invalid start parameters. At least one parameter as an invalid image setting as source."; MITK_ERROR << warning; m_Controls.infoBox->append(QString("") + QString::fromStdString(warning) + QString("")); ok = false; }; } else { ok = false; } return ok; } void PETDynamicView::ConfigureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) const { if (m_Controls.radioButton_StartParameters->isChecked()) { //use user defined initial parameters mitk::InitialParameterizationDelegateBase::Pointer paramDelegate = m_Controls.initialValuesManager->getInitialParametrizationDelegate(); parameterizer->SetInitialParameterizationDelegate(paramDelegate); } } template void PETDynamicView::GenerateModelFit_PixelBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::TimeGridType aifTimeGrid; GetAIF(aif, aifTimeGrid); //Model configuration (static parameters) can be done now modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(this->m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } template void PETDynamicView::GenerateModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); //Compute AIF mitk::AterialInputFunctionGenerator::Pointer aifGenerator = mitk::AterialInputFunctionGenerator::New(); aifGenerator->SetDynamicImage(this->m_selectedImage); aifGenerator->SetMask(this->m_selectedAIFMask); mitk::AIFBasedModelBase::AterialInputFunctionType aif = aifGenerator->GetAterialInputFunction(); mitk::AIFBasedModelBase::TimeGridType aifTimeGrid = aifGenerator->GetAterialInputFunctionTimeGrid(); //Model configuration (static parameters) can be done now modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { roiUID = m_selectedMask->GetUID(); } //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); infoSignal.clear(); for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } void PETDynamicView::DoFit(const mitk::modelFit::ModelFitInfo* fitSession, mitk::ParameterFitImageGeneratorBase* generator) { std::stringstream message; message << "" << "Fitting Data Set . . ." << ""; m_Controls.errorMessageLabel->setText(message.str().c_str()); m_Controls.errorMessageLabel->show(); ///////////////////////// //create job and put it into the thread pool ParameterFitBackgroundJob* pJob = new ParameterFitBackgroundJob(generator, fitSession, this->m_selectedNode); pJob->setAutoDelete(true); connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnJobFinished())); connect(pJob, SIGNAL(ResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), this, SLOT(OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(JobProgress(double)), this, SLOT(OnJobProgress(double))); connect(pJob, SIGNAL(JobStatusChanged(QString)), this, SLOT(OnJobStatusChanged(QString))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } PETDynamicView::PETDynamicView() : m_FittingInProgress(false) { m_selectedImage = nullptr; m_selectedMask = nullptr; mitk::ModelFactoryBase::Pointer factory = mitk::OneTissueCompartmentModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExtendedOneTissueCompartmentModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoTissueCompartmentModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoTissueCompartmentFDGModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isLegacyMask = mitk::NodePredicateAnd::New(isImage, isBinary); mitk::NodePredicateOr::Pointer isMask = mitk::NodePredicateOr::New(isLegacyMask, isLabelSet); mitk::NodePredicateAnd::Pointer isNoMask = mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isMask)); this->m_IsMaskPredicate = mitk::NodePredicateAnd::New(isMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); this->m_IsNoMaskImagePredicate = mitk::NodePredicateAnd::New(isNoMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); } void PETDynamicView::OnJobFinished() { this->m_Controls.infoBox->append(QString("Fitting finished")); this->m_FittingInProgress = false; }; void PETDynamicView::OnJobError(QString err) { MITK_ERROR << err.toStdString().c_str(); m_Controls.infoBox->append(QString("") + err + QString("")); }; void PETDynamicView::OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType results, const ParameterFitBackgroundJob* pJob) { //Store the resulting parameter fit image via convenience helper function in data storage //(handles the correct generation of the nodes and their properties) mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), results, pJob->GetParentNode()); m_Controls.errorMessageLabel->setText(""); m_Controls.errorMessageLabel->hide(); }; void PETDynamicView::OnJobProgress(double progress) { QString report = QString("Progress. ") + QString::number(progress); this->m_Controls.infoBox->append(report); }; void PETDynamicView::OnJobStatusChanged(QString info) { this->m_Controls.infoBox->append(info); } void PETDynamicView::InitModelComboBox() const { this->m_Controls.comboModel->clear(); this->m_Controls.comboModel->addItem(tr("No model selected")); for (ModelFactoryStackType::const_iterator pos = m_FactoryStack.begin(); pos != m_FactoryStack.end(); ++pos) { this->m_Controls.comboModel->addItem(QString::fromStdString((*pos)->GetClassID())); } this->m_Controls.comboModel->setCurrentIndex(0); }; mitk::ModelFitFunctorBase::Pointer PETDynamicView::CreateDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer) const { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::SumOfSquaredDifferencesFitCostFunction::Pointer evaluation = mitk::SumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("sum_diff^2", evaluation); mitk::ChiSquareFitCostFunction::Pointer chi2 = mitk::ChiSquareFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); mitk::ReducedChiSquareFitCostFunction::Pointer redchi2 = mitk::ReducedChiSquareFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("redChi^2", redchi2); if (m_Controls.checkBox_Constraints->isChecked()) { fitFunctor->SetConstraintChecker(m_modelConstraints); } mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); return fitFunctor.GetPointer(); } void PETDynamicView::GetAIF(mitk::AIFBasedModelBase::AterialInputFunctionType& aif, mitk::AIFBasedModelBase::AterialInputFunctionType& aifTimeGrid) { if (this->m_Controls.radioAIFFile->isChecked()) { aif.clear(); aifTimeGrid.clear(); aif.SetSize(AIFinputFunction.size()); aifTimeGrid.SetSize(AIFinputGrid.size()); aif.fill(0.0); aifTimeGrid.fill(0.0); itk::Array::iterator aifPos = aif.begin(); for (std::vector::const_iterator pos = AIFinputFunction.begin(); pos != AIFinputFunction.end(); ++pos, ++aifPos) { *aifPos = *pos; } itk::Array::iterator gridPos = aifTimeGrid.begin(); for (std::vector::const_iterator pos = AIFinputGrid.begin(); pos != AIFinputGrid.end(); ++pos, ++gridPos) { *gridPos = *pos; } } else if (this->m_Controls.radioAIFImage->isChecked()) { aif.clear(); aifTimeGrid.clear(); mitk::AterialInputFunctionGenerator::Pointer aifGenerator = mitk::AterialInputFunctionGenerator::New(); //Hematocrit level aifGenerator->SetHCL(this->m_Controls.HCLSpinBox->value()); //mask settings this->m_selectedAIFMaskNode = m_Controls.comboAIFMask->GetSelectedNode(); this->m_selectedAIFMask = dynamic_cast(this->m_selectedAIFMaskNode->GetData()); if (this->m_selectedAIFMask->GetTimeSteps() > 1) { MITK_INFO << "Selected AIF mask has multiple timesteps. Only use first timestep to mask model fit. AIF Mask name: " << m_selectedAIFMaskNode->GetName() ; mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedAIFMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedAIFMask = maskedImageTimeSelector->GetOutput(); } if (this->m_selectedAIFMask.IsNotNull()) { aifGenerator->SetMask(this->m_selectedAIFMask); } //image settings if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { this->m_selectedAIFImageNode = m_Controls.comboAIFImage->GetSelectedNode(); this->m_selectedAIFImage = dynamic_cast(this->m_selectedAIFImageNode->GetData()); } else { this->m_selectedAIFImageNode = m_selectedNode; this->m_selectedAIFImage = m_selectedImage; } aifGenerator->SetDynamicImage(this->m_selectedAIFImage); aif = aifGenerator->GetAterialInputFunction(); aifTimeGrid = aifGenerator->GetAterialInputFunctionTimeGrid(); } else { mitkThrow() << "Cannot generate AIF. View is in a invalid state. No AIF mode selected."; } } void PETDynamicView::LoadAIFfromFile() { QFileDialog dialog; dialog.setNameFilter(tr("Images (*.csv")); QString fileName = dialog.getOpenFileName(); m_Controls.aifFilePath->setText(fileName); std::string m_aifFilePath = fileName.toStdString(); //Read Input typedef boost::tokenizer< boost::escaped_list_separator > Tokenizer; ///////////////////////////////////////////////////////////////////////////////////////////////// //AIF Data std::ifstream in1(m_aifFilePath.c_str()); if (!in1.is_open()) { m_Controls.errorMessageLabel->setText("Could not open AIF File!"); } std::vector< std::string > vec1; std::string line1; while (getline(in1, line1)) { Tokenizer tok(line1); vec1.assign(tok.begin(), tok.end()); // if (vec1.size() < 3) continue; this->AIFinputGrid.push_back(convertToDouble(vec1[0])); this->AIFinputFunction.push_back(convertToDouble(vec1[1])); } } diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicViewControls.ui b/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicViewControls.ui index ab1f608646..c4a7591b07 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicViewControls.ui +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicViewControls.ui @@ -1,325 +1,348 @@ PETDynamicViewControls 0 0 745 898 0 0 QmitkTemplate Selected Time Series: No series selected. Selected Mask: No mask selected. + + + + Show mask info + + + + + + + <html><head/><body><p>A separate segmentation mask is required each for the tissue and AIF mask input. <br/>Multilabel images (e.g. one segmentation with a tissue and an AIF label) are currently not supported in this view yet.</p><p>Please only use static segmentation masks as input in order to get sensible parametric map results.</p></body></html> + + + Fitting strategy 5 - + + 5 + + + 5 + + + 5 + + 5 Pixel based true ROI based Message - Select pharmacokinetic model... AIF Mask: Select AIF from Image: AIF Mask: Dedicated AIF Image: Select AIF from File: Browse Whole Blood to Plasma Correction: Model Fit Configuration 1 0 0 - 701 - 129 + 180 + 32 Start Parameters Enter parameter starting values manually: 0 0 - 701 - 129 + 715 + 176 Constraints Enter Constraints for Fit Parameters 0 0 Fitting name: Start Modelling true QmitkSimpleBarrierManagerWidget QWidget
QmitkSimpleBarrierManagerWidget.h
QmitkInitialValuesManagerWidget QWidget
QmitkInitialValuesManagerWidget.h
QmitkDataStorageComboBox QWidget
QmitkDataStorageComboBox.h