diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index c282dc8723..27b2b6b969 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,617 +1,618 @@ /*============================================================================ 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 mitkLabelSetImage_h #define mitkLabelSetImage_h #include #include #include namespace mitk { //##Documentation //## @brief LabelSetImage class for handling labels and layers in a segmentation session. //## //## Handles operations for adding, removing, erasing and editing labels and layers. //## @ingroup Data class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: mitkClassMacro(LabelSetImage, Image); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // FUTURE MultiLabelSegmentation: // Section that already contains declarations used in the new class. // So this part of the interface will stay after refactoring towards // the new MultiLabelSegmentation class (see T28524). This section was introduced // because some of the planed features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// using SpatialGroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; + const static LabelValueType UnlabeledLabelValue = 0; using ConstLabelVectorType = std::vector; using LabelVectorType = std::vector; //future declaration (T28524) currently conflicted with old declaration ///** // * \brief Returns true if the value exists in the MultiLabelSegmentation instance*/ //bool ExistLabel(LabelValueType value) const; ///** // * @brief Checks if a label belongs in a certain spatial group // * @param value the label value // * @param groupIndex Indexp of the spacial group which should be checked for the label // * @return true if the label exists otherwise false // */ //bool ExistLabel(LabelValueType value, SpatialGroupIndexType groupIndex) const; /** * \brief Returns true if the spatial group exists in the MultiLabelSegmentation instance.*/ bool ExistGroup(SpatialGroupIndexType index) const; bool IsLabeInGroup(LabelValueType value) const; bool IsLabeInGroup(LabelValueType value, SpatialGroupIndexType& groupIndex) const; /** Returns the group id of the based label value. * @pre label value must exists. */ SpatialGroupIndexType GetGroupIndexOfLabel(LabelValueType value) const; /** * @brief Returns the mitk::Label with the given value. * @param value the pixel value of the label * @return the mitk::Label if available otherwise nullptr */ const mitk::Label* GetLabel(LabelValueType value) const; mitk::Label* GetLabel(LabelValueType value); /** Returns a vector with all labels currently defined in the MultiLabelSegmentation instance.*/ const ConstLabelVectorType GetLabels() const; const LabelVectorType GetLabels(); //////////////////////////////////////////////////////////////////// //Message slots that allow to react to changes in an instance using LabelValueVectorType = std::vector; using LabelEventType = Message1; using LabelsEventType = Message1; using GroupEventType = Message1; /** * \brief LabelAdded is emitted whenever a new label has been added. * * Observers should register to this event by calling this->AddLabelAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the added label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelAdded, LabelValueType); /** * \brief LabelModified is emitted whenever a label has been modified. * * A label is modified if either its pixel content was changed, its spatial group or the label instance * information. * If you just want to get notified at the end of a MultiLabelSegmentation instance manipulation in the * case that at least one label was modified (e.g. to avoid getting a signal for each label * individually), use LabelsChanged instead. * Observers should register to this event by calling this->AddLabelModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the modified label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelModified, LabelValueType); /** * \brief LabelRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddLabelRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the removed label.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelRemoved, LabelValueType); /** * \brief LabelsChanged is emitted when labels are changed (added, removed, modified). * * In difference to the other label events LabelsChanged is send only *one time* after the modification of the * MultiLableImage instance is finished. So e.g. even if 4 labels are changed by a merge operation, this event will * only be sent one time (compared to LabelRemoved or LabelModified). * Observers should register to this event by calling myMultiLabelSegmentation->AddLabelsChangedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the MultiLabelSegmentation. * Observers should unregister by calling myMultiLabelSegmentation->RemoveLabelsChangedListener(myObject, * MyObject::MyMethod). * The registered method will be called with the vector of label values of the modified labels.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelsChanged, LabelValueVectorType); /** * \brief GroupAdded is emitted whenever a new group has been added. * * Observers should register to this event by calling this->AddGroupAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new group has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupAdded, SpatialGroupIndexType); /** * \brief GroupModified is emitted whenever a group has been modified. * * A group is modified if the set of labels associated with it are changed or the group's meta data. * Observers should register to this event by calling this->AddGroupModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupModified, SpatialGroupIndexType); /** * \brief GroupRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddGroupRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the removed group.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupRemoved, SpatialGroupIndexType); protected: void OnLabelAdded(LabelValueType labelValue); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); void OnGroupAdded(SpatialGroupIndexType groupIndex); void OnGroupModified(SpatialGroupIndexType groupIndex); void OnGroupRemoved(SpatialGroupIndexType groupIndex); using LabelMapType = std::map; LabelMapType m_LabelMap; /**This type is internally used to track which label is currently * associated with which layer.*/ using GroupToLabelMapType = std::map; GroupToLabelMapType m_GroupToLabelMap; using LabelToGroupMapType = std::map; LabelToGroupMapType m_LabelToGroupMap; public: /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // END FUTURE MultiLabelSegmentation /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** * \brief */ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer = 0); /** * @brief Removes the label with the given value. * The label is removed from the labelset of the given layer and * the pixel values of the image below the label are reset. * @param pixelValue the pixel value of the label to be removed * @param layer the layer from which the label should be removed */ void RemoveLabel(PixelType pixelValue, unsigned int layer = 0); /** * @brief Removes a list of labels with th given value. * The labels are removed from the labelset of the given layer and * the pixel values of the image below the label are reset. * Calls mitk::LabelSetImage::EraseLabels(). * @param VectorOfLabelPixelValues a list of pixel values of labels to be removed * @param layer the layer from which the labels should be removed */ void RemoveLabels(const std::vector &VectorOfLabelPixelValues, unsigned int layer = 0); /** * @brief Erases the label with the given value from the labelset image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param pixelValue the pixel value of the label that will be erased from the labelset image */ void EraseLabel(PixelType pixelValue); /** * @brief Erases a list of labels with the given values from the labelset image. * @param VectorOfLabelPixelValues the list of pixel values of the labels * that will be erased from the labelset image */ void EraseLabels(std::vector &VectorOfLabelPixelValues); /** * \brief Returns true if the value exists in one of the labelsets*/ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue) const); /** * @brief Checks if a label exists in a certain layer * @param pixelValue the label value * @param layer the layer in which should be searched for the label * @return true if the label exists otherwise false */ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue, unsigned int layer) const); /** * \brief Returns true if the labelset exists*/ //[[deprecated("Will be removed with T28524")]] DEPRECATED(bool ExistLabelSet(unsigned int layer) const); /** * @brief Returns the active label of a specific layer * @param layer the layer ID for which the active label should be returned * @return the active label of the specified layer */ //[[deprecated("Will be removed with T28524")]] DEPRECATED(mitk::Label *GetActiveLabel(unsigned int layer = 0)); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::Label* GetActiveLabel(unsigned int layer = 0) const); /** * @brief Returns the mitk::Label with the given pixelValue and for the given layer * @param pixelValue the pixel value of the label * @param layer the layer in which the labels should be located * @return the mitk::Label if available otherwise nullptr */ mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer = 0) const; /** * @brief Returns the currently active mitk::LabelSet * @return the mitk::LabelSet of the active layer or nullptr if non is present */ //[[deprecated ("Will be removed with T28524")]] DEPRECATED(mitk::LabelSet *GetActiveLabelSet()); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::LabelSet* GetActiveLabelSet() const); /** * @brief Gets the mitk::LabelSet for the given layer * @param layer the layer for which the mitk::LabelSet should be retrieved * @return the respective mitk::LabelSet or nullptr if non exists for the given layer */ mitk::LabelSet *GetLabelSet(unsigned int layer = 0); const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer = 0) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; // This function will need to be ported to an external class // it requires knowledge of pixeltype and dimension and includes // too much algorithm to be sensibly part of a data class ///** // * \brief */ // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index, bool useActiveLayer = true, unsigned int layer = 0); /** * @brief Initialize a new mitk::LabelSetImage by an given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * a new layer will be created * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * \brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one. * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Add a cloned LabelSet to an existing layer * * Remark: The passed LabelSet instance will be cloned before added to ensure clear ownership * of the new LabelSet addition. * * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying * to add a labelSet to a non-existing layer. * * If there are no labelSets for layers with an id less than layerIdx default ones will be added * for them. * * \param layerIdx The index of the layer the LabelSet should be added to * \param labelSet The LabelSet that should be added */ void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet); /** * @brief Removes the active layer and the respective mitk::LabelSet and image information. * The new active layer is the one below, if exists */ void RemoveLayer(); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; void OnLabelSetModified(); /** * @brief Sets the label which is used as default exterior label when creating a new layer * @param label the label which will be used as new exterior label */ void SetExteriorLabel(mitk::Label *label); /** * @brief Gets the mitk::Label which is used as default exterior label * @return the exterior mitk::Label */ mitk::Label *GetExteriorLabel(); const mitk::Label *GetExteriorLabel() const; protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); template void ClearBufferProcessing(ImageType *input); template void EraseLabelProcessing(ImageType *input, PixelType index); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); /** helper needed for ensuring unique values in all layers until the refactoring is done. returns a sorted list of all labels.*/ LabelValueVectorType GetUsedLabelValues() const; //helper function that ensures void RegisterLabelSet(mitk::LabelSet* ls); void ReleaseLabelSet(mitk::LabelSet* ls); std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; mitk::Label::Pointer m_ExteriorLabel; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); /** temporery namespace that is used until the new class MultiLabelSegmentation is introduced. It allows to already introduce/use some upcoming definitions, while refactoring code.*/ namespace MultiLabelSegmentation { enum class MergeStyle { Replace, //The old label content of a lable value will be replaced by its new label content. //Therefore pixels that are labeled might become unlabeled again. //(This means that a lock of the value is also ignored). Merge //The union of old and new label content will be generated. }; enum class OverwriteStyle { RegardLocks, //Locked labels in the same spatial group will not be overwritten/changed. IgnoreLocks //Label locks in the same spatial group will be ignored, so these labels might be changed. }; } /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume of the specified time step. @remark in its current implementation the function only transfers contents of the active layer of the passed LabelSetImages. @remark the function assumes that it is only called with source and destination image of same geometry. @param sourceImage Pointer to the LabelSetImage which active layer should be used as source for the transfer. @param destinationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage and destinationImage must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre sourceImage must contain all indicated sourceLabels in its active layer. @pre destinationImage must contain all indicated destinationLabels in its active layer.*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks, const TimeStepType timeStep = 0); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume of the specified time step. @remark the function assumes that it is only called with source and destination image of same geometry. @param sourceImage Pointer to the image that should be used as source for the transfer. @param destinationImage Pointer to the image that should be used as destination for the transfer. @param destinationLabelSet Pointer to the label set specifying labels and lock states in the destination image. Unkown pixel values in the destinationImage will be assumed to be unlocked. @param sourceBackground Value indicating the background in the source image. @param destinationBackground Value indicating the background in the destination image. @param destinationBackgroundLocked Value indicating the lock state of the background in the destination image. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage, destinationImage and destinationLabelSet must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre destinationLabelSet must contain all indicated destinationLabels for mapping.*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground = 0, mitk::Label::PixelType destinationBackground = 0, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks, const TimeStepType timeStep = 0); } // namespace mitk #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index f8bd883899..91aa50bc95 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,739 +1,739 @@ /*============================================================================ 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 "mitkImageStatisticsContainerManager.h" #include "mitkProportionalTimeGeometry.h" #include "mitkStatisticsToImageRelationRule.h" #include "mitkStatisticsToMaskRelationRule.h" #include "QmitkStyleManager.h" class QmitkMultiLabelSegTreeItem { public: enum class ItemType { Group, Label, Instance }; QmitkMultiLabelSegTreeItem() { }; explicit QmitkMultiLabelSegTreeItem(ItemType type, QmitkMultiLabelSegTreeItem* parentItem, mitk::Label* label = nullptr, std::string className = ""): m_ItemType(type), m_Label(label), m_parentItem(parentItem), 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* 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 item 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 item has no instance item."; 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 item."; } return label->GetValue(); }; QmitkMultiLabelSegTreeItem* m_parentItem = nullptr; std::vector m_childItems; ItemType m_ItemType = ItemType::Group; mitk::Label::Pointer m_Label; std::string m_ClassName; }; QmitkMultiLabelTreeModel::QmitkMultiLabelTreeModel(QObject *parent) : QAbstractItemModel(parent) , m_Observed(false) { 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 ")+QString::number(item->GetGroupID())); case QmitkMultiLabelSegTreeItem::ItemType::Label: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel item is refering to a label that does not exist."; return QVariant(QString::fromStdString(label->GetName())); } case QmitkMultiLabelSegTreeItem::ItemType::Instance: return QVariant(QString("Instance #") + QString::number(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 { } } } else if (role == ItemModelRole::LabelDataRole) { if (TableColumns::NAME_COL == index.column()) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant::fromValue(label); } } } else if (role == ItemModelRole::LabelValueRole) { if (TableColumns::NAME_COL == index.column()) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant(label->GetValue()); } } } 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()); } auto groupID = m_Segmentation->GetGroupIndexOfLabel(label->GetValue()); m_Segmentation->GetLabelSet(groupID)->UpdateLookupTable(label->GetValue()); } 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::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 GetIndexByItem(QmitkMultiLabelSegTreeItem* start, 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::SpatialGroupIndexType 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; } 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; } 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()) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; } else { return Qt::ItemIsEnabled; } return true; } else { if (item->HandleAsInstance()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return Qt::ItemIsEnabled; } return true; } 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->RemoveObserver(); 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 labelSet = m_Segmentation->GetLabelSet(groupID); for (auto lIter = labelSet->IteratorConstBegin(); lIter != labelSet->IteratorConstEnd(); lIter++) { - if (lIter->second == m_Segmentation->GetExteriorLabel()) continue; + if (lIter->first== mitk::LabelSetImage::UnlabeledLabelValue || lIter->second == m_Segmentation->GetExteriorLabel()) continue; bool newItemCreated = false; AddLabelToGroupTree(lIter->second, 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::AddObserver() { if (this->m_Segmentation.IsNotNull()) { if (m_Observed) { MITK_DEBUG << "Invalid observer state in QmitkMultiLabelTreeModel. There is already a registered observer. Internal logic is not correct. May be an old observer was not removed."; } this->m_Segmentation->AddLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->AddLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->AddLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); this->m_Segmentation->AddGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); this->m_Segmentation->AddGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); this->m_Segmentation->AddGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); m_Observed = true; } } void QmitkMultiLabelTreeModel::RemoveObserver() { if (this->m_Segmentation.IsNotNull()) { this->m_Segmentation->RemoveLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->RemoveLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->RemoveLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); this->m_Segmentation->RemoveGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); this->m_Segmentation->RemoveGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); this->m_Segmentation->RemoveGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); } m_Observed = false; } void QmitkMultiLabelTreeModel::OnLabelAdded(LabelValueType labelValue) { SpatialGroupIndexType groupIndex = 0; if (m_Segmentation->IsLabeInGroup(labelValue, groupIndex)) { 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 (label == m_Segmentation->GetExteriorLabel()) 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); } 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 { // instance item was added to existing label item auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); this->endInsertRows(); } } else { mitkThrow() << "Group less labels are not supported in the current implementation."; } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { if (labelValue == m_Segmentation->GetExteriorLabel()->GetValue()) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) { mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved 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 = GetIndexByItem(labelItem, this); emit dataChanged(index, index); } 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 == m_Segmentation->GetExteriorLabel()->GetValue()) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved 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() > 1) { auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); } 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(SpatialGroupIndexType 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(SpatialGroupIndexType groupIndex) { //currently not needed } void QmitkMultiLabelTreeModel::OnGroupRemoved(SpatialGroupIndexType groupIndex) { if (m_ShowGroups) { this->beginRemoveRows(QModelIndex(), groupIndex, groupIndex); auto root = m_RootItem.get(); root->RemoveChild(groupIndex); this->endRemoveRows(); } }