diff --git a/Modules/Multilabel/mitkDICOMSegmentationPropertyHelper.cpp b/Modules/Multilabel/mitkDICOMSegmentationPropertyHelper.cpp index 59e6e61d4a..4bfae098b1 100644 --- a/Modules/Multilabel/mitkDICOMSegmentationPropertyHelper.cpp +++ b/Modules/Multilabel/mitkDICOMSegmentationPropertyHelper.cpp @@ -1,173 +1,171 @@ /*============================================================================ 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 "mitkDICOMSegmentationPropertyHelper.h" #include #include namespace mitk { void DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(LabelSetImage* dicomSegImage) { PropertyList::Pointer propertyList = dicomSegImage->GetPropertyList(); // Add DICOM Tag (0008, 0060) Modality "SEG" propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0008, 0x0060).c_str(), TemporoSpatialStringProperty::New("SEG")); // Add DICOM Tag (0008,103E) Series Description propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0008, 0x103E).c_str(), TemporoSpatialStringProperty::New("MITK Segmentation")); // Add DICOM Tag (0070,0084) Content Creator Name propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0070, 0x0084).c_str(), TemporoSpatialStringProperty::New("MITK")); // Add DICOM Tag (0012, 0071) Clinical Trial Series ID propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0071).c_str(), TemporoSpatialStringProperty::New("Session 1")); // Add DICOM Tag (0012,0050) Clinical Trial Time Point ID propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0050).c_str(), TemporoSpatialStringProperty::New("0")); // Add DICOM Tag (0012, 0060) Clinical Trial Coordinating Center Name propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0060).c_str(), TemporoSpatialStringProperty::New("Unknown")); // Set DICOM properties for each label // Iterate over all layers for (unsigned int layer = 0; layer < dicomSegImage->GetNumberOfLayers(); ++layer) { // Iterate over all labels const LabelSet *labelSet = dicomSegImage->GetLabelSet(layer); auto labelIter = labelSet->IteratorConstBegin(); - // Ignore background label - ++labelIter; for (; labelIter != labelSet->IteratorConstEnd(); ++labelIter) { Label::Pointer label = labelIter->second; SetDICOMSegmentProperties(label); } } } void DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(Label *label) { PropertyList::Pointer propertyList = PropertyList::New(); AnatomicalStructureColorPresets::Category category; AnatomicalStructureColorPresets::Type type; auto presets = vtkSmartPointer::New(); presets->LoadPreset(); for (const auto &preset : presets->GetCategoryPresets()) { auto presetOrganName = preset.first; if (label->GetName().compare(presetOrganName) == 0) { category = preset.second; break; } } for (const auto &preset : presets->GetTypePresets()) { auto presetOrganName = preset.first; if (label->GetName().compare(presetOrganName) == 0) { type = preset.second; break; } } //------------------------------------------------------------ // Add Segment Sequence tags (0062, 0002) // Segment Number:Identification number of the segment.The value of Segment Number(0062, 0004) shall be unique // within the Segmentation instance in which it is created label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()).c_str(), TemporoSpatialStringProperty::New(std::to_string(label->GetValue()))); // Segment Label: User-defined label identifying this segment. label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()).c_str(), TemporoSpatialStringProperty::New(label->GetName())); // Segment Algorithm Type: Type of algorithm used to generate the segment. AUTOMATIC SEMIAUTOMATIC MANUAL label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()).c_str(), TemporoSpatialStringProperty::New("SEMIAUTOMATIC")); //------------------------------------------------------------ // Add Segmented Property Category Code Sequence tags (0062, 0003): Sequence defining the general category of this // segment. // (0008,0100) Code Value if (!category.codeValue.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(category.codeValue)); // (0008,0102) Coding Scheme Designator if (!category.codeScheme.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(category.codeScheme)); // (0008,0104) Code Meaning if (!category.codeName.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(category.codeName)); //------------------------------------------------------------ // Add Segmented Property Type Code Sequence (0062, 000F): Sequence defining the specific property type of this // segment. // (0008,0100) Code Value if (!type.codeValue.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(type.codeValue)); // (0008,0102) Coding Scheme Designator if (!type.codeScheme.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(type.codeScheme)); // (0008,0104) Code Meaning if (!type.codeName.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(type.codeName)); //------------------------------------------------------------ // Add Segmented Property Type Modifier Code Sequence (0062,0011): Sequence defining the modifier of the property // type of this segment. // (0008,0100) Code Value if (!type.modifier.codeValue.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(type.modifier.codeValue)); // (0008,0102) Coding Scheme Designator if (!type.modifier.codeScheme.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(type.modifier.codeScheme)); // (0008,0104) Code Meaning if (!type.modifier.codeName.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(type.modifier.codeName)); } } diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 827557b625..5dabaf8adb 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1553 +1,1553 @@ /*============================================================================ 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 "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include #include //#include #include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); this->RegisterLabelSet(lsClone); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } 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 if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // 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 ls : m_LabelSetContainer) { this->ReleaseLabelSet(ls); } m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RegisterLabelSet(mitk::LabelSet* ls) { // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); ls->AddLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = [this]() {return this->GetUsedLabelValues(); }; } void mitk::LabelSetImage::ReleaseLabelSet(mitk::LabelSet* ls) { ls->RemoveAllObservers(); ls->AddLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = nullptr; } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->OnGroupRemoved(layerToDelete); this->Modified(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { const auto activeIndex = GetActiveLayer(); // remove all observers from active label set GetLabelSet(indexToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (activeIndex>indexToDelete) { SetActiveLayer(activeIndex - 1); } else if (activeIndex==indexToDelete) { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); if (indexToDelete == activeIndex) { //enforces the new active layer to be set and copied auto newActiveIndex = indexToDelete < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 1; this->SetActiveLayer(newActiveIndex); } this->OnGroupRemoved(indexToDelete); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UnlabeledValue }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } return this->AddLayer(newImage, labelSet); } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (labelSet.IsNotNull()) { ls = labelSet; } else { ls = mitk::LabelSet::New(); ls->SetActiveLabel(UnlabeledValue); } ls->SetLayer(newLabelSetId); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); RegisterLabelSet(ls); this->ReinitMaps(); SetActiveLayer(newLabelSetId); this->Modified(); this->OnGroupAdded(newLabelSetId); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } auto clonedLabelSet = labelSet->Clone(); this->RegisterLabelSet(clonedLabelSet); std::vector addedGroups; if (layerIdx < m_LabelSetContainer.size()) { if (m_LabelSetContainer[layerIdx].IsNotNull()) { this->ReleaseLabelSet(m_LabelSetContainer[layerIdx]); } m_LabelSetContainer[layerIdx] = clonedLabelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->SetActiveLabel(UnlabeledValue); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); this->RegisterLabelSet(defaultLabelSet); this->ReinitMaps(); m_LabelSetContainer.push_back(defaultLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } m_LabelSetContainer.push_back(clonedLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } this->ReinitMaps(); for (auto groupID : addedGroups) { this->m_GroupAddedMessage.Send(groupID); } } 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; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter 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; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { if (this->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(this, ClearBufferProcessing,4); } else { AccessByItk(this, ClearBufferProcessing); } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(sourcePixelValue); this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ sourcePixelValue, pixelValue }); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->m_LabelModifiedMessage.Send(vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(pixelValue); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->m_LabelsChangedMessage.Send(modifiedValues); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); //now remove the label entry itself this->GetLabelSet(groupID)->RemoveLabel(pixelValue); // in the interim version triggered by label set events: this->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx]); this->m_LabelsChangedMessage.Send({ VectorOfLabelPixelValues[idx] }); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); mitk::Image* groupImage = this->GetActiveLayer() != groupID ? this->GetLayerImage(groupID) : this; 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->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); Modified(); } void mitk::LabelSetImage::EraseLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } 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."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { // mask->Initialize(this) does not work here if this label set image has a single slice, // since the mask would be automatically flattened to a 2-d image, whereas we expect the // original dimension of this label set image. Hence, initialize the mask more explicitly: mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } 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 (...) { mitkThrow() << "Could not intialize by provided labeled image."; } 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()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (LabelSetImage::UnlabeledValue!=sourceValue && !this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->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->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++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(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UnlabeledValue) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { 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]; GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } 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(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; } } 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::OnLabelAdded(LabelValueType labelValue) { Label* label = nullptr; unsigned int layerID = 0; for (; layerID < this->GetNumberOfLayers(); ++layerID) { label = this->GetLabel(labelValue, layerID); if (nullptr != label) break; } if (!label) mitkThrow() << "Wrong internal state. OnLabelAdded was triggered, but label cannot be found. Invalid label: " << labelValue; AddLabelToMap(labelValue, label, layerID); this->m_LabelAddedMessage.Send(labelValue); } 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; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = m_GroupToLabelMap.find(groupID); if (groupFinding == m_GroupToLabelMap.end()) { m_GroupToLabelMap[groupID] = { labelValue }; } else { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::OnLabelModified(LabelValueType labelValue) { this->m_LabelModifiedMessage.Send(labelValue); } void mitk::LabelSetImage::OnLabelRemoved(LabelValueType labelValue) { m_LabelMap.erase(labelValue); auto finding = m_LabelToGroupMap.find(labelValue); if (finding != m_LabelToGroupMap.end()) { auto labelsInGroup = m_GroupToLabelMap[finding->second]; auto labelFinding = std::find(labelsInGroup.begin(), labelsInGroup.end(),finding->second); if (labelFinding != labelsInGroup.end()) { labelsInGroup.erase(labelFinding); } m_LabelToGroupMap.erase(labelValue); } this->m_LabelRemovedMessage.Send(labelValue); } void mitk::LabelSetImage::OnGroupAdded(GroupIndexType groupIndex) { this->m_GroupToLabelMap.insert(std::make_pair(groupIndex, LabelValueVectorType())); this->m_GroupAddedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupModified(GroupIndexType groupIndex) { this->m_GroupModifiedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupRemoved(GroupIndexType groupIndex) { this->ReinitMaps(); this->m_GroupRemovedMessage.Send(groupIndex); } // future implementation for T28524 //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_LabelSetContainer.size(); //} bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LabelSetContainer.size(); } -bool mitk::LabelSetImage::IsLabeInGroup(LabelValueType value) const +bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value) const { GroupIndexType dummy; - return this->IsLabeInGroup(value, dummy); + return this->IsLabelInGroup(value, dummy); } -bool mitk::LabelSetImage::IsLabeInGroup(LabelValueType value, GroupIndexType& groupIndex) const +bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { groupIndex = finding->second; return true; } return false; } 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 == UnlabeledValue) { 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::ConstLabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) { mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; } mitk::LabelSetImage::ConstLabelVectorType result; const auto labellist = m_GroupToLabelMap.find(index)->second; for (const auto& labelvalue : labellist) { result.emplace_back(this->GetLabel(labelvalue)); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) { if (!this->ExistGroup(index)) { mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; } mitk::LabelSetImage::LabelVectorType result; const auto labellist = m_GroupToLabelMap[index]; for (const auto& labelvalue : labellist) { result.emplace_back(this->GetLabel(labelvalue)); } return result; } void mitk::LabelSetImage::ReinitMaps() { this->m_LabelMap.clear(); this->m_LabelToGroupMap.clear(); this->m_GroupToLabelMap.clear(); for (GroupIndexType layerID = 0; layerID < this->GetNumberOfLayers(); ++layerID) { auto labelSet = this->GetLabelSet(layerID); for (auto iter = labelSet->IteratorBegin(); iter != labelSet->IteratorEnd(); ++iter) { if (iter->first != UnlabeledValue) { this->AddLabelToMap(iter->first, iter->second, layerID); } } } } 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 ---"; // 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; } } 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.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** 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 mitk::LabelSet* destinationLabelSet, 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_DestinationLabelSet(destinationLabelSet), 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_DestinationLabelSet == other.m_DestinationLabelSet; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; 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 label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); if (nullptr == label || !label->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: const mitk::LabelSet* m_DestinationLabelSet = nullptr; 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::LabelSet* destinationLabelSet, 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(destinationLabelSet, 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::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > 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 (nullptr == destinationLabelSet) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationLabelSet 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; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue!=newDestinationLabel && nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); destinationLabelSet->ModifyLabelEvent.Send(newDestinationLabel); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > 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, destinationLabelSet, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UnlabeledValue != 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, destinationLabelSet, timeStep, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > 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/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index fa6c48c2bf..a0fe5a041d 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,672 +1,672 @@ /*============================================================================ 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 planned features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// using GroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; const static LabelValueType UnlabeledValue = 0; using ConstLabelVectorType = std::vector; using LabelVectorType = std::vector; using LabelValueVectorType = std::vector; /** * @brief Removes the label with the given value. * The label is removed from the labelset and * the pixel with the value of the label are set to UnlabeledValue. * @param labelValue the pixel value of the label to be removed */ void RemoveLabel(LabelValueType labelValue); /** * @brief Removes labels from the mitk::MultiLabelSegmentation. * If a label value does not exist, it will be ignored. * @param vectorOfLabelPixelValues a list of labels to be removed */ void RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues); /** * @brief Removes a whole group including all its labels. * @remark with removing a group all groups with greater index will be reindexed to * close the gap. Hence externaly stored spatial group indices may become invalid. * @param group Group index of the spatial group that should be removed. If the spatial group does not exist, an * exception will be raised. * @pre group index must be valid. */ void RemoveGroup(GroupIndexType group); //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, GroupIndexType groupIndex) const; /** * @brief Returns true if the spatial group exists in the MultiLabelSegmentation instance. * * @param index Group index of the group that should be checked for existance. */ bool ExistGroup(GroupIndexType index) const; - bool IsLabeInGroup(LabelValueType value) const; - bool IsLabeInGroup(LabelValueType value, GroupIndexType& groupIndex) const; + bool IsLabelInGroup(LabelValueType value) const; + bool IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const; /** Returns the group id of the based label value. * @pre label value must exists. */ GroupIndexType 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 the lock state of the label (including UnlabeledLabel value). @pre Requested label does exist.*/ bool IsLabelLocked(LabelValueType value) const; /** Returns a vector with all labels currently defined in the MultiLabelSegmentation instance.*/ const ConstLabelVectorType GetLabels() const; const LabelVectorType GetLabels(); /** * @brief Returns a vector of all labels located on the specified group. * @param index the index of the group for which the vector of labels should be retrieved. * If an invalid index is passed an exception will be raised. * @return the respective vector of labels. * @pre group index must exist. */ const ConstLabelVectorType GetLabelsInGroup(GroupIndexType index) const; const LabelVectorType GetLabelsInGroup(GroupIndexType index); itkGetConstMacro(UnlabeledLabelLock, bool); itkSetMacro(UnlabeledLabelLock, bool); itkBooleanMacro(UnlabeledLabelLock); //////////////////////////////////////////////////////////////////// //Message slots that allow to react to changes in an instance 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 once (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, GroupIndexType); /** * \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, GroupIndexType); /** * \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, GroupIndexType); protected: void OnLabelAdded(LabelValueType labelValue); void AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); void OnGroupAdded(GroupIndexType groupIndex); void OnGroupModified(GroupIndexType groupIndex); void OnGroupRemoved(GroupIndexType groupIndex); /** Reeinitalizes the internal maps based on the current layer/label content * of the instance. */ void ReinitMaps(); 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; private: /** Indicates if the MultiLabelSegmentation allows to overwrite unlabeled pixels in normal pixel manipulation operations (e.g. TransferLabelConent).*/ bool m_UnlabeledLabelLock; public: /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue); /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // 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); /** * @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(const 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) 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(); 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; }; /** * @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 for a specific timestep. 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. @remark CAUTION: The function is not save if sourceImage and destinationImage are the same instance and more than one label is transferred, because the changes are made in-place for performance reasons in multiple passes. If a mapped value A equals an "old value" that occurs later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then later again, because it is also an "old" value in the mapping table. @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 TransferLabelContentAtTimeStep(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**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 for all time steps. For more details please see TransferLabelContentAtTimeStep for LabelSetImages. @sa TransferLabelContentAtTimeStep*/ 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); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. 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. @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one label, because the changes are made inplace for performance reasons but not in one pass. If a mapped value A equals a "old value" that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because it is also an "old" value in the mapping table. @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 TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**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 for all time steps. For more details please see TransferLabelContentAtTimeStep. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); } // namespace mitk #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 0524b2d066..649c2c6b57 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1065 +1,1073 @@ /*============================================================================ 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 // Qmitk #include #include #include #include // Qt #include #include #include #include #include QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { 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(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); } 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(); } } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } 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 indices 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 : qAsConst(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); } 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()); auto group = m_Segmentation->GetLabelSet(groupID); m_ModelManipulationOngoing = true; auto newLabel = group->AddLabel(templateLabel, 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(); } return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; return this->AddNewLabelInstanceInternal(currentLabel); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { - mitk::Label::Pointer newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); + auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) - { emit LabelRenameRequested(newLabel, false); - } auto group = m_Segmentation->GetLabelSet(containingGroup); + m_ModelManipulationOngoing = true; group->AddLabel(newLabel, false); 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(); - } + + 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()); 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; return AddNewLabelInternal(groupID); } void QmitkMultiLabelInspector::DeleteLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto currentLabel = this->GetFirstSelectedLabelObject(); auto question = "Do you really want to remove label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; auto answerButton = QMessageBox::question(this, QString("Remove label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal({ currentLabel->GetValue() }); } } 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; } } 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(); 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; 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; - } auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); + + if (!nextIndex.isValid()) + { + QMessageBox::information(this, "Delete group", "Cannot delete last remaining group. A segmentation must contain at least a single group."); + return; + } + 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; } } 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; } QString question = "Do you really want to delete the group of the selected label with all labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Delete group", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } auto currentLabel = GetFirstSelectedLabelObject(); const auto currentGroup = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); this->RemoveGroupInternal(currentGroup); } 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(); QString question = "Do you really want to delete the current group with all labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, QString("Delete group ") + QString::number(groupID), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } this->RemoveGroupInternal(groupID); } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); if (IndexLevelType::Group == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Add label", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); - QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Delete group", this); - QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); - menu->addAction(removeAction); + auto nextIndex = m_Model->ClosestLabelInstanceIndex(m_Controls->view->currentIndex()); + + if (nextIndex.isValid()) + { + QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Delete group", this); + QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); + menu->addAction(removeAction); + } } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock group", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock group", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View group", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "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(QIcon(":/Qmitk/RenameLabel.png"), "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 class", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "&Delete label class", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock label instances", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock label instances", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View label instances", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide label instances", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); QAction* viewOnlyAction = new QAction(QIcon(":/Qmitk/visible.png"), "View only", 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()); } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; QMenu* menu = new QMenu(this); if (this->GetMultiSelectionMode() && selectedLabelValues.size() > 1) { 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(QIcon(":/Qmitk/RemoveLabel.png"), "&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); } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Add label instance...", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "&Delete", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabel); menu->addAction(removeAction); 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) { menu->addSeparator(); QAction* viewOnlyAction = new QAction(QIcon(":/Qmitk/visible.png"), "View only", 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()); } } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); std::vector relevantLabels; if (!relevantLabelValues.empty()) { //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); auto group = m_Segmentation->GetLabelSet(groupID); 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; QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels, group](const int value) { auto opacity = static_cast(value) / 100.0f; for (auto label : relevantLabels) { label->SetOpacity(opacity); group->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } ); 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(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } 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 label instances of class \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label class", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); } } 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(); } } 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(), m_Segmentation->GetActiveLayer()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } 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); } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); } 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(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); emit LabelRenameRequested(currentLabel, true); //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); 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()); group->UpdateLookupTable(label->GetValue()); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } } } 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()) { //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); auto group = m_Segmentation->GetLabelSet(groupID); 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); group->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(false); currentLabel->SetVisible(true); group->UpdateLookupTable(labelID); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(labelID); } 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); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp index 8a54e9fb44..c2550565bc 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp @@ -1,459 +1,457 @@ /*============================================================================ 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 #include #include #include #include #include // Qmitk #include // Qt #include #include #include #include #include #include #include #include #include #include #include // itk #include #include QmitkMultiLabelManager::QmitkMultiLabelManager(QWidget *parent) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelManagerControls), m_Completer(nullptr), m_ProcessingManualSelection(false) { m_Controls->setupUi(this); m_Controls->labelSearchBox->setAlwaysShowClearIcon(true); m_Controls->labelSearchBox->setShowSearchIcon(true); QStringList completionList; completionList << ""; m_Completer = new QCompleter(completionList, this); m_Completer->setCaseSensitivity(Qt::CaseInsensitive); m_Controls->labelSearchBox->setCompleter(m_Completer); m_Controls->labelInspector->SetAllowLabelModification(true); connect(m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, this, &QmitkMultiLabelManager::UpdateControls); connect(m_Controls->labelSearchBox, SIGNAL(returnPressed()), this, SLOT(OnSearchLabel())); QStringListModel *completeModel = static_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); // See T29549 m_Controls->labelSearchBox->hide(); m_Controls->btnSavePreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls->btnLoadPreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); connect(m_Controls->btnAddLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); connect(m_Controls->btnAddInstance, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabelInstance); connect(m_Controls->btnRemoveLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::DeleteLabel); connect(m_Controls->btnAddGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewGroup); connect(m_Controls->btnRemoveGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::RemoveGroup); connect(m_Controls->btnSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnSavePreset); connect(m_Controls->btnSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnLoadPreset); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::GoToLabel, this, &QmitkMultiLabelManager::OnGoToLabel); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::LabelRenameRequested, this, &QmitkMultiLabelManager::OnLabelRenameRequested); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, this, &QmitkMultiLabelManager::CurrentSelectionChanged); auto* renameLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_R), this); connect(renameLabelShortcut, &QShortcut::activated, this, &QmitkMultiLabelManager::OnRenameLabelShortcutActivated); auto* newLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_A), this); connect(newLabelShortcut, &QShortcut::activated, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); this->UpdateControls(); } QmitkMultiLabelManager::~QmitkMultiLabelManager() { delete m_Controls; } QmitkMultiLabelManager::LabelValueVectorType QmitkMultiLabelManager::GetSelectedLabels() const { return m_Controls->labelInspector->GetSelectedLabels(); } void QmitkMultiLabelManager::OnRenameLabelShortcutActivated() { auto selectedLabels = this->GetSelectedLabels(); for (auto labelValue : selectedLabels) { auto currentLabel = this->m_Segmentation->GetLabel(labelValue); emit LabelRenameRequested(currentLabel, true); } } void QmitkMultiLabelManager::OnSelectedLabelChanged(LabelValueVectorType labels) { if (labels.empty() || labels.size() > 1) return; emit CurrentSelectionChanged(labels); } QStringList &QmitkMultiLabelManager::GetLabelStringList() { return m_LabelStringList; } void QmitkMultiLabelManager::SetDefaultLabelNaming(bool defaultLabelNaming) { this->m_Controls->labelInspector->SetDefaultLabelNaming(defaultLabelNaming); } void QmitkMultiLabelManager::setEnabled(bool enabled) { QWidget::setEnabled(enabled); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { this->m_Controls->labelInspector->SetSelectedLabels(selectedLabels); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->m_Controls->labelInspector->SetSelectedLabel(selectedLabel); UpdateControls(); } void QmitkMultiLabelManager::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != this->m_Segmentation.GetPointer()) { m_Segmentation = segmentation; this->m_Controls->labelInspector->SetMultiLabelSegmentation(segmentation); UpdateControls(); } } void QmitkMultiLabelManager::SetDataStorage(mitk::DataStorage *storage) { m_DataStorage = storage; } void QmitkMultiLabelManager::OnSearchLabel() { //std::string text = m_Controls->labelSearchBox->text().toStdString(); //int pixelValue = -1; //int row = -1; //for (int i = 0; i < m_Controls->m_LabelSetTableWidget->rowCount(); ++i) //{ // if (m_Controls->m_LabelSetTableWidget->item(i, 0)->text().toStdString().compare(text) == 0) // { // pixelValue = m_Controls->m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt(); // row = i; // break; // } //} //if (pixelValue == -1) //{ // return; //} //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //QTableWidgetItem *nameItem = m_Controls->m_LabelSetTableWidget->item(row, NAME_COL); //if (!nameItem) //{ // return; //} //m_Controls->m_LabelSetTableWidget->clearSelection(); //m_Controls->m_LabelSetTableWidget->selectRow(row); //m_Controls->m_LabelSetTableWidget->scrollToItem(nameItem); //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //this->WaitCursorOn(); //mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); //m_ToolManager->WorkingDataChanged(); //if (pos.GetVnlVector().max_value() > 0.0) //{ // emit goToLabel(pos); //} //else //{ // GetWorkingImage()->UpdateCenterOfMass(pixelValue, GetWorkingImage()->GetActiveLayer()); // mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); // emit goToLabel(pos); //} //this->WaitCursorOff(); } void QmitkMultiLabelManager::UpdateControls() { bool hasWorkingData = m_Segmentation.IsNotNull(); auto labels = this->m_Controls->labelInspector->GetSelectedLabels(); m_Controls->labelSearchBox->setEnabled(hasWorkingData); m_Controls->btnAddGroup->setEnabled(hasWorkingData); m_Controls->btnAddInstance->setEnabled(hasWorkingData && labels.size()==1); m_Controls->btnAddLabel->setEnabled(hasWorkingData); m_Controls->btnLoadPreset->setEnabled(hasWorkingData); m_Controls->btnRemoveGroup->setEnabled(hasWorkingData && !labels.empty() && m_Segmentation->GetNumberOfLayers()>1); m_Controls->btnRemoveLabel->setEnabled(hasWorkingData && !labels.empty()); m_Controls->btnSavePreset->setEnabled(hasWorkingData); if (!hasWorkingData) return; QStringListModel *completeModel = dynamic_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); } void QmitkMultiLabelManager::OnCreateCroppedMask(bool) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); mitk::Image::Pointer maskImage; auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); try { this->WaitCursorOn(); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput(this->m_Segmentation->CreateLabelMask(pixelValue)); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); maskImage = cropFilter->GetOutput(); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, this->m_SegmentationNode); } void QmitkMultiLabelManager::OnCreateMask(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::Image::Pointer maskImage; try { this->WaitCursorOn(); maskImage = m_Segmentation->CreateLabelMask(pixelValue); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, m_SegmentationNode); } void QmitkMultiLabelManager::OnCreateSmoothedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = m_SegmentationNode; surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", m_Segmentation); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", true); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnCreateDetailedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = m_SegmentationNode; surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", m_Segmentation); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", false); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } - void QmitkMultiLabelManager::OnSavePreset() { } void QmitkMultiLabelManager::OnLoadPreset() { } void QmitkMultiLabelManager::OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const { emit GoToLabel(label, position); } void QmitkMultiLabelManager::OnLabelRenameRequested(mitk::Label* label, bool rename) const { emit LabelRenameRequested(label, rename); } - void QmitkMultiLabelManager::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelManager::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkMultiLabelManager::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } void QmitkMultiLabelManager::OnThreadedCalculationDone() { mitk::StatusBar::GetInstance()->Clear(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index 972ad8379f..1110ef2042 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,971 +1,975 @@ /*============================================================================ 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 "mitkRenderingManager.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_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_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())); + 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."; + + 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(" (") + QString::number(item->m_childItems.size()) + QString(" instances)"); - } + 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(" #") + QString::number(item->GetLabelValue())); + + 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 - { - - } } } 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()); } auto groupID = m_Segmentation->GetGroupIndexOfLabel(label->GetValue()); m_Segmentation->GetLabelSet(groupID)->UpdateLookupTable(label->GetValue()); 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::UnlabeledValue) return QModelIndex(); auto relevantItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == relevantItem) 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) { resultItem = GetFirstInstanceLikeItem(searchItem->NextSibblingItem()); if (nullptr != resultItem) break; //no next closest label instance on this level -> check for closest before resultItem = GetFirstInstanceLikeItem(searchItem->PrevSibblingItem()); 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(); } 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->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->first== mitk::LabelSetImage::UnlabeledValue) 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) { GroupIndexType groupIndex = 0; - if (m_Segmentation->IsLabeInGroup(labelValue, groupIndex)) + if (m_Segmentation->IsLabelInGroup(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 (labelValue == mitk::LabelSetImage::UnlabeledValue) 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 { if (instanceItem->ParentItem()->m_childItems.size() < 3) { //second instance item was added, so label item will now able to colapse // -> 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(); } } } else { mitkThrow() << "Group less labels are not supported in the current implementation."; } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UnlabeledValue) 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 == mitk::LabelSetImage::UnlabeledValue) 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() > 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/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp index b7e607cbda..fce39a51d1 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp @@ -1,401 +1,406 @@ /*============================================================================ 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 "QmitkNewSegmentationDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { // Get standard label name and color suggestions from embedded XML preset file for anatomical structures. QmitkNewSegmentationDialog::SuggestionsType GetStandardSuggestions() { vtkNew presets; presets->LoadPreset(); auto colorPresets = presets->GetColorPresets(); QmitkNewSegmentationDialog::SuggestionsType standardSuggestions; standardSuggestions.reserve(colorPresets.size()); for (const auto& preset : colorPresets) { auto name = QString::fromStdString(preset.first); auto color = QColor::fromRgbF(preset.second.GetRed(), preset.second.GetGreen(), preset.second.GetBlue()); standardSuggestions.emplace_back(name, color); } std::sort(begin(standardSuggestions), end(standardSuggestions), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); return standardSuggestions; } // Parse label name and color suggestions from a JSON file. An array of objects is expected, each consisting // of a "name" string and an optional "color" string. If present, the "color" string must follow the conventions // of QColor::setNamedColor(), i.e., #rrggbb or any SVG color keyword name like CornflowerBlue. Everything else // in the JSON file is simply ignored. In case of any error, an empty map is returned. QmitkNewSegmentationDialog::SuggestionsType ParseSuggestions(const std::string& filename) { std::ifstream file(filename); if (!file.is_open()) { MITK_ERROR << "Could not open \"" << filename << "\"!"; return {}; } auto json = nlohmann::json::parse(file, nullptr, false); if (json.is_discarded() || !json.is_array()) { MITK_ERROR << "Could not parse \"" << filename << "\" as JSON array!"; return {}; } QmitkNewSegmentationDialog::SuggestionsType parsedSuggestions; for (const auto& obj : json) { if (!obj.is_object() || !obj.contains("name")) continue; auto name = QString::fromStdString(obj["name"]); QColor color; if (obj.contains("color")) color.setNamedColor(QString::fromStdString(obj["color"])); auto it = std::find_if(begin(parsedSuggestions), end(parsedSuggestions), [&name](const auto& suggestion) { return name == suggestion.first; }); // Ignore duplicates... if (it != parsedSuggestions.end()) { // unless we can complete the original suggestion with a valid color. if (!it->second.isValid() && color.isValid()) { it->second = color; } else { MITK_WARN << "Ignoring duplicate of suggestion \"" << name.toStdString() << "\"!"; } continue; } parsedSuggestions.emplace_back(name, color); } if (parsedSuggestions.empty()) MITK_WARN << "Could not parse any suggestions from \"" << filename << "\"!"; return parsedSuggestions; } struct Preferences { std::string labelSuggestions; bool replaceStandardSuggestions; bool suggestOnce; std::vector geometry; }; // Get all relevant preferences and consider command-line arguments overrides. Preferences GetPreferences() { auto* nodePrefs = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); Preferences prefs; prefs.labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); if (prefs.labelSuggestions.empty()) prefs.labelSuggestions = nodePrefs->Get("label suggestions", ""); prefs.replaceStandardSuggestions = nodePrefs->GetBool("replace standard suggestions", true); prefs.suggestOnce = nodePrefs->GetBool("suggest once", true); prefs.geometry = nodePrefs->GetByteArray("QmitkNewSegmentationDialog geometry", nullptr, 0); return prefs; } void SaveGeometry(const QByteArray& geometry) { auto* nodePrefs = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); nodePrefs->PutByteArray("QmitkNewSegmentationDialog geometry", reinterpret_cast(geometry.data()), geometry.size()); } // Get names of all labels in all layers of a LabelSetImage. QStringList GetExistingLabelNames(mitk::LabelSetImage* labelSetImage) { QStringList existingLabelNames; existingLabelNames.reserve(labelSetImage->GetTotalNumberOfLabels()); const auto numLayers = labelSetImage->GetNumberOfLayers(); for (std::remove_const_t layerIndex = 0; layerIndex < numLayers; ++layerIndex) { const auto* labelSet = labelSetImage->GetLabelSet(layerIndex); for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { - if (0 == labelIter->first) - continue; // Ignore background label - auto name = QString::fromStdString(labelIter->second->GetName()); if (!name.isEmpty()) // Potential duplicates do not matter for our purpose existingLabelNames.push_back(name); } } return existingLabelNames; } // Remove blacklisted suggestions. QmitkNewSegmentationDialog::SuggestionsType FilterSuggestions(const QmitkNewSegmentationDialog::SuggestionsType& suggestions, const QStringList& blacklist) { QmitkNewSegmentationDialog::SuggestionsType filteredSuggestions; std::remove_copy_if(begin(suggestions), end(suggestions), std::inserter(filteredSuggestions, end(filteredSuggestions)), [&blacklist](const auto& suggestion) { return blacklist.contains(suggestion.first); }); return filteredSuggestions; } } QmitkNewSegmentationDialog::QmitkNewSegmentationDialog(QWidget *parent, mitk::LabelSetImage* labelSetImage, Mode mode) : QDialog(parent), m_Ui(new Ui::QmitkNewSegmentationDialog), m_SuggestOnce(true), m_Color(Qt::red) { m_Ui->setupUi(this); if (RenameLabel == mode) { this->setWindowTitle("Rename Label"); m_Ui->label->setText("New name and color of the label"); m_Ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Rename label"); } else { m_Ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Create label"); } m_Ui->nameLineEdit->setFocus(); connect(this, &QDialog::finished, this, &QmitkNewSegmentationDialog::OnFinished); connect(m_Ui->colorButton, &QToolButton::clicked, this, &QmitkNewSegmentationDialog::OnColorButtonClicked); connect(m_Ui->nameLineEdit, &QLineEdit::textChanged, this, &QmitkNewSegmentationDialog::OnTextChanged); connect(m_Ui->nameList, &QListWidget::itemSelectionChanged, this, &QmitkNewSegmentationDialog::OnSuggestionSelected); connect(m_Ui->nameList, &QListWidget::itemDoubleClicked, this, &QmitkNewSegmentationDialog::OnAccept); connect(m_Ui->buttonBox, &QDialogButtonBox::accepted, this, &QmitkNewSegmentationDialog::OnAccept); this->UpdateColorButtonBackground(); auto prefs = GetPreferences(); if (!prefs.labelSuggestions.empty()) { auto suggestions = ParseSuggestions(prefs.labelSuggestions); this->SetSuggestions(suggestions, prefs.replaceStandardSuggestions && !suggestions.empty()); } else { this->SetSuggestions(GetStandardSuggestions(), true); } if (nullptr != labelSetImage && prefs.suggestOnce) { auto existingLabelNames = GetExistingLabelNames(labelSetImage); m_Suggestions = FilterSuggestions(m_Suggestions, existingLabelNames); this->UpdateNameList(); } if (!(prefs.geometry.empty())) this->restoreGeometry(QByteArray(reinterpret_cast(prefs.geometry.data()), prefs.geometry.size())); } QmitkNewSegmentationDialog::~QmitkNewSegmentationDialog() { } void QmitkNewSegmentationDialog::OnFinished(int) { SaveGeometry(this->saveGeometry()); } void QmitkNewSegmentationDialog::UpdateColorButtonBackground() { m_Ui->colorButton->setStyleSheet("background-color:" + m_Color.name()); } QString QmitkNewSegmentationDialog::GetName() const { return m_Name; } mitk::Color QmitkNewSegmentationDialog::GetColor() const { mitk::Color color; if (m_Color.isValid()) { color.SetRed(m_Color.redF()); color.SetGreen(m_Color.greenF()); color.SetBlue(m_Color.blueF()); } else { color.Set(1.0f, 0.0f, 0.0f); } return color; } void QmitkNewSegmentationDialog::SetName(const QString& name) { m_Ui->nameLineEdit->setText(name); } void QmitkNewSegmentationDialog::SetColor(const mitk::Color& color) { m_Color.setRgbF(color.GetRed(), color.GetGreen(), color.GetBlue()); this->UpdateColorButtonBackground(); } void QmitkNewSegmentationDialog::SetSuggestions(const SuggestionsType& suggestions, bool replaceStandardSuggestions) { m_Suggestions = suggestions; if (!replaceStandardSuggestions) { auto standardSuggestions = GetStandardSuggestions(); // Append all standard suggestions with unique names to the list of custom suggestions std::remove_copy_if(begin(standardSuggestions), end(standardSuggestions), std::inserter(m_Suggestions, end(m_Suggestions)), [this](const auto& suggestion) { return m_Suggestions.end() != std::find_if(begin(m_Suggestions), end(m_Suggestions), [&suggestion](const auto& otherSuggestion) { return suggestion.first == otherSuggestion.first; }); }); } this->UpdateNameList(); } void QmitkNewSegmentationDialog::UpdateNameList() { QStringList names; for (const auto& suggestion : m_Suggestions) names << suggestion.first; m_Ui->nameList->clear(); m_Ui->nameList->addItems(names); } void QmitkNewSegmentationDialog::OnAccept() { m_Name = m_Ui->nameLineEdit->text(); this->accept(); } void QmitkNewSegmentationDialog::OnTextChanged(const QString& text) { auto finding = m_Ui->nameList->findItems(text, Qt::MatchFlag::MatchExactly); if (!finding.isEmpty()) m_Ui->nameList->setCurrentItem(finding.first()); } void QmitkNewSegmentationDialog::OnColorButtonClicked() { auto color = QColorDialog::getColor(m_Color); if (color.isValid()) { m_Color = color; this->UpdateColorButtonBackground(); } } void QmitkNewSegmentationDialog::OnSuggestionSelected() { const auto* currentItem = m_Ui->nameList->currentItem(); if (currentItem == nullptr) return; auto row = m_Ui->nameList->selectionModel()->selectedIndexes().first().row(); m_Ui->nameLineEdit->setText(m_Suggestions[row].first); auto color = m_Suggestions[row].second; if (color.isValid()) { m_Color = color; this->UpdateColorButtonBackground(); } } -bool QmitkNewSegmentationDialog::DoRenameLabel(const mitk::Label* currentLabel, mitk::LabelSetImage* segmentation, QWidget* parent, Mode mode) +bool QmitkNewSegmentationDialog::DoRenameLabel(mitk::Label* label, mitk::LabelSetImage* segmentation, QWidget* parent, Mode mode) { - if (nullptr == currentLabel) mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label is null."; - if (nullptr != segmentation && !segmentation->IsLabeInGroup(currentLabel->GetValue())) mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label value does not exist in segmentation."; + if (nullptr == label) + mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label is null."; + + const auto labelValue = label->GetValue(); + mitk::LabelSetImage::GroupIndexType groupIndex; + + if (nullptr != segmentation && !segmentation->IsLabelInGroup(labelValue, groupIndex)) + mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label value does not exist in segmentation."; + QmitkNewSegmentationDialog dialog(parent, segmentation, mode); - dialog.SetColor(currentLabel->GetColor()); - dialog.SetName(QString::fromStdString(currentLabel->GetName())); + dialog.SetColor(label->GetColor()); + dialog.SetName(QString::fromStdString(label->GetName())); if (dialog.exec() == QDialog::Rejected) - { return false; - } - QString segmentationName = dialog.GetName(); - if (segmentationName.isEmpty()) - { - segmentationName = "Unnamed"; - } + auto name = dialog.GetName(); + + if (name.isEmpty()) + name = "Unnamed"; if (nullptr != segmentation) { - auto groupID = segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); - auto group = segmentation->GetLabelSet(groupID); - group->RenameLabel(currentLabel->GetValue(), segmentationName.toStdString(), dialog.GetColor()); - group->UpdateLookupTable(currentLabel->GetValue()); + auto group = segmentation->GetLabelSet(groupIndex); + group->RenameLabel(labelValue, name.toStdString(), dialog.GetColor()); + group->UpdateLookupTable(labelValue); + } + else + { + label->SetName(name.toStdString()); + label->SetColor(dialog.GetColor()); } return true; } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h index e7c51b335f..8f6f4f0e05 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.h @@ -1,88 +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 QmitkNewSegmentationDialog_h #define QmitkNewSegmentationDialog_h #include #include #include #include #include #include #include namespace mitk { class LabelSetImage; class Label; } namespace Ui { class QmitkNewSegmentationDialog; } -/** - \brief Dialog for naming labels. -*/ +/** \brief Dialog for naming labels. + */ class MITK_QT_SEGMENTATION QmitkNewSegmentationDialog : public QDialog { Q_OBJECT public: using SuggestionsType = std::vector>; enum Mode { NewLabel, RenameLabel }; + /** \brief Static helper function to (re)name a label. + * + * In NewLabel mode it is assumed that the label has not yet been added, hence for example a look-up table update is not done. + * In RenameLabel mode the segmentation (if provided) is updated. + */ + static bool DoRenameLabel(mitk::Label* label, mitk::LabelSetImage* segmentation, QWidget* parent = nullptr, Mode mode = NewLabel); + explicit QmitkNewSegmentationDialog(QWidget *parent = nullptr, mitk::LabelSetImage* labelSetImage = nullptr, Mode mode = NewLabel); ~QmitkNewSegmentationDialog() override; QString GetName() const; mitk::Color GetColor() const; void SetName(const QString& name); void SetColor(const mitk::Color& color); - /**Static helper function to do (re)naming of a label via the dialog class. If mode == NewLabel, the function assumes that the label is not yet - added, thus e.g. no lookup table update is done. In rename mode, if a segmentation is provided, the segmentation will directly be updated.*/ - static bool DoRenameLabel(const mitk::Label* currentLabel, mitk::LabelSetImage* segmentation, QWidget* parent = nullptr, Mode mode = NewLabel); - private: void OnAccept(); void OnFinished(int result); void OnSuggestionSelected(); void OnColorButtonClicked(); void OnTextChanged(const QString& text); void SetSuggestions(const SuggestionsType& suggestions, bool replaceStandardSuggestions = false); void UpdateColorButtonBackground(); void UpdateNameList(); Ui::QmitkNewSegmentationDialog* m_Ui; bool m_SuggestOnce; QString m_Name; QColor m_Color; SuggestionsType m_Suggestions; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 396b35a06b..182470da6c 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1034 +1,1031 @@ /*============================================================================ 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 "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include #include // us #include #include // Qt #include #include #include // vtk #include #include namespace { QList Get2DWindows(const QList allWindows) { QList all2DWindows; for (auto* window : allWindows) { if (window->GetRenderer()->GetMapperID() == mitk::BaseRenderer::Standard2D) { all2DWindows.append(window); } } return all2DWindows; } } const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) , m_SelectionChangeIsAlreadyBeingHandled(false) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnAnySelectionChanged() { // When only a segmentation has been selected and the method is then called by a reference image selection, // the already selected segmentation may not match the geometry predicate of the new reference image anymore. // This will trigger a recursive call of this method further below. While it would be resolved gracefully, we // can spare the extra call with an early-out. The original call of this method will handle the segmentation // selection change afterwards anyway. if (m_SelectionChangeIsAlreadyBeingHandled) return; auto selectedReferenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); bool referenceNodeChanged = false; m_ToolManager->ActivateTool(-1); if (m_ReferenceNode != selectedReferenceNode) { referenceNodeChanged = true; // Remove visibility observer for the current reference node if (m_ReferenceDataObserverTags.find(m_ReferenceNode) != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } // Set new reference node m_ReferenceNode = selectedReferenceNode; m_ToolManager->SetReferenceData(m_ReferenceNode); // Prepare for a potential recursive call when changing node predicates of the working node selector m_SelectionChangeIsAlreadyBeingHandled = true; if (m_ReferenceNode.IsNull()) { // Without a reference image, allow all segmentations to be selected m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_SelectionChangeIsAlreadyBeingHandled = false; } else { // With a reference image, only allow segmentations that fit the geometry of the reference image to be selected. m_Controls->workingNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry()), m_SegmentationPredicate.GetPointer())); m_SelectionChangeIsAlreadyBeingHandled = false; this->ApplySelectionModeOnReferenceNode(); // Add visibility observer for the new reference node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } auto selectedWorkingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool workingNodeChanged = false; if (m_WorkingNode != selectedWorkingNode) { workingNodeChanged = true; // Remove visibility observer for the current working node if (m_WorkingDataObserverTags.find(m_WorkingNode) != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Add visibility observer for the new segmentation node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } // Reset camera if any selection changed but only if both reference node and working node are set if ((referenceNodeChanged || workingNodeChanged) && (m_ReferenceNode.IsNotNull() && m_WorkingNode.IsNotNull())) { if (nullptr != m_RenderWindowPart) { m_RenderWindowPart->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), false); } } this->UpdateGUI(); } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) - { - QmitkNewSegmentationDialog::DoRenameLabel(newLabel,nullptr,m_Parent); - } + QmitkNewSegmentationDialog::DoRenameLabel(newLabel, nullptr, m_Parent); newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels) { auto segmentation = this->GetCurrentSegmentation(); const auto labelValue = labels.front(); const auto groupID = segmentation->GetGroupIndexOfLabel(labelValue); if (groupID != segmentation->GetActiveLayer()) segmentation->SetActiveLayer(groupID); if (labelValue != segmentation->GetActiveLabelSet()->GetActiveLabel()->GetValue()) segmentation->GetActiveLabelSet()->SetActiveLabel(labelValue); segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnGoToLabel(mitk::LabelSetImage::LabelValueType /*label*/, const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelRenameRequested(mitk::Label* label, bool rename) const { auto segmentation = this->GetCurrentSegmentation(); if (rename) { QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::RenameLabel); + return; } - else - { - QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::NewLabel); - } + + QmitkNewSegmentationDialog::DoRenameLabel(label, nullptr, this->m_Parent, QmitkNewSegmentationDialog::Mode::NewLabel); } mitk::LabelSetImage* QmitkSegmentationView::GetCurrentSegmentation() const { auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); if (workingNode.IsNull()) mitkThrow() << "Segmentation view is in an invalid state. Working node is null, but a label selection change has been triggered."; auto segmentation = dynamic_cast(workingNode->GetData()); if (nullptr == segmentation) mitkThrow() << "Segmentation view is in an invalid state. Working node contains no segmentation, but a label selection change has been triggered."; return segmentation; } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut TotalSegmentator"); #ifdef __linux__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &QmitkSegmentationView::OnCurrentLabelSelectionChanged); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &QmitkSegmentationView::OnGoToLabel); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &QmitkSegmentationView::OnLabelRenameRequested); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { if (nullptr == m_RenderWindowPart) { return; } mitk::TimeGeometry* interactionReferenceGeometry = nullptr; auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr != activeTool && m_ReferenceNode.IsNotNull()) { mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNotNull()) { // tool activated, reference image available: set reference geometry interactionReferenceGeometry = m_ReferenceNode->GetData()->GetTimeGeometry(); } } // set the interaction reference geometry for the render window part (might be nullptr) m_RenderWindowPart->SetInteractionReferenceGeometry(interactionReferenceGeometry); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } if (nullptr != m_RenderWindowPart) { auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); if (!m_RenderWindowPart->HasCoupledRenderWindows()) { // react if the active tool changed, only if a render window part with decoupled render windows is used m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); } } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } // remove message-connection to make sure no message is processed if no render window part is available m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); m_Controls->slicesInterpolator->Uninitialize(); } void QmitkSegmentationView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) { if (nullptr == m_RenderWindowPart) { return; } m_Controls->slicesInterpolator->Uninitialize(); auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); } void QmitkSegmentationView::OnPreferencesChanged(const mitk::IPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->multiLabelWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool compactView = prefs->GetBool("compact view", false); int numberOfColumns = compactView ? 6 : 4; m_Controls->toolSelectionBox2D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox2D->SetShowNames(!compactView); m_Controls->toolSelectionBox3D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox3D->SetShowNames(!compactView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = QString::fromStdString(prefs->Get("label set preset", "")); this->ApplyDisplayOptions(); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) this->ApplyDisplayOptions(const_cast(node)); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr == labelSetImage) { return; } // the outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplySelectionMode() { if (!m_SelectionMode) return; this->ApplySelectionModeOnReferenceNode(); this->ApplySelectionModeOnWorkingNode(); } void QmitkSegmentationView::ApplySelectionModeOnReferenceNode() { this->ApplySelectionMode(m_ReferenceNode, m_ReferencePredicate); } void QmitkSegmentationView::ApplySelectionModeOnWorkingNode() { this->ApplySelectionMode(m_WorkingNode, m_SegmentationPredicate); } void QmitkSegmentationView::ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate) { if (!m_SelectionMode || node == nullptr || predicate == nullptr) return; auto nodes = this->GetDataStorage()->GetSubset(predicate); for (auto iter = nodes->begin(); iter != nodes->end(); ++iter) (*iter)->SetVisibility(*iter == node); } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { auto referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool hasReferenceNode = referenceNode.IsNotNull(); bool hasWorkingNode = workingNode.IsNotNull(); bool hasBothNodes = hasReferenceNode && hasWorkingNode; QString warning; bool toolSelectionBoxesEnabled = hasReferenceNode && hasWorkingNode; unsigned int numberOfLabels = 0; m_Controls->multiLabelWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !workingNode->IsVisible(nullptr)) { warning += (!warning.isEmpty() ? "
" : "") + tr("The selected segmentation is currently not visible!"); toolSelectionBoxesEnabled = false; } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->multiLabelWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); auto activeLayer = labelSetImage->GetActiveLayer(); numberOfLabels = labelSetImage->GetNumberOfLabels(activeLayer); if (numberOfLabels > 0) m_Controls->slicesInterpolator->setEnabled(true); m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast(workingNode->GetData())); } else { m_Controls->multiLabelWidget->SetMultiLabelSegmentation(nullptr); } toolSelectionBoxesEnabled &= numberOfLabels > 0; // Here we need to check whether the geometry of the selected segmentation image (working image geometry) // is aligned with the geometry of the 3D render window. // It is not allowed to use a geometry different from the working image geometry for segmenting. // We only need to this if the tool selection box would be enabled without this check. // Additionally this check only has to be performed for render window parts with coupled render windows. // For different render window parts the user is given the option to reinitialize each render window individually // (see QmitkRenderWindow::ShowOverlayMessage). if (toolSelectionBoxesEnabled && nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows()) { const mitk::BaseGeometry* workingNodeGeometry = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* renderWindowGeometry = m_RenderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeometry && nullptr != renderWindowGeometry) { if (!mitk::Equal(*workingNodeGeometry->GetBoundingBox(), *renderWindowGeometry->GetBoundingBox(), mitk::eps, true)) { warning += (!warning.isEmpty() ? "
" : "") + tr("Please reinitialize the selected segmentation image!"); toolSelectionBoxesEnabled = false; } } } m_Controls->toolSelectionBox2D->setEnabled(toolSelectionBoxesEnabled); m_Controls->toolSelectionBox3D->setEnabled(toolSelectionBoxesEnabled); this->UpdateWarningLabel(warning); m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.isEmpty()) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->setText("" + text + ""); m_Controls->selectionWarningLabel->show(); } }