diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 0e402cf10c..238b20e504 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1678 +1,1677 @@ /*============================================================================ 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 <vtkCell.h> #include <vtkTransform.h> #include <vtkTransformPolyDataFilter.h> #include <itkImageRegionIterator.h> #include <itkQuadEdgeMesh.h> #include <itkTriangleMeshToBinaryImageFilter.h> #include <itkLabelGeometryImageFilter.h> -//#include <itkRelabelComponentImageFilter.h> #include <itkCommand.h> #include <itkBinaryFunctorImageFilter.h> namespace mitk { template <typename ImageType> void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void ClearImageBuffer(mitk::Image* image) { if (image->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(image, ClearBufferProcessing, 4); } else { AccessByItk(image, ClearBufferProcessing); } } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ActiveLabelValue(0) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false), m_LookupTable(other.m_LookupTable->Clone()), m_ActiveLabelValue(other.m_ActiveLabelValue) { GroupIndexType i = 0; for (auto groupImage : other.m_LayerContainer) { this->AddLayer(groupImage->Clone(), other.GetConstLabelsByValue(other.GetLabelValuesByGroup(i))); i++; } m_Groups = other.m_Groups; // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType<LabelSetImage::PixelType>()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero ClearImageBuffer(this); // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto [value, label] : m_LabelMap) { this->ReleaseLabel(label); } m_LabelMap.clear(); } 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 { if (m_LayerContainer.size() == 0) mitkThrow() << "Cannot return active layer index. No layer is available."; return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LayerContainer.size(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { if (!this->ExistGroup(indexToDelete)) mitkThrow() << "Cannot remove group. Group does not exist. Invalid group index: "<<indexToDelete; const auto activeIndex = GetActiveLayer(); auto newActiveIndex = activeIndex; auto newActiveIndexBeforeDeletion = activeIndex; //determin new active group index (afte the group will be removed); if (indexToDelete < activeIndex) { //lower the index because position in m_LayerContainer etc has changed newActiveIndex = activeIndex-1; } else if (indexToDelete == activeIndex) { if (this->GetNumberOfLayers() == 1) { //last layer is about to be deleted newActiveIndex = 0; } else { //we have to add/substract one more because we have not removed the layer yet, thus the group count is to 1 high. newActiveIndex = indexToDelete+1 < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 2; newActiveIndexBeforeDeletion = indexToDelete + 1 < GetNumberOfLayers() ? indexToDelete+1 : indexToDelete -1; } } if (activeIndex == indexToDelete) { // we are deleting the active layer, it should not be copied back into the vector m_activeLayerInvalid = true; //copy the image content of the upcoming new active layer; SetActiveLayer(newActiveIndexBeforeDeletion); } auto relevantLabels = m_GroupToLabelMap[indexToDelete]; // remove labels of group for (auto labelValue : relevantLabels) { auto label = m_LabelMap[labelValue]; this->ReleaseLabel(label); m_LabelToGroupMap.erase(labelValue); m_LabelMap.erase(labelValue); this->m_LabelRemovedMessage.Send(labelValue); } // remove the group entries in the maps and the image. m_Groups.erase(m_Groups.begin() + indexToDelete); m_GroupToLabelMap.erase(m_GroupToLabelMap.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); //update old indeces in m_GroupToLabelMap to new layer indices for (auto& element : m_LabelToGroupMap) { if (element.second > indexToDelete) element.second = element.second -1; } //correct active layer index m_ActiveLayer = newActiveIndex; this->m_LabelsChangedMessage.Send(relevantLabels); this->m_GroupRemovedMessage.Send(indexToDelete); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const LabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::ConvertLabelVectorConst(const LabelVectorType& labels) { ConstLabelVectorType result(labels.begin(), labels.end()); return result; }; const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetAllLabelValues() const { LabelValueVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UnlabeledValue }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(ConstLabelVector labels) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); ClearImageBuffer(newImage); return this->AddLayer(newImage, labels); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, ConstLabelVector labels) { GroupIndexType newGroupID = m_Groups.size(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); m_Groups.push_back(""); m_GroupToLabelMap.push_back({}); for (auto label : labels) { if (m_LabelMap.end() != m_LabelMap.find(label->GetValue())) { mitkThrow() << "Cannot add layer. Labels that should be added with layer use at least one label value that is already in use. Conflicted label value: " << label->GetValue(); } auto labelClone = label->Clone(); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); this->AddLabelToMap(labelClone->GetValue(), labelClone, newGroupID); this->RegisterLabel(labelClone); } this->Modified(); this->m_GroupAddedMessage.Send(newGroupID); return newGroupID; } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& labelSet) { if (m_LayerContainer.size() <= groupID) { mitkThrow() << "Trying to replace labels of non-exising group. Invalid group id: "<<groupID; } //remove old group labels auto oldLabels = this->m_GroupToLabelMap[groupID]; for (auto labelID : oldLabels) { this->RemoveLabelFromMap(labelID); this->m_LabelRemovedMessage.Send(labelID); } this->m_LabelsChangedMessage.Send(oldLabels); this->m_GroupModifiedMessage.Send(groupID); //add new labels to group for (auto label : labelSet) { this->AddLabel(label->Clone(), groupID, true, false); } } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& labelSet) { return ReplaceGroupLabels(groupID, ConvertLabelVectorConst(labelSet)); } mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID]; } const mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID].GetPointer(); } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::SetActiveLabel(LabelValueType label) { m_ActiveLabelValue = label; if (label != UnlabeledValue) { auto groupID = this->GetGroupIndexOfLabel(label); if (groupID!=this->GetActiveLayer()) this->SetActiveLayer(groupID); } Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { ClearImageBuffer(this); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LayerContainer.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(); } this->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<PixelType>& 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(); } this->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) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) return; auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); this->RemoveLabelFromMap(pixelValue); if (m_ActiveLabelValue == pixelValue) { this->SetActiveLabel(0); } this->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabelFromMap(LabelValueType pixelValue) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) mitkThrow()<<"Invalid state of of instance. RemoveLabelFromMap was called for unkown label id. invalid label id: "<<pixelValue; auto groupID = this->GetGroupIndexOfLabel(pixelValue); this->ReleaseLabel(m_LabelMap[pixelValue]); //now remove the label entry itself m_LabelMap.erase(pixelValue); m_LabelToGroupMap.erase(pixelValue); auto labelsInGroup = m_GroupToLabelMap[groupID]; labelsInGroup.erase(std::remove(labelsInGroup.begin(), labelsInGroup.end(), pixelValue), labelsInGroup.end()); m_GroupToLabelMap[groupID] = labelsInGroup; } void mitk::LabelSetImage::RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues) { for (const auto labelValue : vectorOfLabelPixelValues) { this->RemoveLabel(labelValue); } this->m_LabelsChangedMessage.Send(vectorOfLabelPixelValues); } 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<PixelType>& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::GetUnusedLabelValue() const { auto usedValues = this->GetUsedLabelValues(); return usedValues.back() + 1; } mitk::Label* mitk::LabelSetImage::AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone, bool correctLabelValue) { unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LayerContainer.size() >= max_size) return nullptr; mitk::Label::Pointer newLabel = addAsClone ? label->Clone() : Label::Pointer(label); auto pixelValue = newLabel->GetValue(); auto usedValues = this->GetUsedLabelValues(); auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); if (!usedValues.empty() && usedValues.end() != finding) { if (correctLabelValue) { pixelValue = this->GetUnusedLabelValue(); newLabel->SetValue(pixelValue); } else { mitkThrow() << "Cannot add label due to conflicting label value that already exists in the MultiLabelSegmentation. Conflicting label value: " << pixelValue; } } // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); this->AddLabelToMap(pixelValue, newLabel, groupID); this->RegisterLabel(newLabel); m_LabelAddedMessage.Send(newLabel->GetValue()); m_ActiveLabelValue = newLabel->GetValue(); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabel(const std::string& name, const mitk::Color& color, GroupIndexType groupID) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); return AddLabel(newLabel,groupID,false); } void mitk::LabelSetImage::RenameLabel(PixelType pixelValue, const std::string& name, const mitk::Color& color) { mitk::Label* label = GetLabel(pixelValue); label->SetName(name); label->SetColor(color); this->UpdateLookupTable(pixelValue); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } mitk::Label *mitk::LabelSetImage::GetActiveLabel() { if (m_ActiveLabelValue == UnlabeledValue) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } const mitk::Label* mitk::LabelSetImage::GetActiveLabel() const { if (m_ActiveLabelValue == UnlabeledValue) return nullptr; auto finding = m_LabelMap.find(m_ActiveLabelValue); return finding == m_LabelMap.end() ? nullptr : finding->second; } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); } void mitk::LabelSetImage::SetLookupTable(mitk::LookupTable* lut) { m_LookupTable = lut; this->Modified(); } void mitk::LabelSetImage::UpdateLookupTable(PixelType pixelValue) { const mitk::Color& color = this->GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast<int>(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (GetLabel(pixelValue)->GetVisible()) rgba[3] = GetLabel(pixelValue)->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast<int>(pixelValue), rgba); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this, CalculateCenterOfMassProcessing, 4, pixelValue); } else { AccessByItk_1(this, CalculateCenterOfMassProcessing, pixelValue); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_GroupToLabelMap[layer].size(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { return m_LabelMap.size(); } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index) { if (!this->ExistLabel(index)) mitkThrow() << "Error, cannot return label mask. Label ID is invalid. Invalid ID: " << index; auto mask = mitk::Image::New(); // 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()); ClearImageBuffer(mask); const auto groupID = this->GetGroupIndexOfLabel(index); auto destinationLabel = this->GetLabel(index)->Clone(); destinationLabel->SetValue(1); TransferLabelContent(this->GetGroupImage(groupID), mask.GetPointer(), {destinationLabel}, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, false, { { index, destinationLabel->GetValue()}}, MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); 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<mitk::Image *>(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (Exception e) { mitkReThrow(e) << "Could not intialize by provided labeled image."; } catch (...) { mitkThrow() << "Could not intialize by provided labeled image due to unkown error."; } this->Modified(); } template <typename LabelSetImageType, typename ImageType> void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<LabelSetImageType> TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { const auto originalSourceValue = sourceIter.Get(); const auto sourceValue = static_cast<PixelType>(originalSourceValue); if (originalSourceValue > mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains a pixel value that exceeds the label value range. Invalid pixel value:" << originalSourceValue; } targetIter.Set(sourceValue); if (LabelSetImage::UnlabeledValue!=sourceValue && !this->ExistLabel(sourceValue)) { if (this->GetTotalNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains to many labels."; } std::stringstream name; name << "object-" << sourceValue; double rgba[4]; this->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->AddLabel(label,0,false); } ++sourceIter; ++targetIter; } } template <typename ImageType> void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); const auto activeLabel = this->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UnlabeledValue) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template <typename ImageType> void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, LabelValueType pixelValue) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter<ImageType>::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; this->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); this->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template <typename TPixel, unsigned int VImageDimension> void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image<TPixel, VImageDimension> *target, unsigned int layer) { typedef itk::Image<TPixel, VImageDimension> ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage<TPixel, VImageDimension>(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> 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 <typename TPixel, unsigned int VImageDimension> void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image<TPixel, VImageDimension> *source, unsigned int layer) const { typedef itk::Image<TPixel, VImageDimension> ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage<TPixel, VImageDimension>(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> 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 <typename ImageType> void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator<ImageType> IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template <typename ImageType> void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator<ImageType> IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; if (!this->ExistGroup(groupID)) mitkThrow() << "Cannot add label. Defined group is unkown. Invalid group index: " << groupID; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = std::find(m_GroupToLabelMap[groupID].begin(), m_GroupToLabelMap[groupID].end(), labelValue); if (groupFinding == m_GroupToLabelMap[groupID].end()) { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::RegisterLabel(mitk::Label* label) { UpdateLookupTable(label->GetValue()); auto command = itk::MemberCommand<LabelSetImage>::New(); command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); label->AddObserver(itk::ModifiedEvent(), command); } void mitk::LabelSetImage::ReleaseLabel(Label* label) { if (nullptr == label) mitkThrow() << "Invalid call of ReleaseLabel with a nullptr."; label->RemoveAllObservers(); } void mitk::LabelSetImage::ApplyToLabels(const LabelValueVectorType& values, std::function<void(Label*)>&& lambda) { auto labels = this->GetLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); m_LabelsChangedMessage.Send(values); } void mitk::LabelSetImage::VisitLabels(const LabelValueVectorType& values, std::function<void(const Label*)>&& lambda) const { auto labels = this->GetConstLabelsByValue(values); std::for_each(labels.begin(), labels.end(), lambda); } void mitk::LabelSetImage::OnLabelModified(const Object* sender, const itk::EventObject&) { auto label = dynamic_cast<const Label*>(sender); if (nullptr == label) mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; Superclass::Modified(); this->m_LabelModifiedMessage.Send(label->GetValue()); } bool mitk::LabelSetImage::ExistLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); return m_LabelMap.end() != finding; } bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { return finding->second == groupIndex; } return false; } bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LayerContainer.size(); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value) const { GroupIndexType dummy; return this->IsLabelInGroup(value, dummy); } 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::LabelVectorType mitk::LabelSetImage::GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) { LabelVectorType result; for (const auto& labelValue : labelValues) { auto* label = this->GetLabel(labelValue); if (label != nullptr) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) const { ConstLabelVectorType result; for (const auto& labelValue : labelValues) { const auto* label = this->GetLabel(labelValue); if (label != nullptr) { result.emplace_back(label); } else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } return result; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; return m_GroupToLabelMap[index]; } const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByName(GroupIndexType index, std::string_view name) const { LabelValueVectorType result; auto searchName = [&result, name](const Label* l) { if(l->GetName() == name) result.push_back(l->GetValue()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return result; } std::vector<std::string> mitk::LabelSetImage::GetLabelClassNames() const { std::set<std::string> names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetAllLabelValues(), searchName); return std::vector<std::string>(names.begin(), names.end()); } std::vector<std::string> mitk::LabelSetImage::GetLabelClassNamesByGroup(GroupIndexType index) const { std::set<std::string> names; auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return std::vector<std::string>(names.begin(), names.end()); } void mitk::LabelSetImage::SetAllLabelsVisible(bool visible) { auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; this->ApplyToLabels(this->GetAllLabelValues(), setVisibility); } void mitk::LabelSetImage::SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible) { auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setVisibility); } void mitk::LabelSetImage::SetAllLabelsVisibleByName(GroupIndexType group, std::string_view name, bool visible) { auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setVisibility); } void mitk::LabelSetImage::SetAllLabelsLocked(bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetAllLabelValues(), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByGroup(GroupIndexType group, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByGroup(group), setLock); } void mitk::LabelSetImage::SetAllLabelsLockedByName(GroupIndexType group, std::string_view name, bool locked) { auto setLock = [locked](Label* l) { l->SetLocked(locked); }; this->ApplyToLabels(this->GetLabelValuesByName(group, name), setLock); } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // m_LookupTable; const mitk::LookupTable* lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable* rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tabels not equal."; return returnValue; ; } // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } if (leftHandSide.GetTotalNumberOfLabels() != rightHandSide.GetTotalNumberOfLabels()) { MITK_INFO(verbose) << "Number of labels are not equal."; return false; } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetGroupImage(layerIndex), *rightHandSide.GetGroupImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // label data auto leftLabelsInGroup = leftHandSide.GetLabelValuesByGroup(layerIndex); auto rightLabelsInGroup = rightHandSide.GetLabelValuesByGroup(layerIndex); if (leftLabelsInGroup.size()!=rightLabelsInGroup.size()) { MITK_INFO(verbose) << "Number of layer labels is not equal. Invalid layer:" <<layerIndex; return false; } for (ConstLabelVector::size_type index = 0; index < leftLabelsInGroup.size(); ++index) { if (!mitk::Equal(*(leftHandSide.GetLabel(leftLabelsInGroup[index])), *(rightHandSide.GetLabel(rightLabelsInGroup[index])),eps,verbose)) { MITK_INFO(verbose) << "At least one label in layer is not equal. Invalid layer:" << layerIndex; return false; } } } return returnValue; } bool mitk::Equal(const mitk::LabelSetImage::ConstLabelVectorType& leftHandSide, const mitk::LabelSetImage::ConstLabelVectorType& rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; // container size; returnValue = leftHandSide.size() == rightHandSide.size(); if (!returnValue) { MITK_INFO(verbose) << "Number of labels not equal."; return returnValue; ; } // m_LabelContainer; auto lhsit = leftHandSide.begin(); auto rhsit = rightHandSide.begin(); for (; lhsit != leftHandSide.end(); ++lhsit, ++rhsit) { returnValue = mitk::Equal(**rhsit, **lhsit,eps,verbose); if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } } return returnValue; } /**Helper function to convert a vector of labels into a label map * @pre every label in the vector has a unique value.*/ using ConstLabelMapType = std::map<mitk::LabelSetImage::LabelValueType, mitk::Label::ConstPointer>; ConstLabelMapType ConvertLabelVectorToMap(const mitk::ConstLabelVector& labelV) { ConstLabelMapType result; for (auto label : labelV) { const auto value = label->GetValue(); auto finding = result.find(value); if (finding != result.end()) mitkThrow() << "Operation failed. Cannot convert label vector into label map, because at least one label value is not unique. Violating label value: " << value; result.insert(std::make_pair(value, label)); } return result; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template <class TDestinationPixel, class TSourcePixel, class TOutputpixel> class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const ConstLabelMapType& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabels(destinationLabels), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabels == other.m_DestinationLabels; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabels = other.m_DestinationLabels; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto labelFinding = this->m_DestinationLabels.find(existingDestinationValue); if (labelFinding==this->m_DestinationLabels.end() || !labelFinding->second->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: ConstLabelMapType m_DestinationLabels; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template<unsigned int VImageDimension> void TransferLabelContentAtTimeStepHelper(const itk::Image<mitk::Label::PixelType, VImageDimension>* itkSourceImage, mitk::Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image<mitk::Label::PixelType, VImageDimension> 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 <mitk::Label::PixelType, mitk::Label::PixelType, mitk::Label::PixelType> LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter<ContentImageType, ContentImageType, ContentImageType, LabelTransferFunctorType> FilterType; LabelTransferFunctorType transferFunctor(ConvertLabelVectorToMap(destinationLabels), sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } auto destLabelMap = ConvertLabelVectorToMap(destinationLabels); for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue!=newDestinationLabel && destLabelMap.end() == destLabelMap.find(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabels, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } auto destinationLabels = destinationImage->GetConstLabelsByValue(destinationImage->GetLabelValuesByGroup(destinationImage->GetActiveLayer())); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::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, destinationLabels, timeStep, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index c41f3027ef..16e31e9cea 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,795 +1,795 @@ /*============================================================================ 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 "mitkSegTool2D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkPlaneGeometry.h" #include <mitkTimeNavigationController.h> #include "mitkImageAccessByItk.h" // Include of the new ImageExtractor #include "mitkMorphologicalOperations.h" #include "mitkPlanarCircle.h" #include "usGetModuleContext.h" // Includes for 3DSurfaceInterpolation #include "mitkImageTimeSelector.h" #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" // includes for resling and overwriting #include <mitkExtractSliceFilter.h> #include <mitkVtkImageOverwrite.h> #include <vtkImageData.h> #include <vtkSmartPointer.h> #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include <mitkDiffSliceOperationApplier.h> #include "mitkAbstractTransformGeometry.h" #include "mitkLabelSetImage.h" #include "mitkContourModelUtils.h" // #include <itkImageRegionIterator.h> #include <vtkAbstractArray.h> #include <vtkFieldData.h> #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) bool mitk::SegTool2D::m_SurfaceInterpolationEnabled = true; mitk::SegTool2D::SliceInformation::SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep) : slice(aSlice), plane(aPlane), timestep(aTimestep) { } mitk::SegTool2D::SegTool2D(const char *type, const us::Module *interactorModule) : Tool(type, interactorModule), m_Contourmarkername("Position") { Tool::m_EventConfig = "DisplayConfigBlockLMB.xml"; } mitk::SegTool2D::~SegTool2D() { } bool mitk::SegTool2D::FilterEvents(InteractionEvent *interactionEvent, DataNode *) { const auto *positionEvent = dynamic_cast<const InteractionPositionEvent *>(interactionEvent); bool isValidEvent = (positionEvent && // Only events of type mitk::InteractionPositionEvent interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard2D // Only events from the 2D renderwindows ); return isValidEvent; } bool mitk::SegTool2D::DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice) { assert(image); assert(plane); // compare normal of plane to the three axis vectors of the image Vector3D normal = plane->GetNormal(); Vector3D imageNormal0 = image->GetSlicedGeometry()->GetAxisVector(0); Vector3D imageNormal1 = image->GetSlicedGeometry()->GetAxisVector(1); Vector3D imageNormal2 = image->GetSlicedGeometry()->GetAxisVector(2); normal.Normalize(); imageNormal0.Normalize(); imageNormal1.Normalize(); imageNormal2.Normalize(); imageNormal0.SetVnlVector(vnl_cross_3d<ScalarType>(normal.GetVnlVector(), imageNormal0.GetVnlVector())); imageNormal1.SetVnlVector(vnl_cross_3d<ScalarType>(normal.GetVnlVector(), imageNormal1.GetVnlVector())); imageNormal2.SetVnlVector(vnl_cross_3d<ScalarType>(normal.GetVnlVector(), imageNormal2.GetVnlVector())); double eps(0.00001); // axial if (imageNormal2.GetNorm() <= eps) { affectedDimension = 2; } // sagittal else if (imageNormal1.GetNorm() <= eps) { affectedDimension = 1; } // coronal else if (imageNormal0.GetNorm() <= eps) { affectedDimension = 0; } else { affectedDimension = -1; // no idea return false; } // determine slice number in image BaseGeometry *imageGeometry = image->GetGeometry(0); Point3D testPoint = imageGeometry->GetCenter(); Point3D projectedPoint; plane->Project(testPoint, projectedPoint); Point3D indexPoint; imageGeometry->WorldToIndex(projectedPoint, indexPoint); affectedSlice = ROUND(indexPoint[affectedDimension]); MITK_DEBUG << "indexPoint " << indexPoint << " affectedDimension " << affectedDimension << " affectedSlice " << affectedSlice; // check if this index is still within the image if (affectedSlice < 0 || affectedSlice >= static_cast<int>(image->GetDimension(affectedDimension))) return false; return true; } void mitk::SegTool2D::UpdateAllSurfaceInterpolations(const LabelSetImage *workingImage, TimeStepType timeStep, const PlaneGeometry *plane, bool detectIntersection) { if (nullptr == workingImage) mitkThrow() << "Cannot update surface interpolation. Invalid working image passed."; if (nullptr == plane) mitkThrow() << "Cannot update surface interpolation. Invalid plane passed."; auto affectedLabels = mitk::SurfaceInterpolationController::GetInstance()->GetAffectedLabels(workingImage, timeStep, plane); for (auto affectedLabel : affectedLabels) { auto groupID = workingImage->GetGroupIndexOfLabel(affectedLabel); auto slice = GetAffectedImageSliceAs2DImage(plane, workingImage->GetGroupImage(groupID), timeStep); std::vector<SliceInformation> slices = { SliceInformation(slice, plane, timeStep) }; - Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection, affectedLabel); + Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection, affectedLabel, true); } + + if(!affectedLabels.empty()) mitk::SurfaceInterpolationController::GetInstance()->Modified(); } void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo, LabelSetImage::LabelValueType labelValue) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; contourInfo.LabelValue = labelValue; contourInfo.TimeStep = sliceInfo.timestep; contourInfo.Plane = sliceInfo.plane; mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo, true); } template <typename ImageType> void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector<SliceInformation>& sliceInfos, const Image* workingImage, bool detectIntersection, - mitk::Label::PixelType activeLabelValue) + mitk::Label::PixelType activeLabelValue, bool silent) { if (!m_SurfaceInterpolationEnabled) return; //Remark: the ImageTimeSelector is just needed to extract a timestep/channel of //the image in order to get the image dimension (time dimension and channel dimension //stripped away). Therfore it is OK to always use time step 0 and channel 0 mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(workingImage); timeSelector->SetTimeNr(0); timeSelector->SetChannelNr(0); timeSelector->Update(); const auto dimRefImg = timeSelector->GetOutput()->GetDimension(); if (dimRefImg != 3) return; std::vector<mitk::Surface::Pointer> contourList; contourList.reserve(sliceInfos.size()); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); std::vector<SliceInformation> relevantSlices = sliceInfos; if (detectIntersection) { relevantSlices.clear(); for (const auto& sliceInfo : sliceInfos) { // Test whether there is something to extract or whether the slice just contains intersections of others //Remark we cannot just errode the clone of sliceInfo.slice, because Erode currently only //works on pixel value 1. But we need to erode active label. Therefore we use TransferLabelContent //as workarround. //If MorphologicalOperations::Erode is supports user defined pixel values, the workarround //can be removed. //Workarround starts mitk::Image::Pointer slice2 = Image::New(); slice2->Initialize(sliceInfo.slice); AccessByItk(slice2, ClearBufferProcessing); LabelSetImage::LabelValueType erodeValue = 1; auto label = Label::New(erodeValue, ""); TransferLabelContent(sliceInfo.slice, slice2, { label }, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, false, { {activeLabelValue, erodeValue} }); //Workarround ends mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); contourExtractor->SetInput(slice2); contourExtractor->SetContourValue(erodeValue); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo, activeLabelValue); } else { relevantSlices.push_back(sliceInfo); } } } - if (relevantSlices.empty()) - return; - SurfaceInterpolationController::CPIVector cpis; for (const auto& sliceInfo : relevantSlices) { contourExtractor->SetInput(sliceInfo.slice); contourExtractor->SetContourValue(activeLabelValue); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo, activeLabelValue); } else { cpis.emplace_back(contour, sliceInfo.plane->Clone(), activeLabelValue, sliceInfo.timestep); } } - mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(cpis); + //this call is relevant even if cpis is empty to ensure SurfaceInterpolationController::Modified is triggered if silent==false; + mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(cpis, false, silent); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const InteractionPositionEvent *positionEvent, const Image *image, unsigned int component /*= 0*/) { if (!positionEvent) { return nullptr; } assert(positionEvent->GetSender()); // sure, right? const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); // get the timestep of the visible part (time-wise) of the image return GetAffectedImageSliceAs2DImage(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(), image, timeStep, component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint), component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PlaneGeometry *planeGeometry, const Image *image, TimeStepType timeStep, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer vtkSmartPointer<mitkVtkImageOverwrite> reslice = vtkSmartPointer<mitkVtkImageOverwrite>::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); // additionally extract the given component // default is 0; the extractor checks for multi-component images extractor->SetComponent(component); extractor->Modified(); extractor->Update(); Image::Pointer slice = extractor->GetOutput(); return slice; } mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const InteractionPositionEvent *positionEvent) const { const auto workingNode = this->GetWorkingDataNode(); if (!workingNode) { return nullptr; } const auto *workingImage = dynamic_cast<Image *>(workingNode->GetData()); if (!workingImage) { return nullptr; } return GetAffectedImageSliceAs2DImage(positionEvent, workingImage); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const InteractionPositionEvent *positionEvent) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto *referenceImage = dynamic_cast<Image *>(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage); } } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto* referenceImage = dynamic_cast<Image*>(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep); } } void mitk::SegTool2D::Activated() { Superclass::Activated(); this->GetToolManager()->SelectedTimePointChanged += mitk::MessageDelegate<mitk::SegTool2D>(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); } void mitk::SegTool2D::Deactivated() { this->GetToolManager()->SelectedTimePointChanged -= mitk::MessageDelegate<mitk::SegTool2D>(this, &mitk::SegTool2D::OnTimePointChangedInternal); Superclass::Deactivated(); } void mitk::SegTool2D::OnTimePointChangedInternal() { if (m_IsTimePointChangeAware && nullptr != this->GetWorkingDataNode()) { const TimePointType timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (timePoint != m_LastTimePointTriggered) { m_LastTimePointTriggered = timePoint; this->OnTimePointChanged(); } } } void mitk::SegTool2D::OnTimePointChanged() { //default implementation does nothing } mitk::DataNode* mitk::SegTool2D::GetWorkingDataNode() const { if (nullptr != this->GetToolManager()) { return this->GetToolManager()->GetWorkingData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetWorkingData() const { auto node = this->GetWorkingDataNode(); if (nullptr != node) { return dynamic_cast<Image*>(node->GetData()); } return nullptr; } mitk::DataNode* mitk::SegTool2D::GetReferenceDataNode() const { if (nullptr != this->GetToolManager()) { return this->GetToolManager()->GetReferenceData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetReferenceData() const { auto node = this->GetReferenceDataNode(); if (nullptr != node) { return dynamic_cast<Image*>(node->GetData()); } return nullptr; } void mitk::SegTool2D::WriteBackSegmentationResult(const InteractionPositionEvent *positionEvent, const Image * segmentationResult) { if (!positionEvent) return; const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); const auto *abstractTransformGeometry( dynamic_cast<const AbstractTransformGeometry *>(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (planeGeometry && segmentationResult && !abstractTransformGeometry) { const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast<Image *>(workingNode->GetData()); const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); this->WriteBackSegmentationResult(planeGeometry, segmentationResult, timeStep); } } void mitk::SegTool2D::WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; SliceInformation sliceInfo(segmentationResult, const_cast<mitk::PlaneGeometry*>(planeGeometry), timeStep); Self::WriteBackSegmentationResults(workingNode, { sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image * segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; if(m_LastEventSender == nullptr) { return; } unsigned int currentSlicePosition = m_LastEventSender->GetSliceNavigationController()->GetStepper()->GetPos(); SliceInformation sliceInfo(segmentationResult, const_cast<mitk::PlaneGeometry *>(planeGeometry), timeStep); sliceInfo.slicePosition = currentSlicePosition; WriteBackSegmentationResults({ sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector<SegTool2D::SliceInformation> &sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == m_LastEventSender) { MITK_WARN << "Cannot write tool results. Tool seems to be in an invalid state, as no interaction event was recieved but is expected."; return; } const auto workingNode = this->GetWorkingDataNode(); // the first geometry is needed otherwise restoring the position is not working const auto* plane3 = dynamic_cast<const PlaneGeometry*>(dynamic_cast<const mitk::SlicedGeometry3D*>( m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) ->GetPlaneGeometry(0)); const unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetStepper()->GetPos(); mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); /* A cleaner solution would be to add a contour marker for each slice info. It currently does not work as the contour markers expect that the plane is always the plane of slice 0. Had not the time to do it properly no. Should be solved by T28146*/ this->AddContourmarker(plane3, slicePosition); } void mitk::SegTool2D::WriteBackSegmentationResults(const DataNode* workingNode, const std::vector<SliceInformation>& sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == workingNode) { mitkThrow() << "Cannot write slice to working node. Working node is invalid."; } auto image = dynamic_cast<Image*>(workingNode->GetData()); mitk::Label::PixelType activeLabelValue = 0; try{ auto labelSetImage = dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData()); activeLabelValue = labelSetImage->GetActiveLabel()->GetValue(); } catch(...) { mitkThrow() << "Working node does not contain labelSetImage."; } if (nullptr == image) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } for (const auto& sliceInfo : sliceList) { if (writeSliceToVolume && nullptr != sliceInfo.plane && sliceInfo.slice.IsNotNull()) { SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false, activeLabelValue); // also mark its node as modified (T27308). Can be removed if T27307 // is properly solved if (workingNode != nullptr) workingNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo) { SliceInformation sliceInfo(slice, planeGeometry, timeStep); WriteSliceToVolume(workingImage, sliceInfo, allowUndo); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo) { if (nullptr == workingImage) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } DiffSliceOperation* undoOperation = nullptr; if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // Create undo operation by caching the not yet modified slices mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, workingImage, sliceInfo.timestep); undoOperation = new DiffSliceOperation(workingImage, originalSlice, dynamic_cast<SlicedGeometry3D*>(originalSlice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); /*============= END undo/redo feature block ========================*/ } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer<mitkVtkImageOverwrite> reslice = vtkSmartPointer<mitkVtkImageOverwrite>::New(); // Set the slice as 'input' // casting const away is needed and OK as long the OverwriteMode of // mitkVTKImageOverwrite is true. // Reason: because then the input slice is not touched but // used to overwrite the input of the ExtractSliceFilter. auto noneConstSlice = const_cast<Image*>(sliceInfo.slice.GetPointer()); reslice->SetInputSlice(noneConstSlice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(workingImage); extractor->SetTimeStep(sliceInfo.timestep); extractor->SetWorldGeometry(sliceInfo.plane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(workingImage->GetGeometry(sliceInfo.timestep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so workingImage->Modified(); workingImage->GetVtkImageData()->Modified(); if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // specify the redo operation with the edited slice auto* doOperation = new DiffSliceOperation(workingImage, extractor->GetOutput(), dynamic_cast<SlicedGeometry3D*>(sliceInfo.slice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); // create an operation event for the undo stack OperationEvent* undoStackItem = new OperationEvent(DiffSliceOperationApplier::GetInstance(), doOperation, undoOperation, "Segmentation"); // add it to the undo controller UndoStackItem::IncCurrObjectEventId(); UndoStackItem::IncCurrGroupEventId(); UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); /*============= END undo/redo feature block ========================*/ } } void mitk::SegTool2D::SetShowMarkerNodes(bool status) { m_ShowMarkerNodes = status; } void mitk::SegTool2D::SetEnable3DInterpolation(bool enabled) { m_SurfaceInterpolationEnabled = enabled; } int mitk::SegTool2D::AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex) { if (planeGeometry == nullptr) return -1; us::ServiceReference<PlanePositionManagerService> serviceRef = us::GetModuleContext()->GetServiceReference<PlanePositionManagerService>(); PlanePositionManagerService *service = us::GetModuleContext()->GetService(serviceRef); unsigned int size = service->GetNumberOfPlanePositions(); unsigned int id = service->AddNewPlanePosition(planeGeometry, sliceIndex); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); contourMarker->SetPlaneGeometry(planeGeometry->Clone()); contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); contourMarker->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); std::stringstream markerStream; auto workingNode = this->GetWorkingDataNode(); markerStream << m_Contourmarkername; markerStream << " "; markerStream << id + 1; DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty("name", StringProperty::New(markerStream.str())); rotatedContourNode->SetProperty("isContourMarker", BoolProperty::New(true)); rotatedContourNode->SetBoolProperty("PlanarFigureInitializedWindow", true, m_LastEventSender); rotatedContourNode->SetProperty("includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty("helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); rotatedContourNode->SetProperty("planarfigure.drawcontrolpoints", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawname", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawoutline", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawshadow", BoolProperty::New(false)); if (planeGeometry) { if (id == size) { this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer markers = this->GetToolManager()->GetDataStorage()->GetDerivations(workingNode, isMarker); for (auto iter = markers->begin(); iter != markers->end(); ++iter) { std::string nodeName = (*iter)->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int markerId = atof(nodeName.substr(t + 1).c_str()) - 1; if (id == markerId) { return id; } } this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } } return id; } void mitk::SegTool2D::InteractiveSegmentationBugMessage(const std::string &message) const { MITK_ERROR << "********************************************************************************" << std::endl << " " << message << std::endl << "********************************************************************************" << std::endl << " " << std::endl << " If your image is rotated or the 2D views don't really contain the patient image, try to press the " "button next to the image selection. " << std::endl << " " << std::endl << " Please file a BUG REPORT: " << std::endl << " https://phabricator.mitk.org/" << std::endl << " Contain the following information:" << std::endl << " - What image were you working on?" << std::endl << " - Which region of the image?" << std::endl << " - Which tool did you use?" << std::endl << " - What did you do?" << std::endl << " - What happened (not)? What did you expect?" << std::endl; } bool mitk::SegTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent* positionEvent, const mitk::BaseData* data) { bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); if (!isPositionEventInsideImageRegion) MITK_WARN("EditableContourTool") << "PositionEvent is outside ImageRegion!"; return isPositionEventInsideImageRegion; } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.h b/Modules/Segmentation/Interactions/mitkSegTool2D.h index 6be2ad9459..a8ba18eb0b 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,289 +1,290 @@ /*============================================================================ 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 mitkSegTool2D_h #define mitkSegTool2D_h #include <mitkCommon.h> #include <mitkImage.h> #include <mitkTool.h> #include <MitkSegmentationExports.h> #include <mitkInteractionPositionEvent.h> #include <mitkInteractionConst.h> #include <mitkPlanePositionManager.h> #include <mitkRestorePlanePositionOperation.h> #include <mitkDiffSliceOperation.h> namespace mitk { class BaseRenderer; /** \brief Abstract base class for segmentation tools. \sa Tool \ingroup Interaction \ingroup ToolManagerEtAl Implements 2D segmentation specific helper methods, that might be of use to all kind of 2D segmentation tools. At the moment these are: - Determination of the slice where the user paints upon (DetermineAffectedImageSlice) - Projection of a 3D contour onto a 2D plane/slice SegTool2D tries to structure the interaction a bit. If you pass "PressMoveRelease" as the interaction type of your derived tool, you might implement the methods OnMousePressed, OnMouseMoved, and OnMouseReleased. Yes, your guess about when they are called is correct. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT SegTool2D : public Tool { public: mitkClassMacro(SegTool2D, Tool); /** \brief Calculates for a given Image and PlaneGeometry, which slice of the image (in index corrdinates) is meant by the plane. \return false, if no slice direction seems right (e.g. rotated planes) \param image \param plane \param affectedDimension The image dimension, which is constant for all points in the plane, e.g. Axial --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice); /** * @brief Updates the surface interpolations by extracting the contour form the given slice for all labels * that have a surface contour information stored for the given plane at the given timestep. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image * @param timeStep the time step for wich the surface interpolation information should be updated. * @param plane the plane in which the slice lies * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateAllSurfaceInterpolations(const LabelSetImage* workingImage, TimeStepType timeStep, const PlaneGeometry *plane, bool detectIntersection); /** * \brief Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of an vector image. * * \param positionEvent Event that specifies the plane that should be used to slice * \param image Image that should be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const InteractionPositionEvent* positionEvent, const Image* image, unsigned int component = 0); /** * \brief Extract the slice of an image cut by given plane. The given component denotes the vector component of a vector image. * * \param planeGeometry Geometry defining the slice that should be cut out. * \param image Image that should be sliced * \param timeStep TimeStep of the image that shold be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, const Image* image, TimeStepType timeStep, unsigned int component = 0); static Image::Pointer GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component = 0); /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * Calls static WriteBackSegmentationResults*/ static void WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep); /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * For more details see protected WriteSliceToVolume version.*/ static void WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo); void SetShowMarkerNodes(bool); /** * \brief Enables or disables the 3D interpolation after writing back the 2D segmentation result, and defaults to * true. */ void SetEnable3DInterpolation(bool); void Activated() override; void Deactivated() override; itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); protected: SegTool2D(); // purposely hidden SegTool2D(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~SegTool2D() override; /** * @brief returns the segmentation node that should be modified by the tool. */ DataNode* GetWorkingDataNode() const; Image* GetWorkingData() const; DataNode* GetReferenceDataNode() const; Image* GetReferenceData() const; /** * This function can be reimplemented by derived classes to react on changes of the current * time point. Default implementation does nothing.*/ virtual void OnTimePointChanged(); struct SliceInformation { mitk::Image::ConstPointer slice; const mitk::PlaneGeometry *plane = nullptr; mitk::TimeStepType timestep = 0; unsigned int slicePosition; SliceInformation() = default; SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep); }; /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param sliceInfos vector of slice information instances from which the contours should be extracted * @param workingImage the segmentation image * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * @param activeLabelValue The label value of the active label. + * @param silent Indicates if the modification event of the SurfaceInterpolationController should be triggered. * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const std::vector<SliceInformation>& sliceInfos, const Image* workingImage, bool detectIntersection, - mitk::Label::PixelType activeLabelValue); + mitk::Label::PixelType activeLabelValue, bool silent = false); /** * \brief Filters events that cannot be handled by 2D segmentation tools * * Currently an event is discarded if it was not sent by a 2D renderwindow and if it is * not of type InteractionPositionEvent */ bool FilterEvents(InteractionEvent *interactionEvent, DataNode *dataNode) override; /** \brief Extract the slice of the currently selected working image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no working image is selected. */ Image::Pointer GetAffectedWorkingSlice(const InteractionPositionEvent *) const; /** \brief Extract the slice of the currently selected reference image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no reference image is selected. */ Image::Pointer GetAffectedReferenceSlice(const InteractionPositionEvent *) const; /** Overload version that gets the reference slice passed on the passed plane geometry and timestep.*/ Image::Pointer GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const; /** Convenience version that can be called for a given event (which is used to deduce timepoint and plane) and a slice image. * Calls non static WriteBackSegmentationResults*/ void WriteBackSegmentationResult(const InteractionPositionEvent *, const Image* segmentationResult); /** Convenience version that can be called for a given planeGeometry, slice image and time step. * Calls non static WriteBackSegmentationResults*/ void WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image* segmentationResult, TimeStepType timeStep); /** Overloaded version that calls the static version and also adds the contour markers. * @remark If the sliceList is empty, this function does nothing.*/ void WriteBackSegmentationResults(const std::vector<SliceInformation> &sliceList, bool writeSliceToVolume = true); /** \brief Writes all provided source slices into the data of the passed workingNode. * The function does the following: 1) for every passed slice write it to workingNode (and generate and undo/redo step); * 2) update the surface interpolation and 3) mark the node as modified. * @param workingNode Pointer to the node that contains the working image. * @param sliceList Vector of all slices that should be written into the workingNode. If the list is * empty, the function call does nothing. * @param writeSliceToVolume If set to false the write operation (WriteSliceToVolume will be skipped) * and only the surface interpolation will be updated. * @pre workingNode must point to a valid instance and contain an image instance as data.*/ static void WriteBackSegmentationResults(const DataNode* workingNode, const std::vector<SliceInformation>& sliceList, bool writeSliceToVolume = true); /** Writes a provided slice into the passed working image. The content of working image that is covered * by the slice will be completly overwritten. If asked for it also generates the needed * undo/redo steps. * @param workingImage Pointer to the image that is the target of the write operation. * @param sliceInfo SliceInfo instance that containes the slice image, the defining plane geometry and time step. * @param allowUndo Indicates if undo/redo operations should be registered for the write operation * performed by this call. true: undo/redo will be generated; false: no undo/redo will be generated, so * this operation cannot be revoked by the user. * @pre workingImage must point to a valid instance.*/ static void WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo); /** \brief Adds a new node called Contourmarker to the datastorage which holds a mitk::PlanarFigure. By selecting this node the slicestack will be reoriented according to the passed PlanarFigure's Geometry */ int AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex); void InteractiveSegmentationBugMessage(const std::string &message) const; /** Helper function to check if a position events points to a point inside the boundingbox of a passed data instance.*/ static bool IsPositionEventInsideImageRegion(InteractionPositionEvent* positionEvent, const BaseData* data); BaseRenderer *m_LastEventSender = nullptr; unsigned int m_LastEventSlice = 0; itkGetMacro(LastTimePointTriggered, TimePointType); private: /** Internal method that gets triggered as soon as the tool manager indicates a * time point change. If the time point has changed since last time and tool * is set to be time point change aware, OnTimePointChanged() will be called.*/ void OnTimePointChangedInternal(); static void RemoveContourFromInterpolator(const SliceInformation& sliceInfo, LabelSetImage::LabelValueType labelValue); // The prefix of the contourmarkername. Suffix is a consecutive number const std::string m_Contourmarkername; bool m_ShowMarkerNodes = false; static bool m_SurfaceInterpolationEnabled; bool m_IsTimePointChangeAware = true; TimePointType m_LastTimePointTriggered = 0.; }; } // namespace #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 10a63382bb..386399d668 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1505 +1,1485 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSlicesInterpolator.h" #include "QmitkRenderWindow.h" #include "QmitkRenderWindowWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include <mitkTimeNavigationController.h> #include "mitkToolManager.h" #include "mitkUndoController.h" #include <mitkExtractSliceFilter.h> #include <mitkPlanarCircle.h> #include <mitkImageReadAccessor.h> #include <mitkImageTimeSelector.h> #include <mitkImageWriteAccessor.h> #include <mitkPlaneProposer.h> #include <mitkUnstructuredGridClusteringFilter.h> #include <mitkVtkImageOverwrite.h> #include <mitkShapeBasedInterpolationAlgorithm.h> #include <itkCommand.h> #include <mitkImageToContourFilter.h> #include <mitkImagePixelReadAccessor.h> // Includes for the merge operation #include "mitkImageToContourFilter.h" #include <mitkLabelSetImage.h> #include <QCheckBox> #include <QCursor> #include <QMenu> #include <QMessageBox> #include <QPushButton> #include <QVBoxLayout> #include <vtkDoubleArray.h> #include <vtkFieldData.h> #include <vtkPolyVertex.h> #include <vtkUnstructuredGrid.h> #include <vtkPolyData.h> #include <array> #include <atomic> #include <thread> #include <vector> namespace { template <typename T = mitk::BaseData> itk::SmartPointer<T> GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast<T*>(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const QmitkSlicesInterpolator::ActionToSliceDimensionMapType QmitkSlicesInterpolator::CreateActionToSlicer(const QList<QmitkRenderWindow*>& windows) { std::map<QAction *, mitk::SliceNavigationController *> actionToSliceDimension; for (auto* window : windows) { std::string windowName; auto renderWindowWidget = dynamic_cast<QmitkRenderWindowWidget*>(window->parentWidget()); if (renderWindowWidget) { windowName = renderWindowWidget->GetCornerAnnotationText(); } else { windowName = window->GetRenderer()->GetName(); } auto slicer = window->GetSliceNavigationController(); actionToSliceDimension[new QAction(QString::fromStdString(windowName), nullptr)] = slicer; } return actionToSliceDimension; } mitk::Image::Pointer ExtractSliceFromImage(mitk::Image* image, const mitk::PlaneGeometry * contourPlane, unsigned int timeStep) { vtkSmartPointer<mitkVtkImageOverwrite> reslice = vtkSmartPointer<mitkVtkImageOverwrite>::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(contourPlane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Update(); mitk::Image::Pointer slice = extractor->GetOutput(); return slice; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), m_Interpolator(mitk::SegmentationInterpolationController::New()), m_SurfaceInterpolator(mitk::SurfaceInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Initialized(false), m_LastSNC(nullptr), m_LastSliceIndex(0), m_2DInterpolationEnabled(false), m_3DInterpolationEnabled(false), - m_PreviousActiveLabelValue(0), m_CurrentActiveLabelValue(0), - m_PreviousLayerIndex(0), - m_CurrentLayerIndex(0), m_FirstRun(true) { m_GroupBoxEnableExclusiveInterpolationMode = new QGroupBox("Interpolation", this); QVBoxLayout *vboxLayout = new QVBoxLayout(m_GroupBoxEnableExclusiveInterpolationMode); m_EdgeDetector = mitk::FeatureBasedEdgeDetectionFilter::New(); m_PointScorer = mitk::PointCloudScoringFilter::New(); m_CmbInterpolation = new QComboBox(m_GroupBoxEnableExclusiveInterpolationMode); m_CmbInterpolation->addItem("Disabled"); m_CmbInterpolation->addItem("2-Dimensional"); m_CmbInterpolation->addItem("3-Dimensional"); vboxLayout->addWidget(m_CmbInterpolation); m_BtnApply2D = new QPushButton("Confirm for single slice", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply2D); m_BtnApplyForAllSlices2D = new QPushButton("Confirm for all slices", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApplyForAllSlices2D); m_BtnApply3D = new QPushButton("Confirm", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply3D); // T28261 // m_BtnSuggestPlane = new QPushButton("Suggest a plane", m_GroupBoxEnableExclusiveInterpolationMode); // vboxLayout->addWidget(m_BtnSuggestPlane); m_BtnReinit3DInterpolation = new QPushButton("Reinit Interpolation", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnReinit3DInterpolation); m_ChkShowPositionNodes = new QCheckBox("Show Position Nodes", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_ChkShowPositionNodes); this->HideAllInterpolationControls(); connect(m_CmbInterpolation, SIGNAL(currentIndexChanged(int)), this, SLOT(OnInterpolationMethodChanged(int))); connect(m_BtnApply2D, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_BtnApplyForAllSlices2D, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); connect(m_BtnApply3D, SIGNAL(clicked()), this, SLOT(OnAccept3DInterpolationClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand<QmitkSlicesInterpolator>::Pointer command = itk::ReceptorMemberCommand<QmitkSlicesInterpolator>::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand<QmitkSlicesInterpolator>::Pointer command2 = itk::ReceptorMemberCommand<QmitkSlicesInterpolator>::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); auto command3 = itk::ReceptorMemberCommand<QmitkSlicesInterpolator>::New(); command3->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationAborted); InterpolationAbortedObserverTag = m_Interpolator->AddObserver(itk::AbortEvent(), command3); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1<QmitkSlicesInterpolator, const mitk::DataNode*>(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1<QmitkSlicesInterpolator, const mitk::DataNode*>(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } void QmitkSlicesInterpolator::SetActiveLabelValue(mitk::LabelSetImage::LabelValueType labelValue) { bool changedValue = labelValue != this->m_CurrentActiveLabelValue; this->m_CurrentActiveLabelValue = labelValue; if (changedValue) this->OnActiveLabelChanged(labelValue); }; mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::InitializeWindow(QmitkRenderWindow* window) { auto slicer = window->GetSliceNavigationController(); if (slicer == nullptr) { MITK_WARN << "Tried setting up interpolation for a render window that does not have a slice navigation controller set"; return; } // Has to be initialized m_LastSNC = slicer; itk::MemberCommand<QmitkSlicesInterpolator>::Pointer deleteCommand = itk::MemberCommand<QmitkSlicesInterpolator>::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); itk::MemberCommand<QmitkSlicesInterpolator>::Pointer sliceChangedCommand = itk::MemberCommand<QmitkSlicesInterpolator>::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand); } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList<QmitkRenderWindow*>& windows) { Q_ASSERT(!windows.empty()); if (m_Initialized) { // remove old observers this->Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate<QmitkSlicesInterpolator>(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate<QmitkSlicesInterpolator>( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); itk::MemberCommand<QmitkSlicesInterpolator>::Pointer timeChangedCommand = itk::MemberCommand<QmitkSlicesInterpolator>::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag = timeNavigationController->AddObserver(mitk::TimeNavigationController::TimeEvent(0), timeChangedCommand); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); // connect to the slice navigation controller. after each change, call the interpolator for (auto* window : windows) { this->InitializeWindow(window); } m_ActionToSlicerMap = CreateActionToSlicer(windows); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate<QmitkSlicesInterpolator>(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate<QmitkSlicesInterpolator>( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); timeNavigationController->RemoveObserver(m_ControllerToTimeObserverTag); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } m_ActionToSlicerMap.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers this->Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1<QmitkSlicesInterpolator, const mitk::DataNode*>(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); } // remove observer m_Interpolator->RemoveObserver(InterpolationAbortedObserverTag); m_Interpolator->RemoveObserver(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); m_SurfaceInterpolator->SetCurrentInterpolationSession(nullptr); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); // T28261 // m_BtnSuggestPlane->setVisible(show); m_ChkShowPositionNodes->setVisible(show); m_BtnReinit3DInterpolation->setVisible(show); } void QmitkSlicesInterpolator::OnInterpolationMethodChanged(int index) { switch (index) { case 0: // Disabled m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation"); this->HideAllInterpolationControls(); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(false); break; case 1: // 2D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show2DInterpolationControls(true); this->OnInterpolationActivated(true); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast<mitk::Image *>(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); m_CmbInterpolation->setCurrentIndex(0); return; } // Updating the current selected segmentation for the 3D interpolation this->SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast<const mitk::TimeNavigationController::TimeEvent*>(&e)) { return; } const auto* timeNavigationController = dynamic_cast<mitk::TimeNavigationController*>(sender); if (nullptr == timeNavigationController) { return; } + bool timeChanged = m_TimePoint != timeNavigationController->GetSelectedTimePoint(); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); - if (m_TimePoint != m_SurfaceInterpolator->GetCurrentTimePoint()) + if (timeChanged) { - m_SurfaceInterpolator->SetCurrentTimePoint(m_TimePoint); if (m_3DInterpolationEnabled) { m_InterpolatedSurfaceNode->SetData(nullptr); } m_SurfaceInterpolator->Modified(); } if (nullptr == m_LastSNC) { return; } if (TranslateAndInterpolateChangedSlice(m_LastSNC->GetCreatedWorldGeometry())) { m_LastSNC->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast<const mitk::SliceNavigationController::GeometrySliceEvent*>(&e)) { return; } auto sliceNavigationController = dynamic_cast<mitk::SliceNavigationController*>(sender); if (nullptr == sliceNavigationController) { return; } if(m_2DInterpolationEnabled) { this->On2DInterpolationEnabled(m_2DInterpolationEnabled); } if (TranslateAndInterpolateChangedSlice(e, sliceNavigationController)) { sliceNavigationController->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject& e, mitk::SliceNavigationController* sliceNavigationController) { const mitk::SliceNavigationController::GeometrySliceEvent* event = dynamic_cast<const mitk::SliceNavigationController::GeometrySliceEvent*>(&e); mitk::TimeGeometry* timeGeometry = event->GetTimeGeometry(); m_LastSNC = sliceNavigationController; return this->TranslateAndInterpolateChangedSlice(timeGeometry); } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const mitk::TimeGeometry* timeGeometry) { if (!m_2DInterpolationEnabled) { return false; } if (nullptr == timeGeometry) { return false; } if (!timeGeometry->IsValidTimePoint(m_TimePoint)) { return false; } mitk::SlicedGeometry3D* slicedGeometry = dynamic_cast<mitk::SlicedGeometry3D*>(timeGeometry->GetGeometryForTimePoint(m_TimePoint).GetPointer()); if (nullptr == slicedGeometry) { return false; } mitk::PlaneGeometry* plane = dynamic_cast<mitk::PlaneGeometry*>(slicedGeometry->GetPlaneGeometry(m_LastSNC->GetStepper()->GetPos())); if (nullptr == plane) { return false; } this->Interpolate(plane); return true; } void QmitkSlicesInterpolator::Interpolate(mitk::PlaneGeometry *plane) { if (nullptr == m_ToolManager) { return; } mitk::DataNode* node = m_ToolManager->GetWorkingData(0); if (nullptr == node) { return; } m_Segmentation = dynamic_cast<mitk::Image*>(node->GetData()); if (nullptr == m_Segmentation) { return; } if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot interpolate WorkingImage. Passed time point is not within the time bounds of WorkingImage. " "Time point: " << m_TimePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); int clickedSliceDimension = -1; int clickedSliceIndex = -1; // calculate real slice position, i.e. slice of the image mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); // maybe just have a variable that stores the active label color. if (m_ToolManager) { auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { auto* activeLabel = dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData())->GetActiveLabel(); if (nullptr != activeLabel) { auto activeColor = activeLabel->GetColor(); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } } } m_LastSliceIndex = clickedSliceIndex; } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { - mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(); +// mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::PlaneGeometry::Pointer slicingPlane = mitk::PlaneGeometry::New(); mitk::Vector3D slicingPlaneNormalVector; FillVector3D(slicingPlaneNormalVector,0.0,1.0,0.0); mitk::Point3D origin; FillVector3D(origin, 0.0, 0.0, 0.0); slicingPlane->InitializePlane(origin, slicingPlaneNormalVector); - if (interpolatedSurface.IsNotNull() && workingNode) + if (m_LastInterpolatedSurface.IsNotNull() && workingNode) { m_BtnApply3D->setEnabled(true); // T28261 // m_BtnSuggestPlane->setEnabled(true); - m_InterpolatedSurfaceNode->SetData(interpolatedSurface); + m_InterpolatedSurfaceNode->SetData(m_LastInterpolatedSurface); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } } - else if (interpolatedSurface.IsNull()) + else if (m_LastInterpolatedSurface.IsNull()) { m_BtnApply3D->setEnabled(false); // T28261 // m_BtnSuggestPlane->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } m_BtnReinit3DInterpolation->setEnabled(true); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } - m_SurfaceInterpolator->ReinitializeInterpolation(); } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { auto* workingNode = m_ToolManager->GetWorkingData(0); auto* planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); auto* interpolatedPreview = dynamic_cast<mitk::Image*>(m_FeedbackNode->GetData()); if (nullptr == workingNode || nullptr == interpolatedPreview) return; auto* segmentationImage = dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData()); if (nullptr == segmentationImage) return; if (!segmentationImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the " "time bounds of segmentation. Time point: " << m_TimePoint; return; } const auto timeStep = segmentationImage->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); auto interpolatedSlice = mitk::SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, segmentationImage, timeStep)->Clone(); auto activeValue = segmentationImage->GetActiveLabel()->GetValue(); mitk::TransferLabelContentAtTimeStep( interpolatedPreview, interpolatedSlice, segmentationImage->GetConstLabelsByValue(segmentationImage->GetLabelValuesByGroup(segmentationImage->GetActiveLayer())), timeStep, 0, mitk::LabelSetImage::UnlabeledValue, false, { {0, mitk::LabelSetImage::UnlabeledValue}, {1, activeValue} } ); mitk::SegTool2D::WriteBackSegmentationResult(workingNode, planeGeometry, interpolatedSlice, timeStep); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { mitk::Image::Pointer segmentation3D = m_Segmentation; unsigned int timeStep = 0; if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not " "within the time bounds of segmentation. Time point: " << m_TimePoint; return; } mitk::Image::Pointer activeLabelImage; try { auto labelSetImage = dynamic_cast<mitk::LabelSetImage *>(m_Segmentation); activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabel()->GetValue()); } catch (const std::exception& e) { MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; } m_Interpolator->SetSegmentationVolume(activeLabelImage); timeStep = geometry->TimePointToTimeStep(m_TimePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // Create an empty diff image for the undo operation auto diffImage = mitk::Image::New(); diffImage->Initialize(segmentation3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType<mitk::Tool::DefaultSegmentationDataType>(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::IOComponentEnum::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType<unsigned char>(); memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension = -1; int sliceIndex = -1; mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); const auto numSlices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); std::atomic_uint totalChangedSlices; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); std::vector<std::vector<unsigned int>> sliceIndices(numThreads); for (std::remove_const_t<decltype(numSlices)> sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); std::vector<std::thread> threads; threads.reserve(numThreads); // This lambda will be executed by the threads auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { auto clonedPlaneGeometry = planeGeometry->Clone(); auto origin = clonedPlaneGeometry->GetOrigin(); // Go through the sliced indices for (auto sliceIndex : sliceIndices[threadIndex]) { slicedGeometry->WorldToIndex(origin, origin); origin[sliceDimension] = sliceIndex; slicedGeometry->IndexToWorld(origin, origin); clonedPlaneGeometry->SetOrigin(origin); auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); if (interpolation.IsNotNull()) { // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume auto reslicer = vtkSmartPointer<mitkVtkImageOverwrite>::New(); // Set overwrite mode to true to write back to the image volume reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); reslicer->SetOverwriteMode(true); reslicer->Modified(); auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); diffSliceWriter->SetInput(diffImage); diffSliceWriter->SetTimeStep(0); diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); diffSliceWriter->SetVtkOutputRequest(true); diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); diffSliceWriter->Modified(); diffSliceWriter->Update(); ++totalChangedSlices; } mitk::ProgressBar::GetInstance()->Progress(); } }; m_Interpolator->EnableSliceImageCache(); // Do the interpolation here. for (size_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) { interpolate(threadIndex); } m_Interpolator->DisableSliceImageCache(); const mitk::Label::PixelType newDestinationLabel = dynamic_cast<mitk::LabelSetImage *>(m_Segmentation)->GetActiveLabel()->GetValue(); // Do and Undo Operations if (totalChangedSlices > 0) { // Create do/undo operations auto* doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); auto* undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); undoOp->SetFactor(-1.0); auto comment = "Confirm all interpolations (" + std::to_string(totalChangedSlices) + ")"; auto* undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment); mitk::OperationEvent::IncCurrGroupEventId(); mitk::OperationEvent::IncCurrObjectEventId(); mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); mitk::DiffImageApplier::GetInstanceForUndo()->SetDestinationLabel(newDestinationLabel); // Apply the changes to the original image mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); for (auto it = m_ActionToSlicerMap.begin(); it != m_ActionToSlicerMap.end(); ++it) { orientationPopup.addAction(it->first); } connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData<mitk::Image>(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto labelSetImage = dynamic_cast<mitk::LabelSetImage*>(segmentationDataNode->GetData()); auto activeLabelColor = labelSetImage->GetActiveLabel()->GetColor(); std::string activeLabelName = labelSetImage->GetActiveLabel()->GetName(); auto segmentation = GetData<mitk::Image>(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint) || !segmentationGeometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData<mitk::Surface>(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::IOComponentEnum::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); auto timeStep = segmentationGeometry->TimePointToTimeStep(m_TimePoint); const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabel()->GetValue(); TransferLabelContentAtTimeStep( interpolatedSegmentation, labelSetImage, labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(labelSetImage->GetActiveLayer())), timeStep, 0, 0, false, {{1, newDestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + " 3D-interpolation - " + activeLabelName; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast<mitk::ProportionalTimeGeometry*>(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); interpolatedSurfaceDataNode->SetColor(activeLabelColor); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { // Step 1. Load from the isContourPlaneGeometry nodes the contourNodes. mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto labelSetImage = dynamic_cast<mitk::LabelSetImage *>(m_ToolManager->GetWorkingData(0)->GetData()); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } mitk::SurfaceInterpolationController::CPIVector newCPIs; // Adding label and timeStep information for the contourNodes. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto contourNode = it->Value(); auto labelID = dynamic_cast<mitk::UShortProperty *>(contourNode->GetProperty("labelID"))->GetValue(); auto timeStep = dynamic_cast<mitk::IntProperty *>(contourNode->GetProperty("timeStep"))->GetValue(); auto planeGeometry = dynamic_cast<mitk::PlanarFigure *>(contourNode->GetData())->GetPlaneGeometry(); auto groupID = labelSetImage->GetGroupIndexOfLabel(labelID); auto sliceImage = ExtractSliceFromImage(labelSetImage->GetGroupImage(groupID), planeGeometry, timeStep); mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); contourExtractor->SetInput(sliceImage); contourExtractor->SetContourValue(labelID); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) continue; contour->DisconnectPipeline(); newCPIs.emplace_back(contour, planeGeometry->Clone(),labelID,timeStep); } m_SurfaceInterpolator->CompleteReinitialization(newCPIs); } catch(const std::exception& e) { MITK_ERROR << "Exception thrown casting toolmanager working data to labelsetImage"; } } } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { auto iter = m_ActionToSlicerMap.find(action); if (iter != m_ActionToSlicerMap.end()) { mitk::SliceNavigationController *slicer = iter->second; this->AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); std::cerr << "Ill construction in " __FILE__ " l. " << __LINE__ << std::endl; } } void QmitkSlicesInterpolator::OnInterpolationActivated(bool on) { m_2DInterpolationEnabled = on; try { if (m_DataStorage.IsNotNull()) { if (on && !m_DataStorage->Exists(m_FeedbackNode)) { m_DataStorage->Add(m_FeedbackNode); } } } catch (...) { // don't care (double add/remove) } if (m_ToolManager) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); QWidget::setEnabled(workingNode != nullptr); m_BtnApply2D->setEnabled(on); m_FeedbackNode->SetVisibility(on); if (!on) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } if (workingNode) { auto labelSetImage = dynamic_cast<mitk::LabelSetImage *>(workingNode->GetData()); if (nullptr == labelSetImage) { MITK_ERROR << "NO LABELSETIMAGE IN WORKING NODE\n"; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } const auto* activeLabel = labelSetImage->GetActiveLabel(); const auto* segmentation = dynamic_cast<mitk::Image*>(workingNode->GetData()); if (nullptr != activeLabel && nullptr != segmentation) { auto activeLabelImage = labelSetImage->CreateLabelMask(activeLabel->GetValue()); m_Interpolator->SetSegmentationVolume(activeLabelImage); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast<mitk::Image *>(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { - m_SurfaceInterpolator->Interpolate(); + auto workingNode = m_ToolManager->GetWorkingData(0); + + if (workingNode == nullptr) + { + MITK_ERROR << "Run3DInterpolation triggered with no working data set."; + return; + } + + const auto segmentation = dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData()); + + if (segmentation == nullptr) + { + MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; + return; + } + + m_LastInterpolatedSurface = m_SurfaceInterpolator->Interpolate(segmentation,m_CurrentActiveLabelValue,segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { if(m_ToolManager) { const auto* workingNode = m_ToolManager->GetWorkingData(0); const auto activeColor = dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData())->GetActiveLabel()->GetColor(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } m_Timer->stop(); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_3DWINDOWS); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; try { // this->PrepareInputsFor3DInterpolation(); m_SurfaceInterpolator->Modified(); } catch (...) { MITK_ERROR << "Error with 3D surface interpolation!"; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::EnableInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated OnInterpolationActivated(on); } void QmitkSlicesInterpolator::Enable3DInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated this->On3DInterpolationActivated(on); } void QmitkSlicesInterpolator::UpdateVisibleSuggestion() { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::OnInterpolationAborted(const itk::EventObject& /*e*/) { m_CmbInterpolation->setCurrentIndex(0); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_InterpolatedSurfaceNode->SetData(nullptr); - auto* workingNode = m_ToolManager->GetWorkingData(0); - - if (workingNode == nullptr) - return; - - const auto* labelSetImage = dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData()); - const auto* label = labelSetImage->GetActiveLabel(); - - if (label == nullptr) - return; - - m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(label->GetValue()); + m_LastInterpolatedSurface = nullptr; m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { QWidget::setEnabled(true); // In case the time is not valid use 0 to access the time geometry of the working node unsigned int time_position = 0; if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << m_TimePoint; return; } - // Sets up the surface interpolator to accept - time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); - - mitk::Vector3D spacing = workingNode->GetData()->GetGeometry(time_position)->GetSpacing(); - double minSpacing = 100; - double maxSpacing = 0; - for (int i = 0; i < 3; i++) - { - if (spacing[i] < minSpacing) - { - minSpacing = spacing[i]; - } - if (spacing[i] > maxSpacing) - { - maxSpacing = spacing[i]; - } - } - - m_SurfaceInterpolator->SetMaxSpacing(maxSpacing); - m_SurfaceInterpolator->SetMinSpacing(minSpacing); m_SurfaceInterpolator->SetDistanceImageVolume(50000); auto segmentationImage = dynamic_cast<mitk::LabelSetImage *>(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); - m_SurfaceInterpolator->SetCurrentTimePoint(m_TimePoint); } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnActiveLabelChanged(mitk::Label::PixelType) { m_FeedbackNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_SurfaceInterpolator->Modified(); } if (m_2DInterpolationEnabled) { m_FeedbackNode->SetData(nullptr); this->OnInterpolationActivated(true); m_LastSNC->SendSlice(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) { m_Segmentation = dynamic_cast<mitk::Image *>(m_ToolManager->GetWorkingData(0)->GetData()); if (m_3DInterpolationEnabled && m_Segmentation && ((m_Segmentation->GetDimension() != 3) || (m_Segmentation->GetDimension() != 4)) ) { QMessageBox info; info.setWindowTitle("3D Interpolation Process"); info.setIcon(QMessageBox::Information); info.setText("3D Interpolation is only supported for 3D/4D images at the moment!"); info.exec(); m_CmbInterpolation->setCurrentIndex(0); } } } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast<mitk::SliceNavigationController *>(const_cast<itk::Object *>(sender)); if (slicer) { m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSlicesInterpolator::WaitForFutures() { if (m_Watcher.isRunning()) { m_Watcher.waitForFinished(); } if (m_PlaneWatcher.isRunning()) { m_PlaneWatcher.waitForFinished(); } } void QmitkSlicesInterpolator::NodeRemoved(const mitk::DataNode* node) { if ((m_ToolManager && m_ToolManager->GetWorkingData(0) == node) || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h index c14e6d823e..67a018d336 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h @@ -1,350 +1,348 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSlicesInterpolator_h #define QmitkSlicesInterpolator_h #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkSegmentationInterpolationController.h" #include "mitkSurfaceInterpolationController.h" #include "mitkToolManager.h" #include <MitkSegmentationUIExports.h> #include "mitkFeatureBasedEdgeDetectionFilter.h" #include "mitkPointCloudScoringFilter.h" #include <QWidget> #include <map> #include <QCheckBox> #include <QComboBox> #include <QFrame> #include <QGroupBox> #include <QRadioButton> #include "mitkVtkRepresentationProperty.h" #include "vtkProperty.h" // For running 3D interpolation in background #include <QFuture> #include <QFutureWatcher> #include <QTimer> #include <QtConcurrentRun> namespace mitk { class PlaneGeometry; class SliceNavigationController; class TimeNavigationController; } class QPushButton; class QmitkRenderWindow; /** \brief GUI for slices interpolation. \ingroup ToolManagerEtAl \ingroup Widgets \sa QmitkInteractiveSegmentation \sa mitk::SegmentationInterpolation While mitk::SegmentationInterpolation does the bookkeeping of interpolation (keeping track of which slices contain how much segmentation) and the algorithmic work, QmitkSlicesInterpolator is responsible to watch the GUI, to notice, which slice is currently visible. It triggers generation of interpolation suggestions and also triggers acception of suggestions. */ class MITKSEGMENTATIONUI_EXPORT QmitkSlicesInterpolator : public QWidget { Q_OBJECT public: QmitkSlicesInterpolator(QWidget *parent = nullptr, const char *name = nullptr); /** To be called once before real use. */ void Initialize(mitk::ToolManager *toolManager, const QList<QmitkRenderWindow*>& windows); /** * @brief * */ void Uninitialize(); ~QmitkSlicesInterpolator() override; /** * @brief Set the Data Storage object * * @param storage */ void SetDataStorage(mitk::DataStorage::Pointer storage); /** * @brief Get the Data Storage object * * @return mitk::DataStorage* */ mitk::DataStorage *GetDataStorage(); void SetActiveLabelValue(mitk::LabelSetImage::LabelValueType labelValue); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerWorkingDataModified(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerReferenceDataModified(); /** * @brief Reacts to the time changed event. * * @param sender */ void OnTimeChanged(itk::Object *sender, const itk::EventObject &); /** * @brief Reacts to the slice changed event * * @param sender */ void OnSliceChanged(itk::Object *sender, const itk::EventObject &); void OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationInfoChanged(const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationAborted(const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnSurfaceInterpolationInfoChanged(const itk::EventObject &); private: /** * @brief Set the visibility of the 3d interpolation */ void Show3DInterpolationResult(bool); /** * @brief Function that reacts to a change in the activeLabel of the working segmentation image. * */ void OnActiveLabelChanged(mitk::Label::PixelType); signals: void SignalRememberContourPositions(bool); void SignalShowMarkerNodes(bool); public slots: virtual void setEnabled(bool); /** Call this from the outside to enable/disable interpolation */ void EnableInterpolation(bool); void Enable3DInterpolation(bool); /** Call this from the outside to accept all interpolations */ void FinishInterpolation(mitk::SliceNavigationController *slicer = nullptr); protected slots: /** Reaction to button clicks. */ void OnAcceptInterpolationClicked(); /* Opens popup to ask about which orientation should be interpolated */ void OnAcceptAllInterpolationsClicked(); /* Reaction to button clicks */ void OnAccept3DInterpolationClicked(); /** * @brief Reaction to reinit 3D Interpolation. Re-reads the plane geometries of the image * that should have generated the * */ void OnReinit3DInterpolation(); /* * Will trigger interpolation for all slices in given orientation (called from popup menu of * OnAcceptAllInterpolationsClicked) */ void OnAcceptAllPopupActivated(QAction *action); /** Called on activation/deactivation */ void OnInterpolationActivated(bool); void On3DInterpolationActivated(bool); void OnInterpolationMethodChanged(int index); // Enhancement for 3D interpolation void On2DInterpolationEnabled(bool); void On3DInterpolationEnabled(bool); void OnInterpolationDisabled(bool); void OnShowMarkers(bool); void Run3DInterpolation(); /** * @brief Function triggers when the surface interpolation thread completes running. * It is responsible for retrieving the data, rendering it in the active color label, * storing the surface information in the feedback node. * */ void OnSurfaceInterpolationFinished(); void StartUpdateInterpolationTimer(); void StopUpdateInterpolationTimer(); void ChangeSurfaceColor(); protected: typedef std::map<QAction*, mitk::SliceNavigationController*> ActionToSliceDimensionMapType; const ActionToSliceDimensionMapType CreateActionToSlicer(const QList<QmitkRenderWindow*>& windows); ActionToSliceDimensionMapType m_ActionToSlicerMap; void AcceptAllInterpolations(mitk::SliceNavigationController *slicer); /** Retrieves the currently selected PlaneGeometry from a SlicedGeometry3D that is generated by a SliceNavigationController and calls Interpolate to further process this PlaneGeometry into an interpolation. \param e is a actually a mitk::SliceNavigationController::GeometrySliceEvent, sent by a SliceNavigationController \param sliceNavigationController the SliceNavigationController */ bool TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *sliceNavigationController); bool TranslateAndInterpolateChangedSlice(const mitk::TimeGeometry* timeGeometry); /** Given a PlaneGeometry, this method figures out which slice of the first working image (of the associated ToolManager) should be interpolated. The actual work is then done by our SegmentationInterpolation object. */ void Interpolate(mitk::PlaneGeometry *plane); /** Called internally to update the interpolation suggestion. Finds out about the focused render window and requests an interpolation. */ void UpdateVisibleSuggestion(); void SetCurrentContourListID(); private: void InitializeWindow(QmitkRenderWindow* window); void HideAllInterpolationControls(); void Show2DInterpolationControls(bool show); void Show3DInterpolationControls(bool show); void CheckSupportedImageDimension(); void WaitForFutures(); void NodeRemoved(const mitk::DataNode* node); mitk::SegmentationInterpolationController::Pointer m_Interpolator; mitk::SurfaceInterpolationController::Pointer m_SurfaceInterpolator; mitk::FeatureBasedEdgeDetectionFilter::Pointer m_EdgeDetector; mitk::PointCloudScoringFilter::Pointer m_PointScorer; mitk::ToolManager::Pointer m_ToolManager; bool m_Initialized; unsigned int m_ControllerToTimeObserverTag; QHash<mitk::SliceNavigationController *, int> m_ControllerToSliceObserverTag; QHash<mitk::SliceNavigationController *, int> m_ControllerToDeleteObserverTag; unsigned int InterpolationInfoChangedObserverTag; unsigned int SurfaceInterpolationInfoChangedObserverTag; unsigned int InterpolationAbortedObserverTag; QGroupBox *m_GroupBoxEnableExclusiveInterpolationMode; QComboBox *m_CmbInterpolation; QPushButton *m_BtnApply2D; QPushButton *m_BtnApplyForAllSlices2D; QPushButton *m_BtnApply3D; // T28261 // QPushButton *m_BtnSuggestPlane; QCheckBox *m_ChkShowPositionNodes; QPushButton *m_BtnReinit3DInterpolation; mitk::DataNode::Pointer m_FeedbackNode; mitk::DataNode::Pointer m_InterpolatedSurfaceNode; + mitk::Surface::Pointer m_LastInterpolatedSurface; mitk::Image *m_Segmentation; mitk::SliceNavigationController *m_LastSNC; unsigned int m_LastSliceIndex; mitk::TimePointType m_TimePoint; bool m_2DInterpolationEnabled; bool m_3DInterpolationEnabled; unsigned int m_numTimesLabelSetConnectionAdded; mitk::DataStorage::Pointer m_DataStorage; QFuture<void> m_Future; QFutureWatcher<void> m_Watcher; QFuture<void> m_ModifyFuture; QFutureWatcher<void> m_ModifyWatcher; QTimer *m_Timer; QFuture<void> m_PlaneFuture; QFutureWatcher<void> m_PlaneWatcher; - mitk::Label::PixelType m_PreviousActiveLabelValue; mitk::Label::PixelType m_CurrentActiveLabelValue; - unsigned int m_PreviousLayerIndex; - unsigned int m_CurrentLayerIndex; bool m_FirstRun; }; #endif diff --git a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp index 2e375d4131..3f3a6bb78d 100644 --- a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp +++ b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp @@ -1,610 +1,608 @@ /*============================================================================ 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 <mitkSurfaceInterpolationController.h> #include <mitkTestFixture.h> #include <mitkTestingMacros.h> #include "mitkImageAccessByItk.h" #include <mitkImageTimeSelector.h> #include <mitkLabelSetImage.h> #include <vtkDebugLeaks.h> #include <vtkDoubleArray.h> #include <vtkFieldData.h> #include <vtkPolygon.h> #include <vtkRegularPolygonSource.h> template <typename ImageType> void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } class mitkSurfaceInterpolationControllerTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkSurfaceInterpolationControllerTestSuite); MITK_TEST(TestSingleton); MITK_TEST(TestSetCurrentInterpolationSession); MITK_TEST(TestRemoveAllInterpolationSessions); MITK_TEST(TestRemoveInterpolationSession); MITK_TEST(TestOnSegmentationDeleted); MITK_TEST(TestSetCurrentInterpolationSession4D); MITK_TEST(TestRemoveAllInterpolationSessions4D); MITK_TEST(TestRemoveInterpolationSession4D); MITK_TEST(TestOnSegmentationDeleted4D); /// \todo Workaround for memory leak in TestAddNewContour. Bug 18096. vtkDebugLeaks::SetExitError(0); MITK_TEST(TestAddNewContours); MITK_TEST(TestRemoveContours); CPPUNIT_TEST_SUITE_END(); private: mitk::SurfaceInterpolationController::Pointer m_Controller; public: mitk::Image::Pointer createImage(unsigned int *dimensions) { mitk::Image::Pointer newImage = mitk::Image::New(); // mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); mitk::PixelType p_type = mitk::MakeScalarPixelType<unsigned char>(); newImage->Initialize(p_type, 3, dimensions); AccessFixedDimensionByItk(newImage, ClearBufferProcessing, 3); return newImage; } mitk::LabelSetImage::Pointer createLabelSetImage(unsigned int *dimensions) { mitk::Image::Pointer image = createImage(dimensions); mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); newImage->InitializeByLabeledImage(image); return newImage; } mitk::Image::Pointer createImage4D(unsigned int *dimensions) { mitk::Image::Pointer newImage = mitk::Image::New(); mitk::PixelType p_type = mitk::MakeScalarPixelType<unsigned char>(); newImage->Initialize(p_type, 4, dimensions); return newImage; } mitk::LabelSetImage::Pointer createLabelSetImage4D(unsigned int *dimensions) { mitk::Image::Pointer image = createImage4D(dimensions); mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); newImage->InitializeByLabeledImage(image); return newImage; } void setUp() override { m_Controller = mitk::SurfaceInterpolationController::GetInstance(); m_Controller->RemoveAllInterpolationSessions(); - m_Controller->SetCurrentTimePoint(0); vtkSmartPointer<vtkRegularPolygonSource> polygonSource = vtkSmartPointer<vtkRegularPolygonSource>::New(); polygonSource->SetRadius(100); polygonSource->SetNumberOfSides(7); polygonSource->Update(); mitk::Surface::Pointer surface = mitk::Surface::New(); surface->SetVtkPolyData(polygonSource->GetOutput()); } void TestSingleton() { mitk::SurfaceInterpolationController::Pointer controller2 = mitk::SurfaceInterpolationController::GetInstance(); CPPUNIT_ASSERT_MESSAGE("SurfaceInterpolationController pointers are not equal!", m_Controller.GetPointer() == controller2.GetPointer()); } void TestSetCurrentInterpolationSession() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); // Test 2 m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 3 m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 4 m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == nullptr); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } mitk::PlaneGeometry::Pointer CreatePlaneGeometry(mitk::ScalarType zIndex) { mitk::Point3D origin; mitk::Vector3D right, bottom, spacing; mitk::ScalarType widthInMM, heightInMM, thicknessInMM; auto planegeometry = mitk::PlaneGeometry::New(); widthInMM = 100; heightInMM = 200; thicknessInMM = 1.0; mitk::FillVector3D(origin, 4.5, 7.3, zIndex*thicknessInMM); mitk::FillVector3D(right, widthInMM, 0, 0); mitk::FillVector3D(bottom, 0, heightInMM, 0); mitk::FillVector3D(spacing, 1.0, 1.0, thicknessInMM); planegeometry->InitializeStandardPlane(right, bottom); planegeometry->SetOrigin(origin); planegeometry->SetSpacing(spacing); return planegeometry; } mitk::Surface::Pointer CreateContour(int numOfSides) { double center_1[3] = { 1.25f, 3.43f, 4.44f }; double normal_1[3] = { 0.25f, 1.76f, 0.93f }; vtkSmartPointer<vtkRegularPolygonSource> p_source = vtkSmartPointer<vtkRegularPolygonSource>::New(); p_source->SetNumberOfSides(numOfSides*10); p_source->SetCenter(center_1); p_source->SetRadius(4); p_source->SetNormal(normal_1); p_source->Update(); vtkPolyData* poly_1 = p_source->GetOutput(); mitk::Surface::Pointer surf_1 = mitk::Surface::New(); surf_1->SetVtkPolyData(poly_1); return surf_1; } void TestRemoveAllInterpolationSessions() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; auto segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; auto segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); m_Controller->RemoveAllInterpolationSessions(); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestRemoveInterpolationSession() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", m_Controller->GetCurrentSegmentation() != nullptr); m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Current segmentation is not null after session was removed", m_Controller->GetCurrentSegmentation() == nullptr); } void TestOnSegmentationDeleted() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; auto segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); segmentation_1 = nullptr; CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestAddNewContours() { // Create segmentation image unsigned int dimensions1[] = {10, 10, 10}; auto segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); auto surf_1 = CreateContour(3); auto surf_2 = CreateContour(3); auto surf_3 = CreateContour(3); auto surf_3New = CreateContour(3); auto planeGeometry1 = CreatePlaneGeometry(1); auto planeGeometry2 = CreatePlaneGeometry(2); auto planeGeometry3 = CreatePlaneGeometry(3); mitk::SurfaceInterpolationController::CPIVector cpis= { {surf_1, planeGeometry1, 1, 0}, {surf_2, planeGeometry2, 1, 0}, {surf_3, planeGeometry3, 1, 0}, {surf_3New, planeGeometry3, 1, 0}, {surf_1, planeGeometry1, 1, 1}, {surf_1, planeGeometry1, 2, 0}, {surf_2, planeGeometry3, 2, 0} }; // Add contours m_Controller->AddNewContours(cpis, true); // Check if all contours are there auto contours = m_Controller->GetContours(1, 0); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_3New->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(1, 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(2, 0); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 2); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(2, 1); CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); contours = m_Controller->GetContours(3, 0); CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); // Create another segmentation image unsigned int dimensions2[] = {20, 20, 20}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); m_Controller->SetCurrentInterpolationSession(segmentation_2); auto planeGeometry4 = CreatePlaneGeometry(4); auto planeGeometry5 = CreatePlaneGeometry(5); auto planeGeometry6 = CreatePlaneGeometry(6); auto surf_4 = CreateContour(4); auto surf_5 = CreateContour(5); auto surf_6 = CreateContour(6); mitk::SurfaceInterpolationController::CPIVector cpis2 = { {surf_1, planeGeometry1, 1, 0}, {surf_2, planeGeometry2, 1, 0}, {surf_4, planeGeometry4, 1, 0}, {surf_5, planeGeometry5, 1, 1}, {surf_6, planeGeometry6, 2, 0} }; contours = m_Controller->GetContours(1, 0); CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); m_Controller->AddNewContours(cpis2); // Check if all contours are there contours = m_Controller->GetContours(1, 0); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_4->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(1, 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_5->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(2, 0); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_6->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(2, 1); CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); contours = m_Controller->GetContours(3, 0); CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); // Check if all contours of segmentation_1 are still there m_Controller->SetCurrentInterpolationSession(segmentation_1); contours = m_Controller->GetContours(1, 0); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_3New->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(1, 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(2, 0); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 2); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); contours = m_Controller->GetContours(2, 1); CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); contours = m_Controller->GetContours(3, 0); CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); } void TestRemoveContours() { // Create segmentation image unsigned int dimensions1[] = {12, 12, 12}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); auto surf_1 = CreateContour(3); auto surf_2 = CreateContour(3); auto surf_3 = CreateContour(3); auto planeGeometry1 = CreatePlaneGeometry(1); auto planeGeometry2 = CreatePlaneGeometry(2); auto planeGeometry3 = CreatePlaneGeometry(3); mitk::SurfaceInterpolationController::CPIVector cpis = { {surf_1, planeGeometry1, 1, 0}, {surf_2, planeGeometry2, 1, 1}, {surf_3, planeGeometry3, 1, 2}, {surf_1, planeGeometry1, 2, 0}, {surf_2, planeGeometry3, 2, 0} }; m_Controller->AddNewContours(cpis); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); //Remove unkown label m_Controller->RemoveContours(9); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); //Remove unkown time step m_Controller->RemoveContours(1,3); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); m_Controller->RemoveContours(1, 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1) == nullptr); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); m_Controller->RemoveContours(1); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0) == nullptr); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1) == nullptr); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2) == nullptr); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); m_Controller->RemoveContours(2); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0) == nullptr); } bool AssertImagesEqual4D(mitk::LabelSetImage *img1, mitk::LabelSetImage *img2) { auto selector1 = mitk::ImageTimeSelector::New(); selector1->SetInput(img1); selector1->SetChannelNr(0); auto selector2 = mitk::ImageTimeSelector::New(); selector2->SetInput(img2); selector2->SetChannelNr(0); int numTs1 = img1->GetTimeSteps(); int numTs2 = img2->GetTimeSteps(); if (numTs1 != numTs2) { return false; } /*mitk::ImagePixelWriteAccessor<unsigned char,4> accessor( img1 ); itk::Index<4> ind; ind[0] = 5; ind[1] = 5; ind[2] = 5; ind[3] = 2; accessor.SetPixelByIndex( ind, 7 );*/ for (int ts = 0; ts < numTs1; ++ts) { selector1->SetTimeNr(ts); selector2->SetTimeNr(ts); selector1->Update(); selector2->Update(); mitk::Image::Pointer imgSel1 = selector1->GetOutput(); mitk::Image::Pointer imgSel2 = selector2->GetOutput(); MITK_ASSERT_EQUAL(imgSel1, imgSel2, "Segmentation images are not equal"); } return true; } void TestSetCurrentInterpolationSession4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 5}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); // mitk::Image * segmentationImage_1 = dynamic_cast<mitk::Image *>(segmentation_1.GetPointer()); unsigned int dimensions2[] = {20, 10, 30, 4}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); auto currentSegmentation = dynamic_cast<mitk::LabelSetImage *>(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); // Test 2 m_Controller->SetCurrentInterpolationSession(segmentation_2); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_2->Clone(), "Segmentation images are not // equal"); currentSegmentation = dynamic_cast<mitk::LabelSetImage *>(m_Controller->GetCurrentSegmentation()); // AssertImagesEqual4D(currentSegmentation, segmentation_2->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", currentSegmentation == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 3 m_Controller->SetCurrentInterpolationSession(segmentation_1); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not // equal"); currentSegmentation = dynamic_cast<mitk::LabelSetImage *>(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 4 m_Controller->SetCurrentInterpolationSession(segmentation_1); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not // equal"); currentSegmentation = dynamic_cast<mitk::LabelSetImage *>(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation()==nullptr); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } void TestRemoveAllInterpolationSessions4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 4}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 5}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); m_Controller->RemoveAllInterpolationSessions(); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestRemoveInterpolationSession4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 3}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 6}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", m_Controller->GetCurrentSegmentation()!=nullptr); m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Current segmentation is not null after session was removed", m_Controller->GetCurrentSegmentation()==nullptr); } void TestOnSegmentationDeleted4D() { { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 7}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); - m_Controller->SetCurrentTimePoint(3); } CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } }; MITK_TEST_SUITE_REGISTRATION(mitkSurfaceInterpolationController) diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp index 3e2b963371..d05cdae90d 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp @@ -1,824 +1,769 @@ /*============================================================================ 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 <mitkSurfaceInterpolationController.h> #include <shared_mutex> #include <mitkCreateDistanceImageFromSurfaceFilter.h> #include <mitkComputeContourSetNormalsFilter.h> #include <mitkImageAccessByItk.h> #include <mitkImagePixelReadAccessor.h> #include <mitkImageTimeSelector.h> #include <mitkImageToSurfaceFilter.h> #include <mitkLabelSetImage.h> #include <mitkMemoryUtilities.h> #include <mitkNodePredicateDataUID.h> #include <mitkNodePredicateProperty.h> #include <mitkNodePredicateAnd.h> #include <mitkPlanarCircle.h> #include <mitkPlaneGeometry.h> #include <mitkReduceContourSetFilter.h> #include <vtkFieldData.h> #include <vtkMath.h> #include <vtkPolygon.h> typedef std::map<mitk::TimeStepType, mitk::SurfaceInterpolationController::CPIVector> CPITimeStepMap; typedef std::map<mitk::LabelSetImage::LabelValueType, CPITimeStepMap> CPITimeStepLabelMap; typedef std::map<const mitk::LabelSetImage*, CPITimeStepLabelMap> CPITimeStepLabelSegMap; CPITimeStepLabelSegMap cpiMap; std::shared_mutex cpiMutex; std::map<mitk::LabelSetImage*, unsigned long> segmentationObserverTags; mitk::SurfaceInterpolationController::SurfaceInterpolationController() - : m_SelectedSegmentation(nullptr), - m_CurrentTimePoint(0.) + : m_DistanceImageVolume(50000), + m_SelectedSegmentation(nullptr) { - m_DistanceImageSpacing = 0.0; - m_ReduceFilter = ReduceContourSetFilter::New(); - m_NormalsFilter = ComputeContourSetNormalsFilter::New(); - m_InterpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); - - m_ReduceFilter->SetUseProgressBar(false); - m_NormalsFilter->SetUseProgressBar(true); - m_NormalsFilter->SetProgressStepSize(1); - m_InterpolateSurfaceFilter->SetUseProgressBar(true); - m_InterpolateSurfaceFilter->SetProgressStepSize(7); - - m_InterpolationResult = nullptr; - m_CurrentNumberOfReducedContours = 0; } mitk::SurfaceInterpolationController::~SurfaceInterpolationController() { this->RemoveObservers(); } void mitk::SurfaceInterpolationController::RemoveObservers() { // Removing all observers while (segmentationObserverTags.size()) { this->RemoveObserversInternal(segmentationObserverTags.begin()->first); } } mitk::SurfaceInterpolationController *mitk::SurfaceInterpolationController::GetInstance() { static mitk::SurfaceInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SurfaceInterpolationController::New(); } return m_Instance; } void mitk::SurfaceInterpolationController::AddNewContours(const std::vector<ContourPositionInformation>& newCPIs, - bool reinitializationAction) + bool reinitializationAction, bool silent) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return; for (auto cpi : newCPIs) { if (cpi.Contour->GetVtkPolyData()->GetNumberOfPoints() > 0) { this->AddToCPIMap(cpi, reinitializationAction); } } - this->Modified(); + if (!silent) this->Modified(); } mitk::DataNode* GetSegmentationImageNodeInternal(mitk::DataStorage* ds, const mitk::LabelSetImage* seg) { if (nullptr == ds) return nullptr; if (nullptr == seg) return nullptr; mitk::DataNode* segmentationNode = nullptr; mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(seg->GetUID()); auto dataNodeObjects = ds->GetSubset(dataUIDPredicate); if (dataNodeObjects->Size() != 0) { for (auto it = dataNodeObjects->Begin(); it != dataNodeObjects->End(); ++it) { segmentationNode = it->Value(); } } else { MITK_ERROR << "Unable to find the labelSetImage with the desired UID."; } return segmentationNode; } mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() const { if (m_DataStorage.IsNull()) return nullptr; auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return nullptr; return GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation); } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const { DataStorage::SetOfObjects::Pointer relevantNodes = DataStorage::SetOfObjects::New(); if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(segNode, labelValue); for (auto it = nodes->Begin(); it != nodes->End(); ++it) { auto aTS = dynamic_cast<mitk::IntProperty*>(it->Value()->GetProperty("timeStep"))->GetValue(); bool sameTS = (timeStep == aTS); if (sameTS) { relevantNodes->push_back(it->Value()); } } } return relevantNodes; } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const { auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); auto isCorrectLabel = NodePredicateProperty::New("labelID", mitk::UShortProperty::New(labelValue)); auto searchPredicate = NodePredicateAnd::New(isContourPlaneGeometry, isCorrectLabel); mitk::DataStorage::SetOfObjects::ConstPointer result; if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, searchPredicate); return result; } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const { auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer result; if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, isContourPlaneGeometry); return result; } void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow()<< "Cannot add plane geometries. No valid segmentation selected."; } - if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) { MITK_ERROR << "Invalid time point requested in AddPlaneGeometryNodeToDataStorage."; return; } if (m_DataStorage.IsNull()) { MITK_DEBUG << "Cannot add plane geometry nodes. No data storage is set."; return; } auto planeGeometry = contourInfo.Plane; if (planeGeometry) { auto segmentationNode = this->GetSegmentationImageNode(); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = this->GetPlaneGeometryNodeFromDataStorage(segmentationNode, contourInfo.LabelValue, contourInfo.TimeStep); mitk::DataNode::Pointer contourPlaneGeometryDataNode; // Go through the pre-existing contours and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto planeData = dynamic_cast<mitk::PlanarFigure*>(it->Value()->GetData()); if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); if (samePlane) { contourPlaneGeometryDataNode = it->Value(); break; } } // Go through the contourPlaneGeometry Data and add the segmentationNode to it. if (contourPlaneGeometryDataNode.IsNull()) { auto planeGeometryData = mitk::PlanarCircle::New(); planeGeometryData->SetPlaneGeometry(planeGeometry->Clone()); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); planeGeometryData->PlaceFigure(p1); planeGeometryData->SetCurrentControlPoint(p1); planeGeometryData->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); std::string contourName = "contourPlane L " + std::to_string(contourInfo.LabelValue) + " T " + std::to_string(contourInfo.TimeStep); contourPlaneGeometryDataNode = mitk::DataNode::New(); contourPlaneGeometryDataNode->SetData(planeGeometryData); // No need to change properties contourPlaneGeometryDataNode->SetProperty("helper object", mitk::BoolProperty::New(false)); contourPlaneGeometryDataNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetProperty("isContourPlaneGeometry", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetVisibility(false); // Need to change properties contourPlaneGeometryDataNode->SetProperty("name", mitk::StringProperty::New(contourName) ); contourPlaneGeometryDataNode->SetProperty("labelID", mitk::UShortProperty::New(contourInfo.LabelValue)); contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(contourInfo.TimeStep)); contourPlaneGeometryDataNode->SetData(planeGeometryData); m_DataStorage->Add(contourPlaneGeometryDataNode, segmentationNode); } } } void mitk::SurfaceInterpolationController::AddToCPIMap(ContourPositionInformation& contourInfo, bool reinitializationAction) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return; - if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) { - MITK_ERROR << "Invalid time point requested for interpolation pipeline."; + MITK_ERROR << "Invalid time step requested for interpolation pipeline."; return; } if (contourInfo.Plane == nullptr) { MITK_ERROR << "contourInfo plane is null."; return; } if (contourInfo.Contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { this->RemoveContour(contourInfo); MITK_DEBUG << "contourInfo contour is empty."; return; } { std::lock_guard<std::shared_mutex> guard(cpiMutex); const auto& currentTimeStep = contourInfo.TimeStep; const auto& currentLabelValue = contourInfo.LabelValue; auto& currentImageContours = cpiMap[selectedSegmentation]; auto& currentLabelContours = currentImageContours[currentLabelValue]; auto& currentContourList = currentLabelContours[currentTimeStep]; auto finding = std::find_if(currentContourList.begin(), currentContourList.end(), [contourInfo](const ContourPositionInformation& element) {return contourInfo.Plane->IsOnPlane(element.Plane); }); if (finding != currentContourList.end()) { MITK_DEBUG << "CPI already exists. CPI is updated. Label: "<< currentLabelValue << "; Time Step: " << currentTimeStep; *finding = contourInfo; } else { currentContourList.push_back(contourInfo); } } if (!reinitializationAction) { this->AddPlaneGeometryNodeToDataStorage(contourInfo); } } bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo, bool keepPlaceholderForUndo) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { return false; } - if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) { return false; } bool removedIt = false; { std::lock_guard<std::shared_mutex> cpiGuard(cpiMutex); const auto currentTimeStep = contourInfo.TimeStep; const auto currentLabel = contourInfo.LabelValue; auto it = cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).begin(); while (it != cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).end()) { const ContourPositionInformation& currentContour = (*it); if (currentContour.Plane->IsOnPlane(contourInfo.Plane)) { if (keepPlaceholderForUndo) { it->Contour = nullptr; } else { cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).erase(it); } removedIt = true; if (m_DataStorage.IsNotNull()) { mitk::DataNode::Pointer contourPlaneGeometryDataNode; auto contourNodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(m_DataStorage, selectedSegmentation), currentLabel, currentTimeStep); // Go through the nodes and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto planeData = dynamic_cast<mitk::PlanarFigure*>(it->Value()->GetData()); if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); if (samePlane) { m_DataStorage->Remove(it->Value()); break; } } } break; } ++it; } } - if (removedIt) - { - this->ReinitializeInterpolation(); - this->Modified(); - } - return removedIt; } -void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel) +void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(ReduceContourSetFilter* reduceFilter, const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { - this->ReinitializeInterpolation(); - - auto selectedSegmentation = m_SelectedSegmentation.Lock(); - if (selectedSegmentation.IsNull()) - { - mitkThrow() << "Cannot add active label contours. No valid segmentation selected."; - } - - if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) - { - MITK_ERROR << "Invalid time point requested for interpolation pipeline."; - return; - } - - if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) - { - MITK_ERROR << "Invalid time point requested for interpolation pipeline."; - return; - } - const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - std::shared_lock<std::shared_mutex> guard(cpiMutex); - auto& currentImageContours = cpiMap.at(selectedSegmentation); + auto& currentImageContours = cpiMap.at(segmentationImage); - auto finding = currentImageContours.find(activeLabel); + auto finding = currentImageContours.find(labelValue); if (finding == currentImageContours.end()) { - MITK_INFO << "Contours for label don't exist. Label value: " << activeLabel; + MITK_INFO << "Contours for label don't exist. Label value: " << labelValue; return; } auto currentLabelContoursMap = finding->second; - auto tsfinding = currentLabelContoursMap.find(currentTimeStep); + auto tsfinding = currentLabelContoursMap.find(timeStep); if (tsfinding == currentLabelContoursMap.end()) { MITK_INFO << "Contours for current time step don't exist."; return; } CPIVector& currentContours = tsfinding->second; unsigned int index = 0; - m_ReduceFilter->Reset(); for (const auto& cpi : currentContours) { if (!cpi.IsPlaceHolder()) { - m_ReduceFilter->SetInput(index, cpi.Contour); + reduceFilter->SetInput(index, cpi.Contour); ++index; } } } -void mitk::SurfaceInterpolationController::Interpolate() +mitk::Surface::Pointer mitk::SurfaceInterpolationController::Interpolate(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { - auto selectedSegmentation = m_SelectedSegmentation.Lock(); - if (selectedSegmentation.IsNull()) + if (nullptr == segmentationImage) { - mitkThrow() << "Cannot interpolate. No valid segmentation selected."; + mitkThrow() << "Cannot interpolate contours. No valid segmentation passed."; } - if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + auto it = cpiMap.find(segmentationImage); + if (it == cpiMap.end()) { - MITK_WARN << "No interpolation possible, currently selected timepoint is not in the time bounds of currently selected segmentation. Time point: " << m_CurrentTimePoint; - m_InterpolationResult = nullptr; - return; + mitkThrow() << "Cannot interpolate contours. Passed segmentation is not registered at controller."; + } + + if (!segmentationImage->ExistLabel(labelValue)) + { + mitkThrow() << "Cannot interpolate contours. None existant label request. Invalid label:" << labelValue; } - const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - m_ReduceFilter->Update(); - m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); - if (m_CurrentNumberOfReducedContours == 1) + if (!segmentationImage->GetTimeGeometry()->IsValidTimeStep(timeStep)) + { + mitkThrow() << "Cannot interpolate contours. No valid time step requested. Invalid time step:" << timeStep; + } + + mitk::Surface::Pointer interpolationResult = nullptr; + + auto reduceFilter = ReduceContourSetFilter::New(); + auto normalsFilter = ComputeContourSetNormalsFilter::New(); + auto interpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); + + const auto spacing = segmentationImage->GetGeometry(timeStep)->GetSpacing(); + double minSpacing = 100; + double maxSpacing = 0; + for (int i = 0; i < 3; i++) { - vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); - if (tmp == nullptr) + if (spacing[i] < minSpacing) + { + minSpacing = spacing[i]; + } + if (spacing[i] > maxSpacing) { - m_CurrentNumberOfReducedContours = 0; + maxSpacing = spacing[i]; } } + reduceFilter->SetMinSpacing(minSpacing); + reduceFilter->SetMaxSpacing(maxSpacing); + normalsFilter->SetMaxSpacing(maxSpacing); + interpolateSurfaceFilter->SetDistanceImageVolume(m_DistanceImageVolume); - // We use the timeSelector to get the segmentation image for the current segmentation. + reduceFilter->SetUseProgressBar(false); + normalsFilter->SetUseProgressBar(true); + normalsFilter->SetProgressStepSize(1); + interpolateSurfaceFilter->SetUseProgressBar(true); + interpolateSurfaceFilter->SetProgressStepSize(7); + + // Set reference image for interpolation surface filter + itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(selectedSegmentation); - timeSelector->SetTimeNr(currentTimeStep); + timeSelector->SetInput(segmentationImage); + timeSelector->SetTimeNr(timeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); - mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); + AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); + interpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); - m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); - - for (size_t i = 0; i < m_CurrentNumberOfReducedContours; ++i) + try { - mitk::Surface::Pointer reducedContour = m_ReduceFilter->GetOutput(i); - reducedContour->DisconnectPipeline(); - m_NormalsFilter->SetInput(i, reducedContour); - m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); - } + this->AddActiveLabelContoursForInterpolation(reduceFilter, segmentationImage, labelValue, timeStep); + reduceFilter->Update(); + auto currentNumberOfReducedContours = reduceFilter->GetNumberOfOutputs(); - if (m_CurrentNumberOfReducedContours < 2) - { - // If no interpolation is possible reset the interpolation result - MITK_INFO << "Interpolation impossible: not enough contours."; - m_InterpolationResult = nullptr; - return; - } + if (currentNumberOfReducedContours < 2) + { + // If no interpolation is possible reset the interpolation result + MITK_INFO << "Interpolation impossible: not enough contours."; + } + else + { + normalsFilter->SetSegmentationBinaryImage(refSegImage); - // Setting up progress bar - mitk::ProgressBar::GetInstance()->AddStepsToDo(10); + for (size_t i = 0; i < currentNumberOfReducedContours; ++i) + { + mitk::Surface::Pointer reducedContour = reduceFilter->GetOutput(i); + reducedContour->DisconnectPipeline(); + normalsFilter->SetInput(i, reducedContour); + interpolateSurfaceFilter->SetInput(i, normalsFilter->GetOutput(i)); + } + + // Setting up progress bar + mitk::ProgressBar::GetInstance()->AddStepsToDo(10); - // create a surface from the distance-image - mitk::ImageToSurfaceFilter::Pointer imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); - imageToSurfaceFilter->SetInput(m_InterpolateSurfaceFilter->GetOutput()); - imageToSurfaceFilter->SetThreshold(0); - imageToSurfaceFilter->SetSmooth(true); - imageToSurfaceFilter->SetSmoothIteration(1); - imageToSurfaceFilter->Update(); + // create a surface from the distance-image + auto imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); + imageToSurfaceFilter->SetInput(interpolateSurfaceFilter->GetOutput()); + imageToSurfaceFilter->SetThreshold(0); + imageToSurfaceFilter->SetSmooth(true); + imageToSurfaceFilter->SetSmoothIteration(1); + imageToSurfaceFilter->Update(); - mitk::Surface::Pointer interpolationResult = mitk::Surface::New(); - interpolationResult->Expand(selectedSegmentation->GetTimeSteps()); + interpolationResult = mitk::Surface::New(); + interpolationResult->Expand(segmentationImage->GetTimeSteps()); - auto geometry = selectedSegmentation->GetTimeGeometry()->Clone(); - geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); - interpolationResult->SetTimeGeometry(geometry); + auto geometry = segmentationImage->GetTimeGeometry()->Clone(); + geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); + interpolationResult->SetTimeGeometry(geometry); - interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), currentTimeStep); - m_InterpolationResult = interpolationResult; + interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), timeStep); + interpolationResult->DisconnectPipeline(); - m_DistanceImageSpacing = m_InterpolateSurfaceFilter->GetDistanceImageSpacing(); + // Last progress step + mitk::ProgressBar::GetInstance()->Progress(20); + + } + } + catch (const Exception& e) + { + MITK_ERROR << "Interpolation failed: " << e.what(); + interpolationResult = nullptr; + } - // Last progress step - mitk::ProgressBar::GetInstance()->Progress(20); - m_InterpolationResult->DisconnectPipeline(); + return interpolationResult; } mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult() { - return m_InterpolationResult; + return nullptr; } void mitk::SurfaceInterpolationController::SetDataStorage(DataStorage::Pointer ds) { m_DataStorage = ds; } -void mitk::SurfaceInterpolationController::SetMinSpacing(double minSpacing) -{ - m_ReduceFilter->SetMinSpacing(minSpacing); -} - -void mitk::SurfaceInterpolationController::SetMaxSpacing(double maxSpacing) -{ - m_ReduceFilter->SetMaxSpacing(maxSpacing); - m_NormalsFilter->SetMaxSpacing(maxSpacing); -} - void mitk::SurfaceInterpolationController::SetDistanceImageVolume(unsigned int distImgVolume) { - m_InterpolateSurfaceFilter->SetDistanceImageVolume(distImgVolume); + m_DistanceImageVolume = distImgVolume; } mitk::LabelSetImage* mitk::SurfaceInterpolationController::GetCurrentSegmentation() { return m_SelectedSegmentation.Lock(); } -mitk::Image *mitk::SurfaceInterpolationController::GetInterpolationImage() -{ - return m_InterpolateSurfaceFilter->GetOutput(); -} - unsigned int mitk::SurfaceInterpolationController::GetNumberOfInterpolationSessions() { return cpiMap.size(); } template <typename TPixel, unsigned int VImageDimension> void mitk::SurfaceInterpolationController::GetImageBase(itk::Image<TPixel, VImageDimension> *input, itk::ImageBase<3>::Pointer &result) { result->Graft(input); } void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::LabelSetImage* currentSegmentationImage) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (currentSegmentationImage == selectedSegmentation) { return; } m_SelectedSegmentation = currentSegmentationImage; selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { std::lock_guard<std::shared_mutex> guard(cpiMutex); auto it = cpiMap.find(selectedSegmentation); if (it == cpiMap.end()) { cpiMap[selectedSegmentation] = CPITimeStepLabelMap(); auto command = itk::MemberCommand<SurfaceInterpolationController>::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); segmentationObserverTags[selectedSegmentation] = selectedSegmentation->AddObserver(itk::DeleteEvent(), command); selectedSegmentation->AddLabelRemovedListener(mitk::MessageDelegate1<SurfaceInterpolationController, mitk::LabelSetImage::LabelValueType>( this, &SurfaceInterpolationController::OnRemoveLabel)); } - } - - m_InterpolationResult = nullptr; - m_CurrentNumberOfReducedContours = 0; - m_NormalsFilter->SetSegmentationBinaryImage(nullptr); - this->ReinitializeInterpolation(); } void mitk::SurfaceInterpolationController::RemoveInterpolationSession(const mitk::LabelSetImage* segmentationImage) { if (nullptr != segmentationImage) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation == segmentationImage) { this->SetCurrentInterpolationSession(nullptr); } { std::lock_guard<std::shared_mutex> guard(cpiMutex); this->RemoveObserversInternal(segmentationImage); cpiMap.erase(segmentationImage); if (m_DataStorage.IsNotNull()) { auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage)); this->m_DataStorage->Remove(nodes); } } } } void mitk::SurfaceInterpolationController::RemoveObserversInternal(const mitk::LabelSetImage* segmentationImage) { auto pos = segmentationObserverTags.find(const_cast<mitk::LabelSetImage*>(segmentationImage)); if (pos != segmentationObserverTags.end()) { pos->first->RemoveObserver((*pos).second); segmentationImage->RemoveLabelRemovedListener(mitk::MessageDelegate1<SurfaceInterpolationController, mitk::LabelSetImage::LabelValueType>( this, &SurfaceInterpolationController::OnRemoveLabel)); segmentationObserverTags.erase(const_cast<mitk::LabelSetImage*>(segmentationImage)); } } void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() { while (!cpiMap.empty()) { this->RemoveInterpolationSession(cpiMap.begin()->first); } } void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label, TimeStepType timeStep) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot remove contours. No valid segmentation selected."; } std::lock_guard<std::shared_mutex> guard(cpiMutex); auto& cpiLabelMap = cpiMap[selectedSegmentation]; auto finding = cpiLabelMap.find(label); if (finding != cpiLabelMap.end()) { finding->second.erase(timeStep); } if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation), label, timeStep); this->m_DataStorage->Remove(nodes); } this->Modified(); } void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot remove contours. No valid segmentation selected."; } std::lock_guard<std::shared_mutex> guard(cpiMutex); cpiMap[selectedSegmentation].erase(label); if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation), label); this->m_DataStorage->Remove(nodes); } this->Modified(); } void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject & /*event*/) { auto tempImage = dynamic_cast<mitk::LabelSetImage *>(const_cast<itk::Object *>(caller)); if (tempImage) { this->RemoveInterpolationSession(tempImage); } } -void mitk::SurfaceInterpolationController::ReinitializeInterpolation() -{ - // If session has changed reset the pipeline - m_ReduceFilter->Reset(); - m_NormalsFilter->Reset(); - m_InterpolateSurfaceFilter->Reset(); - - itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); - - auto selectedSegmentation = m_SelectedSegmentation.Lock(); - - if (selectedSegmentation.IsNotNull()) - { - if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) - { - MITK_WARN << "Interpolation cannot be reinitialized. Currently selected timepoint is not in the time bounds of the currently selected segmentation. Time point: " << m_CurrentTimePoint; - return; - } - - const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - - // Set reference image for interpolation surface filter - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(selectedSegmentation); - timeSelector->SetTimeNr(currentTimeStep); - timeSelector->SetChannelNr(0); - timeSelector->Update(); - mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); - AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); - m_InterpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); - } -} - void mitk::SurfaceInterpolationController::OnRemoveLabel(mitk::Label::PixelType removedLabelValue) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { this->RemoveContours(removedLabelValue); } } mitk::SurfaceInterpolationController::CPIVector* mitk::SurfaceInterpolationController::GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation == nullptr) return nullptr; std::shared_lock<std::shared_mutex> guard(cpiMutex); auto labelFinding = cpiMap[selectedSegmentation].find(labelValue); if (labelFinding != cpiMap[selectedSegmentation].end()) { auto tsFinding = labelFinding->second.find(timeStep); if (tsFinding != labelFinding->second.end()) { return &(tsFinding->second); } } return nullptr; } std::vector<mitk::LabelSetImage::LabelValueType> mitk::SurfaceInterpolationController::GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const { std::lock_guard<std::shared_mutex> guard(cpiMutex); std::vector<mitk::LabelSetImage::LabelValueType> result; auto finding = cpiMap.find(seg); if (finding == cpiMap.end()) return result; auto currentImageContours = finding->second; for (auto [label, contours] : currentImageContours) { auto tsFinding = contours.find(timeStep); if (tsFinding != contours.end()) { auto cpis = tsFinding->second; auto finding = std::find_if(cpis.begin(), cpis.end(), [plane](const ContourPositionInformation& element) {return plane->IsOnPlane(element.Plane); }); if (finding != cpis.end()) { result.push_back(label); } } } return result; } void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector<ContourPositionInformation>& newCPIs) { this->ClearInterpolationSession(); // Now the layers should be empty and the new layers can be added. this->AddNewContours(newCPIs, true); } void mitk::SurfaceInterpolationController::ClearInterpolationSession() { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation != nullptr) { std::lock_guard<std::shared_mutex> guard(cpiMutex); cpiMap[selectedSegmentation].clear(); } } diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h index 3a3e32c9ca..bb1d2f0ba2 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h @@ -1,295 +1,260 @@ /*============================================================================ 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 mitkSurfaceInterpolationController_h #define mitkSurfaceInterpolationController_h #include <mitkDataStorage.h> #include <mitkLabelSetImage.h> #include <mitkLabel.h> #include <mitkSurface.h> #include <MitkSurfaceInterpolationExports.h> namespace mitk { class ComputeContourSetNormalsFilter; class CreateDistanceImageFromSurfaceFilter; class LabelSetImage; class ReduceContourSetFilter; class MITKSURFACEINTERPOLATION_EXPORT SurfaceInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - itkGetMacro(DistanceImageSpacing, double); struct MITKSURFACEINTERPOLATION_EXPORT ContourPositionInformation { Surface::ConstPointer Contour; PlaneGeometry::ConstPointer Plane; Label::PixelType LabelValue; TimeStepType TimeStep; ContourPositionInformation() : Plane(nullptr), LabelValue(std::numeric_limits<Label::PixelType>::max()), TimeStep(std::numeric_limits<TimeStepType>::max()) { } ContourPositionInformation(Surface::ConstPointer contour, PlaneGeometry::ConstPointer plane, Label::PixelType labelValue, TimeStepType timeStep) : Contour(contour), Plane(plane), LabelValue(labelValue), TimeStep(timeStep) { } bool IsPlaceHolder() const { return Contour.IsNull(); } }; typedef std::vector<ContourPositionInformation> CPIVector; static SurfaceInterpolationController *GetInstance(); - void SetCurrentTimePoint(TimePointType tp) - { - if (m_CurrentTimePoint != tp) - { - m_CurrentTimePoint = tp; - - if (m_SelectedSegmentation) - { - this->ReinitializeInterpolation(); - } - } - }; - - TimePointType GetCurrentTimePoint() const { return m_CurrentTimePoint; }; - /** * @brief Adds new extracted contours to the list. If one or more contours at a given position * already exist they will be updated respectively */ - void AddNewContours(const std::vector<ContourPositionInformation>& newCPIs, bool reinitializeAction = false); + void AddNewContours(const std::vector<ContourPositionInformation>& newCPIs, bool reinitializeAction = false, bool silent = false); /** * @brief Removes the contour for a given plane for the current selected segmenation * @param contourInfo the contour which should be removed * @return true if a contour was found and removed, false if no contour was found */ bool RemoveContour(ContourPositionInformation contourInfo, bool keepPlaceholderForUndo = false); - /** - * @brief Resets the pipeline for interpolation. The various filters used are reset. - * - */ - void ReinitializeInterpolation(); - void RemoveObservers(); /** * @brief Performs the interpolation. * */ - void Interpolate(); + Surface::Pointer Interpolate(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); /** * @brief Get the Result of the interpolation operation. * * @return mitk::Surface::Pointer */ mitk::Surface::Pointer GetInterpolationResult(); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface. * * @param minSpacing Paramter to set */ void SetMinSpacing(double minSpacing); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface * @param maxSpacing Set the max Spacing for interpolation */ void SetMaxSpacing(double maxSpacing); /** * Sets the volume i.e. the number of pixels that the distance image should have * By evaluation we found out that 50.000 pixel delivers a good result */ void SetDistanceImageVolume(unsigned int distImageVolume); /** * @brief Get the current selected segmentation for which the interpolation is performed * @return the current segmentation image */ mitk::LabelSetImage* GetCurrentSegmentation(); void SetDataStorage(DataStorage::Pointer ds); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param currentSegmentationImage The current selected segmentation */ void SetCurrentInterpolationSession(LabelSetImage* currentSegmentationImage); /** * @brief Remove interpolation session * @param segmentationImage the session to be removed */ void RemoveInterpolationSession(const LabelSetImage* segmentationImage); /** * @brief Removes all sessions */ void RemoveAllInterpolationSessions(); - mitk::Image *GetInterpolationImage(); - /** * @brief Get the Contours at a certain timeStep and layerID. * * @param timeStep Time Step from which to get the contours. * @param labelValue label from which to get the contours. * @return std::vector<ContourPositionInformation> Returns contours. */ CPIVector* GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); std::vector<LabelSetImage::LabelValueType> GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const; /** * @brief Trigerred with the "Reinit Interpolation" action. The contours are used to repopulate the * surfaceInterpolator data structures so that interpolation can be performed after reloading data. * * @param contourList List of contours extracted * @param contourPlanes List of planes at which the contours were extracted */ void CompleteReinitialization(const std::vector<ContourPositionInformation>& newCPIs); /** * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param label Label of contour to remove. * @param timeStep Time step in which to remove the contours. * @remark if the label or time step does not exist, nothing happens. */ void RemoveContours(mitk::Label::PixelType label, TimeStepType timeStep); /** * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param label Label of contour to remove. * @param timeStep Time step in which to remove the contours. * @remark if the label or time step does not exist, nothing happens. */ void RemoveContours(mitk::Label::PixelType label); - /** - * Adds Contours from the active Label to the interpolation pipeline - */ - void AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel); - unsigned int GetNumberOfInterpolationSessions(); /** * @brief Get the Segmentation Image Node object * * @return DataNode* returns the DataNode containing the segmentation image. */ mitk::DataNode* GetSegmentationImageNode() const; protected: SurfaceInterpolationController(); ~SurfaceInterpolationController() override; template <typename TPixel, unsigned int VImageDimension> void GetImageBase(itk::Image<TPixel, VImageDimension> *input, itk::ImageBase<3>::Pointer &result); private: /** * @brief * * @param caller * @param event */ void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); /** * @brief Function that removes contours of a particular label when the "Remove Label" event is trigerred in the labelSetImage. * */ void OnRemoveLabel(mitk::Label::PixelType removedLabelValue); /** * @brief When a new contour is added to the pipeline or an existing contour is replaced, * the plane geometry information of that contour is added as a child node to the * current node of the segmentation image. This is useful in the retrieval of contour information * when data is reloaded after saving. * * @param contourInfo contourInfo struct to add to data storage. */ void AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const; + /** + * Adds Contours from the active Label to the interpolation pipeline + */ + void AddActiveLabelContoursForInterpolation(ReduceContourSetFilter* reduceFilter, const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); + /** * @brief Clears the interpolation data structures. Called from CompleteReinitialization(). * */ void ClearInterpolationSession(); void RemoveObserversInternal(const mitk::LabelSetImage* segmentationImage); /** * @brief Add contour to the interpolation pipeline * * @param contourInfo Contour information to be added * @param reinitializationAction If the contour is coming from a reinitialization process or not */ void AddToCPIMap(ContourPositionInformation& contourInfo, bool reinitializationAction = false); - itk::SmartPointer<ReduceContourSetFilter> m_ReduceFilter; - itk::SmartPointer<ComputeContourSetNormalsFilter> m_NormalsFilter; - itk::SmartPointer<CreateDistanceImageFromSurfaceFilter> m_InterpolateSurfaceFilter; - - double m_DistanceImageSpacing; - + unsigned int m_DistanceImageVolume; mitk::DataStorage::Pointer m_DataStorage; - mitk::Surface::Pointer m_InterpolationResult; - - unsigned int m_CurrentNumberOfReducedContours; - WeakPointer<LabelSetImage> m_SelectedSegmentation; - - mitk::TimePointType m_CurrentTimePoint; }; } #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 3cd4345e95..ec09f0e2d7 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1094 +1,1098 @@ /*============================================================================ 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 <berryIWorkbenchPage.h> // mitk #include <mitkApplicationCursor.h> #include <mitkBaseApplication.h> #include <mitkBaseRendererHelper.h> #include <mitkCameraController.h> #include <mitkLabelSetImage.h> #include <mitkLabelSetImageHelper.h> #include <mitkMultiLabelIOHelper.h> #include <mitkManualPlacementAnnotationRenderer.h> #include <mitkNodePredicateSubGeometry.h> #include <mitkSegmentationObjectFactory.h> #include <mitkSegTool2D.h> #include <mitkStatusBar.h> #include <mitkToolManagerProvider.h> #include <mitkVtkResliceInterpolationProperty.h> #include <mitkWorkbenchUtil.h> #include <mitkIPreferences.h> // Qmitk #include <QmitkRenderWindow.h> #include <QmitkStaticDynamicSegmentationDialog.h> #include <QmitkNewSegmentationDialog.h> #include <QmitkMultiLabelManager.h> // us #include <usModuleResource.h> #include <usModuleResourceStream.h> // Qt #include <QMessageBox> #include <QShortcut> #include <QDir> // vtk #include <vtkQImageToImageSource.h> #include <regex> namespace { QList<QmitkRenderWindow*> Get2DWindows(const QList<QmitkRenderWindow*> allWindows) { QList<QmitkRenderWindow*> 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<mitk::Image>::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<mitk::LabelSetImage>::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(); this->RemoveObserversFromWorkingImage(); // 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>(); mitk::PlanePositionManagerService* service = context->getService<mitk::PlanePositionManagerService>(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate<Self>(this, &Self::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList<mitk::DataNode::Pointer>) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList<mitk::DataNode::Pointer>) { 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<Self>::New(); command->SetCallbackFunction(this, &Self::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; this->RemoveObserversFromWorkingImage(); // 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<Self>::New(); command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); this->AddObserversToWorkingImage(); } } // 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::OnLabelAdded(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnLabelRemoved(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnGroupRemoved(mitk::LabelSetImage::GroupIndexType) { this->ValidateSelectionInput(); } mitk::LabelSetImage* QmitkSegmentationView::GetWorkingImage() { if (m_WorkingNode.IsNull()) return nullptr; return dynamic_cast<mitk::LabelSetImage*>(m_WorkingNode->GetData()); } void QmitkSegmentationView::AddObserversToWorkingImage() { auto* workingImage = this->GetWorkingImage(); if (workingImage != nullptr) { workingImage->AddLabelAddedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::LabelValueType>(this, &Self::OnLabelAdded)); workingImage->AddLabelRemovedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::LabelValueType>(this, &Self::OnLabelRemoved)); workingImage->AddGroupRemovedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::GroupIndexType>(this, &Self::OnGroupRemoved)); } } void QmitkSegmentationView::RemoveObserversFromWorkingImage() { auto* workingImage = this->GetWorkingImage(); if (workingImage != nullptr) { workingImage->RemoveLabelAddedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::LabelValueType>(this, &Self::OnLabelAdded)); workingImage->RemoveLabelRemovedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::LabelValueType>(this, &Self::OnLabelRemoved)); workingImage->RemoveGroupRemovedListener(mitk::MessageDelegate1<Self, mitk::LabelSetImage::GroupIndexType>(this, &Self::OnGroupRemoved)); } } 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<mitk::LabelSetImage*>(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); auto labels = workingImage->GetLabelValuesByGroup(workingImage->GetActiveLayer()); auto it = std::find(labels.begin(), labels.end(), workingImage->GetActiveLabel()->GetValue()); if (it != labels.end()) ++it; if (it == labels.end()) { it = labels.begin(); } workingImage->SetActiveLabel(*it); 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<mitk::Image*>(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<mitk::LabelSetImage*>(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); newLabelSetImage->AddLabel(newLabel, newLabelSetImage->GetActiveLayer()); } 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<mitk::SegTool2D*>(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(); if (nullptr == segmentation->GetActiveLabel() || labelValue != segmentation->GetActiveLabel()->GetValue()) { segmentation->SetActiveLabel(labelValue); - m_Controls->slicesInterpolator->SetActiveLabelValue(labelValue); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } + m_Controls->slicesInterpolator->SetActiveLabelValue(labelValue); } 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; } 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<mitk::LabelSetImage*>(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, &Self::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &Self::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, &Self::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &Self::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' 'Segment Anything'"); 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, &Self::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, &Self::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &Self::OnShowMarkerNodes); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &Self::OnCurrentLabelSelectionChanged); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &Self::OnGoToLabel); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &Self::OnLabelRenameRequested); auto command = itk::SimpleMemberCommand<Self>::New(); command->SetCallbackFunction(this, &Self::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<mitk::Image *>(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<Self>(this, &Self::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<Self>(this, &Self::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<mitk::DataNode*>(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>(); mitk::PlanePositionManagerService* service = context->getService<mitk::PlanePositionManagerService>(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; auto image = dynamic_cast<mitk::LabelSetImage*>(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<mitk::LabelSetImage*>(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>(); mitk::PlanePositionManagerService* service = context->getService<mitk::PlanePositionManagerService>(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<mitk::DataNode::Pointer>& 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() ? "<br>" : "") + 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<mitk::LabelSetImage *>(workingNode->GetData()); numberOfLabels = labelSetImage->GetTotalNumberOfLabels(); if (numberOfLabels > 0) m_Controls->slicesInterpolator->setEnabled(true); m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData())); + + if (!m_Controls->multiLabelWidget->GetSelectedLabels().empty()) + { + m_Controls->slicesInterpolator->SetActiveLabelValue(m_Controls->multiLabelWidget->GetSelectedLabels().front()); + } } 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() ? "<br>" : "") + 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("<font color=\"red\">" + text + "</font>"); m_Controls->selectionWarningLabel->show(); } }