diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index b2cab17e93..de863b6070 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1152 +1,1153 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include #include //#include #include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ExteriorLabel(nullptr) { // Iniitlaize Background Label mitk::Color color; color.Set(0, 0, 0); m_ExteriorLabel = mitk::Label::New(); m_ExteriorLabel->SetColor(color); m_ExteriorLabel->SetName("Exterior"); m_ExteriorLabel->SetOpacity(0.0); m_ExteriorLabel->SetLocked(false); m_ExteriorLabel->SetValue(0); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false), m_ExteriorLabel(other.GetExteriorLabel()->Clone()) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); lsClone->AddObserver(itk::ModifiedEvent(), command); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::SetExteriorLabel(mitk::Label *label) { m_ExteriorLabel = label; } mitk::Label *mitk::LabelSetImage::GetExteriorLabel() { return m_ExteriorLabel; } const mitk::Label *mitk::LabelSetImage::GetExteriorLabel() const { return m_ExteriorLabel; } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->Modified(); } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } unsigned int newLabelSetId = this->AddLayer(newImage, labelSet); return newLabelSetId; } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (labelSet.IsNotNull()) { ls = labelSet; } else { ls = mitk::LabelSet::New(); ls->AddLabel(GetExteriorLabel()); ls->SetActiveLabel(0 /*Exterior Label*/); } ls->SetLayer(newLabelSetId); // Add exterior Label to label set // mitk::Label::Pointer exteriorLabel = CreateExteriorLabel(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); SetActiveLayer(newLabelSetId); // MITK_INFO << GetActiveLayer(); this->Modified(); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } if (layerIdx < m_LabelSetContainer.size()) { m_LabelSetContainer[layerIdx] = labelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->AddLabel(GetExteriorLabel()); defaultLabelSet->SetActiveLabel(0 /*Exterior Label*/); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); m_LabelSetContainer.push_back(defaultLabelSet); } m_LabelSetContainer.push_back(labelSet); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { if (this->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(this, ClearBufferProcessing,4); } else { AccessByItk(this, ClearBufferProcessing); } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::RemoveLabel(PixelType pixelValue, unsigned int layer) { this->GetLabelSet(layer)->RemoveLabel(pixelValue); this->EraseLabel(pixelValue); } void mitk::LabelSetImage::RemoveLabels(std::vector& VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx], layer); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(this, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } Modified(); } void mitk::LabelSetImage::EraseLabels(std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { // mask->Initialize(this) does not work here if this label set image has a single slice, // since the mask would be automatically flattened to a 2-d image, whereas we expect the // original dimension of this label set image. Hence, initialize the mask more explicitly: mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (!this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && (forceOverwrite || !this->GetLabel(targetValue)->GetLocked())) // skip exterior and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabelSet(destinationLabelSet), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabelSet == other.m_DestinationLabelSet; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); if (nullptr == label || !label->GetLocked()) { return this->m_NewDestinationLabel; } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: const mitk::LabelSet* m_DestinationLabelSet = nullptr; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContent to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye, const TimeStepType timeStep) { 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."; } if (nullptr == destinationLabelSet) { mitkThrow() << "Invalid call of TransferLabelContent; destinationLabelSet must not be null"; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage does not have the requested time step: " << timeStep; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContent. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye, const TimeStepType timeStep) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } const auto sourceBackground = sourceImage->GetExteriorLabel()->GetValue(); const auto destinationBackground = destinationImage->GetExteriorLabel()->GetValue(); const auto destinationBackgroundLocked = destinationImage->GetExteriorLabel()->GetLocked(); const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); for (const auto& mappingElement : labelMapping) { if (!sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContent. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContent(sourceImage, destinationImage, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye, timeStep); } + diff --git a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp index 0d009c3680..812c2b06fa 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp +++ b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp @@ -1,359 +1,376 @@ /*============================================================================ 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 "mitkDiffImageApplier.h" #include "mitkApplyDiffImageOperation.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include "mitkRenderingManager.h" #include "mitkSegmentationInterpolationController.h" +#include + #include #include #include mitk::DiffImageApplier::DiffImageApplier() : m_Image(nullptr), m_SliceDifferenceImage(nullptr), m_SliceIndex(0), m_SliceDimension(0), m_TimeStep(0), m_Dimension0(0), m_Dimension1(0), + m_DestinationLabel(std::numeric_limits::max()), m_Factor(1.0) { } mitk::DiffImageApplier::~DiffImageApplier() { } +void mitk::DiffImageApplier::SetDestinationLabel(mitk::Label::PixelType label) +{ + m_DestinationLabel = label; +} + void mitk::DiffImageApplier::ExecuteOperation(Operation *operation) { auto *imageOperation = dynamic_cast(operation); if (imageOperation // we actually have the kind of operation that we can handle && imageOperation->IsImageStillValid()) // AND the image is not yet deleted { m_Image = imageOperation->GetImage(); Image::Pointer image3D = m_Image; // will be changed later in case of 3D+t m_SliceDifferenceImage = imageOperation->GetDiffImage(); m_TimeStep = imageOperation->GetTimeStep(); m_Factor = imageOperation->GetFactor(); if (m_SliceDifferenceImage->GetDimension() == 2) { m_SliceIndex = imageOperation->GetSliceIndex(); m_SliceDimension = imageOperation->GetSliceDimension(); switch (m_SliceDimension) { default: case 2: m_Dimension0 = 0; m_Dimension1 = 1; break; case 1: m_Dimension0 = 0; m_Dimension1 = 2; break; case 0: m_Dimension0 = 1; m_Dimension1 = 2; break; } if (m_SliceDifferenceImage->GetDimension() != 2 || (m_Image->GetDimension() < 3 || m_Image->GetDimension() > 4) || m_SliceDifferenceImage->GetDimension(0) != m_Image->GetDimension(m_Dimension0) || m_SliceDifferenceImage->GetDimension(1) != m_Image->GetDimension(m_Dimension1) || m_SliceIndex >= m_Image->GetDimension(m_SliceDimension)) { itkExceptionMacro( "Slice and image dimensions differ or slice index is too large. Sorry, cannot work like this."); return; } if (m_Image->GetDimension() == 4) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(m_TimeStep); timeSelector->UpdateLargestPossibleRegion(); image3D = timeSelector->GetOutput(); } - // this will do a long long if/else to find out both pixel types AccessFixedDimensionByItk(image3D, ItkImageSwitch2DDiff, 3); if (m_Factor == 1 || m_Factor == -1) { if (m_Factor == -1) { // multiply diff pixels by factor and then send this diff slice AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 2); } // just send the diff to SegmentationInterpolationController SegmentationInterpolationController *interpolator = SegmentationInterpolationController::InterpolatorForImage(m_Image); if (interpolator) { interpolator->BlockModified(true); interpolator->SetChangedSlice(m_SliceDifferenceImage, m_SliceDimension, m_SliceIndex, m_TimeStep); } m_Image->Modified(); if (interpolator) { interpolator->BlockModified(false); } if (m_Factor == -1) // return to normal values { AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 2); } } else // no trivial case, too lazy to do something else { m_Image->Modified(); // check if interpolation is called. prefer to send diff directly } RenderingManager::GetInstance()->RequestUpdateAll(); } else if (m_SliceDifferenceImage->GetDimension() == 3) { // ... if (m_SliceDifferenceImage->GetDimension(0) != m_Image->GetDimension(0) || m_SliceDifferenceImage->GetDimension(1) != m_Image->GetDimension(1) || m_SliceDifferenceImage->GetDimension(2) != m_Image->GetDimension(2) || m_TimeStep >= m_Image->GetDimension(3)) { itkExceptionMacro("Diff image size differs from original image size. Sorry, cannot work like this."); return; } if (m_Image->GetDimension() == 4) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(m_TimeStep); timeSelector->UpdateLargestPossibleRegion(); image3D = timeSelector->GetOutput(); } + auto labelSetImage = dynamic_cast(m_Image.GetPointer()); + // this will do a long long if/else to find out both pixel types - AccessFixedDimensionByItk(image3D, ItkImageSwitch3DDiff, 3); + TransferLabelContent( + m_SliceDifferenceImage, + labelSetImage, + labelSetImage->GetActiveLabelSet(), + 0, + 0, + false, + {{1, m_DestinationLabel}}, + mitk::MultiLabelSegmentation::MergeStyle::Merge, + mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks, + m_TimeStep); if (m_Factor == 1 || m_Factor == -1) { if (m_Factor == -1) { // multiply diff pixels by factor and then send this diff slice AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 3); } // just send the diff to SegmentationInterpolationController SegmentationInterpolationController *interpolator = SegmentationInterpolationController::InterpolatorForImage(m_Image); if (interpolator) { interpolator->BlockModified(true); interpolator->SetChangedVolume(m_SliceDifferenceImage, m_TimeStep); } - m_Image->Modified(); - if (interpolator) { interpolator->BlockModified(false); } if (m_Factor == -1) // return to normal values { AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 3); } } else // no trivial case, too lazy to do something else { m_Image->Modified(); // check if interpolation is called. prefer to send diff directly } RenderingManager::GetInstance()->RequestUpdateAll(); } else { itkExceptionMacro("Diff image must be 2D or 3D. Sorry, cannot work like this."); return; } } m_Image = nullptr; m_SliceDifferenceImage = nullptr; } mitk::DiffImageApplier *mitk::DiffImageApplier::GetInstanceForUndo() { static DiffImageApplier::Pointer s_Instance = DiffImageApplier::New(); return s_Instance; } // basically copied from mitk/Core/Algorithms/mitkImageAccessByItk.h #define myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, pixeltype, dimension, itkimage2) \ if (typeId == MapPixelComponentType::value) \ \ { \ typedef itk::Image ImageType; \ typedef mitk::ImageToItk ImageToItkType; \ itk::SmartPointer imagetoitk = ImageToItkType::New(); \ const mitk::Image *constImage = mitkImage; \ mitk::Image *nonConstImage = const_cast(constImage); \ nonConstImage->Update(); \ imagetoitk->SetInput(nonConstImage); \ imagetoitk->Update(); \ itkImageTypeFunction(imagetoitk->GetOutput(), itkimage2); \ \ } #define myMITKDiffImageApplierFilterAccessAllTypesByItk(mitkImage, itkImageTypeFunction, dimension, itkimage2) \ \ { \ myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, double, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk( \ mitkImage, \ itkImageTypeFunction, \ float, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, int, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ unsigned int, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, short, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, unsigned short, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ char, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ unsigned char, \ dimension, \ itkimage2) \ \ } template void mitk::DiffImageApplier::ItkImageSwitch2DDiff(itk::Image *itkImage) { const auto typeId = m_SliceDifferenceImage->GetPixelType().GetComponentType(); myMITKDiffImageApplierFilterAccessAllTypesByItk(m_SliceDifferenceImage, ItkImageProcessing2DDiff, 2, itkImage); } template void mitk::DiffImageApplier::ItkImageSwitch3DDiff(itk::Image *itkImage) { const auto typeId = m_SliceDifferenceImage->GetPixelType().GetComponentType(); myMITKDiffImageApplierFilterAccessAllTypesByItk(m_SliceDifferenceImage, ItkImageProcessing3DDiff, 3, itkImage); } template void mitk::DiffImageApplier::ItkImageProcessing2DDiff(itk::Image *diffImage, itk::Image *outputImage) { typedef itk::Image DiffImageType; typedef itk::Image VolumeImageType; typedef itk::ImageSliceIteratorWithIndex OutputSliceIteratorType; typedef itk::ImageRegionConstIterator DiffSliceIteratorType; typename VolumeImageType::RegionType sliceInVolumeRegion; sliceInVolumeRegion = outputImage->GetLargestPossibleRegion(); sliceInVolumeRegion.SetSize(m_SliceDimension, 1); // just one slice sliceInVolumeRegion.SetIndex(m_SliceDimension, m_SliceIndex); // exactly this slice, please OutputSliceIteratorType outputIterator(outputImage, sliceInVolumeRegion); outputIterator.SetFirstDirection(m_Dimension0); outputIterator.SetSecondDirection(m_Dimension1); DiffSliceIteratorType diffIterator(diffImage, diffImage->GetLargestPossibleRegion()); // iterate over output slice (and over input slice simultaneously) outputIterator.GoToBegin(); diffIterator.GoToBegin(); while (!outputIterator.IsAtEnd()) { while (!outputIterator.IsAtEndOfSlice()) { while (!outputIterator.IsAtEndOfLine()) { TPixel2 newValue = outputIterator.Get() + (TPixel2)((double)diffIterator.Get() * m_Factor); outputIterator.Set(newValue); ++outputIterator; ++diffIterator; } outputIterator.NextLine(); } outputIterator.NextSlice(); } } template void mitk::DiffImageApplier::ItkImageProcessing3DDiff(itk::Image *diffImage, itk::Image *outputImage) { typedef itk::Image DiffImageType; typedef itk::Image VolumeImageType; typedef itk::ImageRegionIterator OutputSliceIteratorType; typedef itk::ImageRegionConstIterator DiffSliceIteratorType; OutputSliceIteratorType outputIterator(outputImage, outputImage->GetLargestPossibleRegion()); DiffSliceIteratorType diffIterator(diffImage, diffImage->GetLargestPossibleRegion()); // iterate over output slice (and over input slice simultaneously) outputIterator.GoToBegin(); diffIterator.GoToBegin(); while (!outputIterator.IsAtEnd()) { TPixel2 newValue = outputIterator.Get() + (TPixel2)((double)diffIterator.Get() * m_Factor); outputIterator.Set(newValue); ++outputIterator; ++diffIterator; } } #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4146) // unary minus operator applied to unsigned type, result still unsigned #endif template void mitk::DiffImageApplier::ItkInvertPixelValues(itk::Image *itkImage) { typedef itk::ImageRegionIterator> IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { iter.Set(-(iter.Get())); ++iter; } } #ifdef _MSC_VER # pragma warning(pop) #endif diff --git a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.h b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.h index 6f28dbf217..38d57e5634 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.h +++ b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.h @@ -1,82 +1,87 @@ /*============================================================================ 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 mitkDiffImageApplier_h_Included #define mitkDiffImageApplier_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include "mitkOperationActor.h" #include +#include #include #include namespace mitk { /** \brief Applies difference images to 3D images. This class is supposed to execute ApplyDiffImageOperations, which contain information about pixel changes within one image slice. Class should be called from the undo stack. At the moment, ApplyDiffImageOperations are only created by QmitkSlicesInterpolator. $Author: maleike $ */ class MITKSEGMENTATION_EXPORT DiffImageApplier : public itk::Object, public OperationActor { public: mitkClassMacroItkParent(DiffImageApplier, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void ExecuteOperation(Operation *operation) override; + void SetDestinationLabel(mitk::Label::PixelType); + static DiffImageApplier *GetInstanceForUndo(); protected: DiffImageApplier(); // purposely hidden ~DiffImageApplier() override; template void ItkImageSwitch2DDiff(itk::Image *image); template void ItkImageSwitch3DDiff(itk::Image *image); template void ItkImageProcessing2DDiff(itk::Image *itkImage1, itk::Image *itkImage2); template void ItkImageProcessing3DDiff(itk::Image *itkImage1, itk::Image *itkImage2); template void ItkInvertPixelValues(itk::Image *itkImage); Image::Pointer m_Image; Image::Pointer m_SliceDifferenceImage; unsigned int m_SliceIndex; unsigned int m_SliceDimension; unsigned int m_TimeStep; unsigned int m_Dimension0; unsigned int m_Dimension1; + mitk::Label::PixelType m_DestinationLabel; + double m_Factor; }; } // namespace #endif diff --git a/Modules/Segmentation/Algorithms/mitkImageToContourFilter.cpp b/Modules/Segmentation/Algorithms/mitkImageToContourFilter.cpp index 962df6f98d..d813903a84 100644 --- a/Modules/Segmentation/Algorithms/mitkImageToContourFilter.cpp +++ b/Modules/Segmentation/Algorithms/mitkImageToContourFilter.cpp @@ -1,154 +1,168 @@ /*============================================================================ 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 "mitkImageToContourFilter.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkVtkRepresentationProperty.h" -#include "vtkLinearTransform.h" -#include "vtkMatrix4x4.h" -#include "vtkProperty.h" -#include "vtkSmartPointer.h" +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +#include mitk::ImageToContourFilter::ImageToContourFilter() { this->m_UseProgressBar = false; this->m_ProgressStepSize = 1; this->m_SliceGeometry = nullptr; } mitk::ImageToContourFilter::~ImageToContourFilter() { } void mitk::ImageToContourFilter::GenerateData() { mitk::Image::ConstPointer sliceImage = ImageToSurfaceFilter::GetInput(); if (!sliceImage) { MITK_ERROR << "mitk::ImageToContourFilter: No input available. Please set the input!" << std::endl; itkExceptionMacro("mitk::ImageToContourFilter: No input available. Please set the input!"); return; } if (sliceImage->GetDimension() > 2 || sliceImage->GetDimension() < 2) { MITK_ERROR << "mitk::ImageToImageFilter::GenerateData() works only with 2D images. Please assure that your input " "image is 2D!" << std::endl; itkExceptionMacro( "mitk::ImageToImageFilter::GenerateData() works only with 2D images. Please assure that your input image is 2D!"); return; } m_SliceGeometry = sliceImage->GetGeometry(); AccessFixedDimensionByItk(sliceImage, Itk2DContourExtraction, 2); // Setting progressbar if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(this->m_ProgressStepSize); } template void mitk::ImageToContourFilter::Itk2DContourExtraction(const itk::Image *sliceImage) { typedef itk::Image ImageType; typedef itk::ContourExtractor2DImageFilter ContourExtractor; typedef itk::ConstantPadImageFilter PadFilterType; typename PadFilterType::Pointer padFilter = PadFilterType::New(); typename ImageType::SizeType lowerExtendRegion; lowerExtendRegion[0] = 1; lowerExtendRegion[1] = 1; typename ImageType::SizeType upperExtendRegion; upperExtendRegion[0] = 1; upperExtendRegion[1] = 1; + auto filter = itk::UnaryGeneratorImageFilter::New(); + + auto contourValPicker = [this] (TPixel pixVal) + { + return fabs(pixVal-m_ContourValue) < mitk::eps + ? pixVal + : TPixel(); + }; + + filter->SetInput(sliceImage); + filter->SetFunctor(contourValPicker); + filter->Update(); + /* * We need to pad here, since the ITK contour extractor fails if the * segmentation touches more than one image edge. * By padding the image for one row at each edge we overcome this issue */ - padFilter->SetInput(sliceImage); + padFilter->SetInput(filter->GetOutput()); padFilter->SetConstant(0); padFilter->SetPadLowerBound(lowerExtendRegion); padFilter->SetPadUpperBound(upperExtendRegion); typename ContourExtractor::Pointer contourExtractor = ContourExtractor::New(); contourExtractor->SetInput(padFilter->GetOutput()); - contourExtractor->SetContourValue(0.5); - + contourExtractor->SetContourValue(m_ContourValue-1.0); contourExtractor->Update(); unsigned int foundPaths = contourExtractor->GetNumberOfOutputs(); vtkSmartPointer contourSurface = vtkSmartPointer::New(); vtkSmartPointer points = vtkSmartPointer::New(); vtkSmartPointer polygons = vtkSmartPointer::New(); - unsigned int pointId(0); + unsigned int pointId = 0; for (unsigned int i = 0; i < foundPaths; i++) { const ContourPath *currentPath = contourExtractor->GetOutput(i)->GetVertexList(); vtkSmartPointer polygon = vtkSmartPointer::New(); polygon->GetPointIds()->SetNumberOfIds(currentPath->Size()); Point3D currentPoint; Point3D currentWorldPoint; for (unsigned int j = 0; j < currentPath->Size(); j++) { currentPoint[0] = currentPath->ElementAt(j)[0]; currentPoint[1] = currentPath->ElementAt(j)[1]; currentPoint[2] = 0; m_SliceGeometry->IndexToWorld(currentPoint, currentWorldPoint); points->InsertPoint(pointId, currentWorldPoint[0], currentWorldPoint[1], currentWorldPoint[2]); polygon->GetPointIds()->SetId(j, pointId); pointId++; } // for2 polygons->InsertNextCell(polygon); - } // for1 contourSurface->SetPoints(points); contourSurface->SetPolys(polygons); contourSurface->BuildLinks(); Surface::Pointer finalSurface = this->GetOutput(); finalSurface->SetVtkPolyData(contourSurface); -} + } void mitk::ImageToContourFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); } void mitk::ImageToContourFilter::SetUseProgressBar(bool status) { this->m_UseProgressBar = status; } void mitk::ImageToContourFilter::SetProgressStepSize(unsigned int stepSize) { this->m_ProgressStepSize = stepSize; -} +} \ No newline at end of file diff --git a/Modules/Segmentation/Algorithms/mitkImageToContourFilter.h b/Modules/Segmentation/Algorithms/mitkImageToContourFilter.h index 3aff2a90fe..1576816fcb 100644 --- a/Modules/Segmentation/Algorithms/mitkImageToContourFilter.h +++ b/Modules/Segmentation/Algorithms/mitkImageToContourFilter.h @@ -1,91 +1,98 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkImageToContourFilter_h_Included #define mitkImageToContourFilter_h_Included //#include "MitkSBExports.h" #include "itkContourExtractor2DImageFilter.h" #include "itkImage.h" #include "mitkImage.h" #include "mitkImageToSurfaceFilter.h" #include "mitkSurface.h" #include "vtkCellArray.h" #include "vtkPolyData.h" #include "vtkPolygon.h" #include #include "mitkProgressBar.h" namespace mitk { /** \brief A filter that can extract contours out of a 2D binary image This class takes an 2D mitk::Image as input and extracts all contours which are drawn it. The contour extraction is done by using the itk::ContourExtractor2DImageFilter. The output is a mitk::Surface. $Author: fetzer$ */ class MITKSEGMENTATION_EXPORT ImageToContourFilter : public ImageToSurfaceFilter { public: mitkClassMacro(ImageToContourFilter, ImageToSurfaceFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Set macro for the geometry of the slice. If it is not set explicitly the geometry will be taken from the slice \a Parameter The slice`s geometry */ itkSetMacro(SliceGeometry, BaseGeometry *); // typedef unsigned int VDimension; typedef itk::PolyLineParametricPath<2> PolyLineParametricPath2D; typedef PolyLineParametricPath2D::VertexListType ContourPath; /** \brief Set whether the mitkProgressBar should be used \a Parameter true for using the progress bar, false otherwise */ void SetUseProgressBar(bool); /** \brief Set the stepsize which the progress bar should proceed \a Parameter The stepsize for progressing */ void SetProgressStepSize(unsigned int stepSize); + /** + * @brief Set the contour value to be extracted if there are multiple contours + * + */ + itkSetMacro (ContourValue, ScalarType); + protected: ImageToContourFilter(); ~ImageToContourFilter() override; void GenerateData() override; void GenerateOutputInformation() override; private: const BaseGeometry *m_SliceGeometry; bool m_UseProgressBar; unsigned int m_ProgressStepSize; + ScalarType m_ContourValue; template void Itk2DContourExtraction(const itk::Image *sliceImage); }; // class } // namespace #endif diff --git a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp index ea074a7f31..c681bd1c49 100644 --- a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp +++ b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp @@ -1,157 +1,155 @@ /*============================================================================ 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 "mitkShapeBasedInterpolationAlgorithm.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include #include #include #include #include #include mitk::Image::Pointer mitk::ShapeBasedInterpolationAlgorithm::Interpolate( Image::ConstPointer lowerSlice, unsigned int lowerSliceIndex, Image::ConstPointer upperSlice, unsigned int upperSliceIndex, unsigned int requestedIndex, unsigned int /*sliceDimension*/, // commented variables are not used Image::Pointer resultImage, unsigned int /*timeStep*/, Image::ConstPointer /*referenceImage*/) { auto lowerDistanceImage = this->ComputeDistanceMap(lowerSliceIndex, lowerSlice); auto upperDistanceImage = this->ComputeDistanceMap(upperSliceIndex, upperSlice); // calculate where the current slice is in comparison to the lower and upper neighboring slices float ratio = (float)(requestedIndex - lowerSliceIndex) / (float)(upperSliceIndex - lowerSliceIndex); AccessFixedDimensionByItk_3(resultImage, InterpolateIntermediateSlice, 2, upperDistanceImage, lowerDistanceImage, ratio); return resultImage; } mitk::Image::Pointer mitk::ShapeBasedInterpolationAlgorithm::ComputeDistanceMap(unsigned int sliceIndex, Image::ConstPointer slice) { static const auto MAX_CACHE_SIZE = 2 * std::thread::hardware_concurrency(); { std::lock_guard lock(m_DistanceImageCacheMutex); if (0 != m_DistanceImageCache.count(sliceIndex)) return m_DistanceImageCache[sliceIndex]; if (MAX_CACHE_SIZE < m_DistanceImageCache.size()) m_DistanceImageCache.clear(); } mitk::Image::Pointer distanceImage; AccessFixedDimensionByItk_1(slice, ComputeDistanceMap, 2, distanceImage); std::lock_guard lock(m_DistanceImageCacheMutex); m_DistanceImageCache[sliceIndex] = distanceImage; return distanceImage; } template void mitk::ShapeBasedInterpolationAlgorithm::ComputeDistanceMap(const itk::Image *binaryImage, mitk::Image::Pointer &result) { typedef itk::Image DistanceFilterInputImageType; typedef itk::FastChamferDistanceImageFilter DistanceFilterType; typedef itk::IsoContourDistanceImageFilter IsoContourType; typedef itk::InvertIntensityImageFilter InvertIntensityImageFilterType; typedef itk::SubtractImageFilter SubtractImageFilterType; typename DistanceFilterType::Pointer distanceFilter = DistanceFilterType::New(); typename DistanceFilterType::Pointer distanceFilterInverted = DistanceFilterType::New(); typename IsoContourType::Pointer isoContourFilter = IsoContourType::New(); typename IsoContourType::Pointer isoContourFilterInverted = IsoContourType::New(); typename InvertIntensityImageFilterType::Pointer invertFilter = InvertIntensityImageFilterType::New(); typename SubtractImageFilterType::Pointer subtractImageFilter = SubtractImageFilterType::New(); // arbitrary maximum distance int maximumDistance = 100; // this assumes the image contains only 1 and 0 invertFilter->SetInput(binaryImage); invertFilter->SetMaximum(1); // do the processing on the image and the inverted image to get inside and outside distance isoContourFilter->SetInput(binaryImage); isoContourFilter->SetFarValue(maximumDistance + 1); isoContourFilter->SetLevelSetValue(0); isoContourFilterInverted->SetInput(invertFilter->GetOutput()); isoContourFilterInverted->SetFarValue(maximumDistance + 1); isoContourFilterInverted->SetLevelSetValue(0); distanceFilter->SetInput(isoContourFilter->GetOutput()); distanceFilter->SetMaximumDistance(maximumDistance); distanceFilterInverted->SetInput(isoContourFilterInverted->GetOutput()); distanceFilterInverted->SetMaximumDistance(maximumDistance); // inside distance should be negative, outside distance positive subtractImageFilter->SetInput2(distanceFilter->GetOutput()); subtractImageFilter->SetInput1(distanceFilterInverted->GetOutput()); subtractImageFilter->Update(); result = mitk::GrabItkImageMemory(subtractImageFilter->GetOutput()); } template void mitk::ShapeBasedInterpolationAlgorithm::InterpolateIntermediateSlice(itk::Image *result, const mitk::Image::Pointer &lower, const mitk::Image::Pointer &upper, float ratio) { typename DistanceFilterImageType::Pointer lowerITK = DistanceFilterImageType::New(); typename DistanceFilterImageType::Pointer upperITK = DistanceFilterImageType::New(); CastToItkImage(lower, lowerITK); CastToItkImage(upper, upperITK); itk::ImageRegionConstIteratorWithIndex lowerIter(lowerITK, lowerITK->GetLargestPossibleRegion()); lowerIter.GoToBegin(); if (!lowerITK->GetLargestPossibleRegion().IsInside(upperITK->GetLargestPossibleRegion()) || !lowerITK->GetLargestPossibleRegion().IsInside(result->GetLargestPossibleRegion())) { // TODO Exception etc. MITK_ERROR << "The regions of the slices for the 2D interpolation are not equally sized!"; return; } float weight[2] = {1.0f - ratio, ratio}; - while (!lowerIter.IsAtEnd()) { typename DistanceFilterImageType::PixelType lowerPixelVal = lowerIter.Get(); typename DistanceFilterImageType::PixelType upperPixelVal = upperITK->GetPixel(lowerIter.GetIndex()); - typename DistanceFilterImageType::PixelType intermediatePixelVal = (weight[0] * upperPixelVal + weight[1] * lowerPixelVal > 0 ? 0 : 1); result->SetPixel(lowerIter.GetIndex(), static_cast(intermediatePixelVal)); ++lowerIter; } } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index 80f4bcba3a..78bc087643 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,769 +1,799 @@ /*============================================================================ 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 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 #include #include #include #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include #include "mitkAbstractTransformGeometry.h" #include "mitkLabelSetImage.h" #include "mitkContourModelUtils.h" -#include "itkImageRegionIterator.h" +// #include + +#include #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(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(normal.GetVnlVector(), imageNormal0.GetVnlVector())); imageNormal1.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal1.GetVnlVector())); imageNormal2.SetVnlVector(vnl_cross_3d(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(image->GetDimension(affectedDimension))) return false; return true; } void mitk::SegTool2D::UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection) { std::vector slices = { SliceInformation(slice, plane, 0)}; - Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection); + Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection, 0, 0); } void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; - contourInfo.contourNormal = sliceInfo.plane->GetNormal(); - contourInfo.contourPoint = sliceInfo.plane->GetOrigin(); + contourInfo.ContourNormal = sliceInfo.plane->GetNormal(); + contourInfo.ContourPoint = sliceInfo.plane->GetOrigin(); mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, - bool detectIntersection) + bool detectIntersection, + unsigned int activeLayerID, + mitk::Label::PixelType activeLabelValue) { 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 contourList; contourList.reserve(sliceInfos.size()); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); std::vector 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 mitk::Image::Pointer slice2 = sliceInfo.slice->Clone(); mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); contourExtractor->SetInput(slice2); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo); } else { relevantSlices.push_back(sliceInfo); } } } if (relevantSlices.empty()) return; + std::vector contourPlanes; 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); } else { + vtkSmartPointer intArray = vtkSmartPointer::New(); + intArray->InsertNextValue(activeLabelValue); + intArray->InsertNextValue(activeLayerID); + contour->GetVtkPolyData()->GetFieldData()->AddArray(intArray); contour->DisconnectPipeline(); contourList.push_back(contour); + contourPlanes.push_back(sliceInfo.plane); } } - mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList); + + mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList, contourPlanes); } 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 reslice = vtkSmartPointer::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(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(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(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(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); } void mitk::SegTool2D::Deactivated() { this->GetToolManager()->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); Superclass::Deactivated(); } void mitk::SegTool2D::OnTimePointChangedInternal() { if (m_IsTimePointChangeAware && nullptr != this->GetWorkingDataNode()) { const auto 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(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(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(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (planeGeometry && segmentationResult && !abstractTransformGeometry) { const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast(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(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()->GetSlice()->GetPos(); SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); + sliceInfo.slicePosition = currentSlicePosition; WriteBackSegmentationResults({ sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector &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(); - mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); - // the first geometry is needed otherwise restoring the position is not working const auto* plane3 = dynamic_cast(dynamic_cast( m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) ->GetPlaneGeometry(0)); unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetSlice()->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& 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(workingNode->GetData()); + auto image = dynamic_cast(workingNode->GetData()); + + mitk::Label::PixelType activeLabelValue = 0; + unsigned int activeLayerID = 0; + + try{ + auto labelSetImage = dynamic_cast(workingNode->GetData()); + activeLayerID = labelSetImage->GetActiveLayer(); + activeLabelValue = labelSetImage->GetActiveLabelSet()->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()) { - mitk::SegTool2D::WriteSliceToVolume(image, sliceInfo, true); + SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } - mitk::SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false); + SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false, activeLayerID, 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); + + 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(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 reslice = vtkSmartPointer::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(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(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 serviceRef = us::GetModuleContext()->GetServiceReference(); 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); mitk::Point2D p2 = p1; p2[0] -= planeGeometry->GetSpacing()[0]; p2[1] -= planeGeometry->GetSpacing()[1]; contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); contourMarker->SetPlaneGeometry(planeGeometry->Clone()); 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; } void mitk::SegTool2D::WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue) { if (nullptr == targetSlice) { mitkThrow() << "Cannot write preview on working image. Target slice does not point to a valid instance."; } if (nullptr == sourceSlice) { mitkThrow() << "Cannot write preview on working image. Source slice does not point to a valid instance."; } if (nullptr == workingImage) { mitkThrow() << "Cannot write preview on working image. Working image does not point to a valid instance."; } auto constVtkSource = sourceSlice->GetVtkImageData(); /*Need to const cast because Vtk interface does not support const correctly. (or I am not experienced enough to use it correctly)*/ auto nonConstVtkSource = const_cast(constVtkSource); ContourModelUtils::FillSliceInSlice(nonConstVtkSource, targetSlice->GetVtkImageData(), workingImage, paintingPixelValue, 1.0); } 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 ed13ab6d29..823a240b95 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,298 +1,303 @@ /*============================================================================ 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_Included #define mitkSegTool2D_h_Included -#include "mitkCommon.h" -#include "mitkImage.h" -#include "mitkTool.h" +#include +#include +#include #include -#include "mitkInteractionPositionEvent.h" +#include -#include "mitkInteractionConst.h" -#include "mitkPlanePositionManager.h" -#include "mitkRestorePlanePositionOperation.h" +#include +#include +#include #include 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 interpolation by extracting the contour form the given slice. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image * @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 UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, 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 activeLayerID The layer ID of the active label. + * @param activeLabelValue The label value of the active label. * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, - bool detectIntersection); + bool detectIntersection, + unsigned int activeLayerID, + mitk::Label::PixelType activeLabelValue); /** * \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 &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) marke 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& sliceList, bool writeSliceToVolume = true); /** Writes the provided source slice into the target slice with the given pixel value. * If passed workingImage is a LabelSetImage the label set rules will be applied when * writing all non zero source pixels into the target slice (e.g. locked lables will not be touched) * with the given paintingPixelValue. * @param targetSlice Pointer to the slice that should be filled with the content of the sourceSlice. * @param sourceSlice Pointer to the slice that is the source/preview every pixel will be (tried to be) transfered . * @param workingImage Will be used to check if LabeSetImageRules have to be applied and the label set state. * @param paintingPixelValue Value that will be used to paint onto target slice. * @pre targetSlice must point to a valid instance. * @pre sourceSlice must point to a valid instance. * @pre workingImage must point to a valid instance.*/ static void WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue); /** 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); // 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 48b35ba5f7..29beb90c50 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1437 +1,1966 @@ /*============================================================================ 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 "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 "mitkToolManager.h" #include "mitkUndoController.h" + #include +#include #include #include #include #include #include #include #include - #include +#include +#include + +// Includes for the merge operation +#include "mitkImageToContourFilter.h" + #include #include #include #include #include #include #include #include +#include #include #include #include #include namespace { template itk::SmartPointer GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const std::map QmitkSlicesInterpolator::createActionToSliceDimension() { std::map actionToSliceDimension; foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) { actionToSliceDimension[new QAction(QString::fromStdString(slicer->GetViewDirectionAsString()), nullptr)] = slicer; } return actionToSliceDimension; } +// Check whether the given contours are coplanar +bool AreContoursCoplanar(mitk::SurfaceInterpolationController::ContourPositionInformation leftHandSide, + mitk::SurfaceInterpolationController::ContourPositionInformation rightHandSide) +{ + // Here we check two things: + // 1. Whether the normals of both contours are at least parallel + // 2. Whether both contours lie in the same plane + + // Check for coplanarity: + // a. Span a vector between two points one from each contour + // b. Calculate dot product for the vector and one of the normals + // c. If the dot is zero the two vectors are orthogonal and the contours are coplanar + + double vec[3]; + vec[0] = leftHandSide.ContourPoint[0] - rightHandSide.ContourPoint[0]; + vec[1] = leftHandSide.ContourPoint[1] - rightHandSide.ContourPoint[1]; + vec[2] = leftHandSide.ContourPoint[2] - rightHandSide.ContourPoint[2]; + double n[3]; + n[0] = rightHandSide.ContourNormal[0]; + n[1] = rightHandSide.ContourNormal[1]; + n[2] = rightHandSide.ContourNormal[2]; + double dot = vtkMath::Dot(n, vec); + + double n2[3]; + n2[0] = leftHandSide.ContourNormal[0]; + n2[1] = leftHandSide.ContourNormal[1]; + n2[2] = leftHandSide.ContourNormal[2]; + + // The normals of both contours have to be parallel but not of the same orientation + double lengthLHS = leftHandSide.ContourNormal.GetNorm(); + double lengthRHS = rightHandSide.ContourNormal.GetNorm(); + double dot2 = vtkMath::Dot(n, n2); + bool contoursParallel = mitk::Equal(fabs(lengthLHS * lengthRHS), fabs(dot2), 0.001); + + if (mitk::Equal(dot, 0.0, 0.001) && contoursParallel) + return true; + else + return false; +} + +mitk::Image::Pointer ExtractSliceFromImage(mitk::Image* image, + const mitk::PlaneGeometry * contourPlane, + unsigned int timeStep) +{ + vtkSmartPointer reslice = vtkSmartPointer::New(); + // set to false to extract a slice + reslice->SetOverwriteMode(false); + reslice->Modified(); + + mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); + extractor->SetInput(image); + extractor->SetTimeStep(timeStep); + extractor->SetWorldGeometry(contourPlane); + extractor->SetVtkOutputRequest(false); + extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); + extractor->Update(); + mitk::Image::Pointer slice = extractor->GetOutput(); + return slice; +} + + +template +std::vector GetPixelValuesPresentInImage(mitk::LabelSetImage* labelSetImage) +{ + std::vector pixelsPresent; + mitk::ImagePixelReadAccessor readAccessor(labelSetImage); + + std::size_t numberOfPixels = 1; + for (size_t dim = 0; dim < VImageDimension; ++dim) + numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); + + auto src = readAccessor.GetData(); + for (std::size_t i = 0; i < numberOfPixels; ++i) + { + mitk::Label::PixelType pixelVal = *(src + i); + if ( (std::find(pixelsPresent.begin(), pixelsPresent.end(), pixelVal) == pixelsPresent.end()) && (pixelVal != labelSetImage->GetExteriorLabel()->GetValue()) ) + pixelsPresent.push_back(pixelVal); + } + return pixelsPresent; +} + + +template +ModifyLabelActionTrigerred ModifyLabelProcessing(mitk::LabelSetImage* labelSetImage, + mitk::SurfaceInterpolationController::Pointer surfaceInterpolator, + unsigned int timePoint) +{ + auto currentLayerID = labelSetImage->GetActiveLayer(); + auto numTimeSteps = labelSetImage->GetTimeSteps(); + + ModifyLabelActionTrigerred actionTriggered = ModifyLabelActionTrigerred::Null; + mitk::SurfaceInterpolationController::ContourPositionInformationList ¤tContourList = + surfaceInterpolator->GetContours(timePoint, currentLayerID); + + mitk::LabelSetImage::Pointer labelSetImage2 = labelSetImage->Clone(); + + mitk::ImagePixelReadAccessor readAccessor(labelSetImage2.GetPointer()); + + for (auto& contour : currentContourList) + { + mitk::Label::PixelType contourPixelValue; + + itk::Index<3> itkIndex; + labelSetImage2->GetGeometry()->WorldToIndex(contour.ContourPoint, itkIndex); + if (VImageDimension == 4) + { + itk::Index time3DIndex; + for (size_t i = 0; i < itkIndex.size(); ++i) + time3DIndex[i] = itkIndex[i]; + time3DIndex[3] = timePoint; + contourPixelValue = readAccessor.GetPixelByIndexSafe(time3DIndex); + } + else if (VImageDimension == 3) + { + itk::Index geomIndex; + for (size_t i = 0; i < itkIndex.size(); ++i) + geomIndex[i] = itkIndex[i]; + contourPixelValue = readAccessor.GetPixelByIndexSafe(geomIndex); + } + + if (contour.LabelValue != contourPixelValue) + { + if (contourPixelValue == 0) // Erase label + { + for (size_t t = 0; t < numTimeSteps; ++t) + surfaceInterpolator->RemoveContours(contour.LabelValue, t, currentLayerID); + actionTriggered = ModifyLabelActionTrigerred::Erase; + } + else + { + contour.LabelValue = contourPixelValue; + actionTriggered = ModifyLabelActionTrigerred::Merge; + } + } + } + return actionTriggered; +} + QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), // ACTION_TO_SLICEDIMENSION( createActionToSliceDimension() ), 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())); - // T28261 - // connect(m_BtnSuggestPlane, SIGNAL(clicked()), this, SLOT(OnSuggestPlaneClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); auto command3 = itk::ReceptorMemberCommand::New(); command3->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationAborted); InterpolationAbortedObserverTag = m_Interpolator->AddObserver(itk::AbortEvent(), command3); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); m_3DContourNode = mitk::DataNode::New(); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(0.0, 0.0, 0.0)); m_3DContourNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("name", mitk::StringProperty::New("Drawn Contours")); m_3DContourNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); m_3DContourNode->SetProperty("material.wireframeLineWidth", mitk::FloatProperty::New(2.0f)); m_3DContourNode->SetProperty("3DContourContainer", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_3DContourNode->SetVisibility(false); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } + // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList &controllers) { Q_ASSERT(!controllers.empty()); if (m_Initialized) { // remove old observers - Uninitialize(); + this->Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); // connect to the slice navigation controller. after each change, call the interpolator foreach (mitk::SliceNavigationController *slicer, controllers) { // Has to be initialized m_LastSNC = slicer; m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); - m_ControllerToDeleteObserverTag.insert(slicer, slicer->AddObserver(itk::DeleteEvent(), deleteCommand)); + m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); - m_ControllerToTimeObserverTag.insert( - slicer, slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand)); + m_ControllerToTimeObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); - m_ControllerToSliceObserverTag.insert( - slicer, slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand)); + m_ControllerToSliceObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand); } + ACTION_TO_SLICEDIMENSION = createActionToSliceDimension(); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } - foreach (mitk::SliceNavigationController *slicer, m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } - ACTION_TO_SLICEDIMENSION.clear(); + auto dataIter = m_SegmentationObserverTags.begin(); + while (dataIter != m_SegmentationObserverTags.end()) + { + auto labelSetImage = (*dataIter).first; + labelSetImage->RemoveObserver((*dataIter).second); + for (size_t layerID = 0; layerID < labelSetImage->GetNumberOfLayers(); ++layerID) + { + this->OnRemoveLabelSetConnection(labelSetImage, layerID); + } + ++dataIter; + } + m_SegmentationObserverTags.clear(); + ACTION_TO_SLICEDIMENSION.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers - Uninitialize(); + this->Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_3DContourNode)) m_DataStorage->Remove(m_3DContourNode); 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->UnsetSelectedImage(); + 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); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); + try { + if (m_SegmentationObserverTags.find(labelSetImage) == m_SegmentationObserverTags.end()) + { + auto command2 = itk::MemberCommand::New(); + command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnModifyLabelChanged); + auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + m_SegmentationObserverTags[workingImage] = workingImage->AddObserver(itk::ModifiedEvent(), command2); + } + } + catch (const std::exception& e) + { + MITK_ERROR << "Error casting node data to LabelSetImage\n"; + } } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_3DContourNode); m_3DContourNode->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 - SetCurrentContourListID(); + this->SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } - this->CheckSupportedImageDimension(); } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); const auto timePoint = slicer->GetSelectedTimePoint(); m_TimePoints[slicer] = timePoint; - m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); + if (m_Watcher.isRunning()) + m_Watcher.waitForFinished(); + + if (timePoint != m_SurfaceInterpolator->GetCurrentTimePoint()) + { + m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); + if (m_3DInterpolationEnabled) + { + m_3DContourNode->SetData(nullptr); + m_InterpolatedSurfaceNode->SetData(nullptr); + } + m_SurfaceInterpolator->Modified(); + } if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); + if(m_2DInterpolationEnabled) + { + this->On2DInterpolationEnabled(m_2DInterpolationEnabled); + } if (TranslateAndInterpolateChangedSlice(e, slicer)) { slicer->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (!m_2DInterpolationEnabled) return false; try { const mitk::SliceNavigationController::GeometrySliceEvent &event = dynamic_cast(e); mitk::TimeGeometry *tsg = event.GetTimeGeometry(); if (tsg && m_TimePoints.contains(slicer) && tsg->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = dynamic_cast(tsg->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); + if (slicedGeometry) { m_LastSNC = slicer; mitk::PlaneGeometry *plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(event.GetPos())); if (plane) + { Interpolate(plane, m_TimePoints[slicer], slicer); + } return true; } } } catch (const std::bad_cast &) { return false; // so what } return false; } +void QmitkSlicesInterpolator::OnLayerChanged() +{ + auto* workingNode = m_ToolManager->GetWorkingData(0); + + if (workingNode != nullptr) + { + m_3DContourNode->SetData(nullptr); + this->Show3DInterpolationResult(false); + } + + 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::Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { if (m_ToolManager) { mitk::DataNode *node = m_ToolManager->GetWorkingData(0); if (node) { m_Segmentation = dynamic_cast(node->GetData()); + if (m_Segmentation) { if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot interpolate segmentation. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); - int clickedSliceDimension(-1); - int clickedSliceIndex(-1); + 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 activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabelSet()->GetActiveLabel()->GetColor(); + m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); + } + } + m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } } } } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(); + mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); - if (interpolatedSurface.IsNotNull() && workingNode && workingNode->IsVisible(nullptr)) + 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 && + workingNode->IsVisible( + mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2")))) { m_BtnApply3D->setEnabled(true); // T28261 // m_BtnSuggestPlane->setEnabled(true); m_InterpolatedSurfaceNode->SetData(interpolatedSurface); + m_3DContourNode->SetData(m_SurfaceInterpolator->GetContoursAsSurface()); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } - if (!m_DataStorage->Exists(m_3DContourNode)) - { - m_DataStorage->Add(m_3DContourNode, workingNode); - } } else if (interpolatedSurface.IsNull()) { m_BtnApply3D->setEnabled(false); // T28261 // m_BtnSuggestPlane->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } m_BtnReinit3DInterpolation->setEnabled(true); foreach (mitk::SliceNavigationController *slicer, m_ControllerToTimeObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } + m_SurfaceInterpolator->ReinitializeInterpolation(); } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { if (m_Segmentation && m_FeedbackNode->GetData()) { // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set slice as input mitk::Image::Pointer slice = dynamic_cast(m_FeedbackNode->GetData()); reslice->SetInputSlice(slice->GetSliceData()->GetVtkImageAccessor(slice)->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } + mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(m_Segmentation); const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(m_LastSNC->GetCurrentPlaneGeometry()); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); - extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so m_Segmentation->Modified(); m_Segmentation->GetVtkImageData()->Modified(); m_FeedbackNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } 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; const auto timePoint = slicer->GetSelectedTimePoint(); if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } + mitk::Image::Pointer activeLabelImage; + try + { + auto labelSetImage = dynamic_cast(m_Segmentation); + activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(), true, 0); + } + catch (const std::exception& e) + { + MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; + } + + m_Interpolator->SetSegmentationVolume(activeLabelImage); + timeStep = geometry->TimePointToTimeStep(timePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // Create an empty diff image for the undo operation auto diffImage = mitk::Image::New(); diffImage->Initialize(segmentation3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::IOComponentEnum::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension = -1; int sliceIndex = -1; mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); const auto numSlices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); std::atomic_uint totalChangedSlices; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); + // const auto numThreads = 1; std::vector> sliceIndices(numThreads); for (std::remove_const_t sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); std::vector threads; threads.reserve(numThreads); // This lambda will be executed by the threads auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { auto clonedPlaneGeometry = planeGeometry->Clone(); auto origin = clonedPlaneGeometry->GetOrigin(); + // Go through the sliced indices for (auto sliceIndex : sliceIndices[threadIndex]) { slicedGeometry->WorldToIndex(origin, origin); origin[sliceDimension] = sliceIndex; slicedGeometry->IndexToWorld(origin, origin); clonedPlaneGeometry->SetOrigin(origin); auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); if (interpolation.IsNotNull()) { // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume auto reslicer = vtkSmartPointer::New(); // Set overwrite mode to true to write back to the image volume reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); reslicer->SetOverwriteMode(true); reslicer->Modified(); auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); diffSliceWriter->SetInput(diffImage); diffSliceWriter->SetTimeStep(0); diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); diffSliceWriter->SetVtkOutputRequest(true); diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); diffSliceWriter->Modified(); diffSliceWriter->Update(); ++totalChangedSlices; } mitk::ProgressBar::GetInstance()->Progress(); } }; - m_Interpolator->EnableSliceImageCache(); - for (std::remove_const_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) - threads.emplace_back(interpolate, threadIndex); // Run the interpolation - - for (auto& thread : threads) - thread.join(); + // Do the interpolation here. + for (size_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) + { + interpolate(threadIndex); + } m_Interpolator->DisableSliceImageCache(); + const mitk::Label::PixelType newDestinationLabel = dynamic_cast(m_Segmentation)->GetActiveLabelSet()->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); std::map::const_iterator it; for (it = ACTION_TO_SLICEDIMENSION.begin(); it != ACTION_TO_SLICEDIMENSION.end(); it++) orientationPopup.addAction(it->first); connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); - orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); + + auto labelSetImage = dynamic_cast(segmentationDataNode->GetData()); + auto activeLabelColor = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetColor(); + std::string activeLabelName = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetName(); + auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(timePoint) || !segmentationGeometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::IOComponentEnum::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); - - auto timeStep = interpolatedSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); - mitk::ImageReadAccessor readAccessor(interpolatedSegmentation, interpolatedSegmentation->GetVolumeData(timeStep)); - const auto* dataPointer = readAccessor.GetData(); - - if (nullptr == dataPointer) - return; - - timeStep = segmentationGeometry->TimePointToTimeStep(timePoint); - segmentation->SetVolume(dataPointer, timeStep, 0); - - m_CmbInterpolation->setCurrentIndex(0); + auto timeStep = segmentationGeometry->TimePointToTimeStep(timePoint); + const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); + + TransferLabelContent( + interpolatedSegmentation, + labelSetImage, + labelSetImage->GetActiveLabelSet(), + 0, + 0, + false, + {{1, newDestinationLabel}}, + mitk::MultiLabelSegmentation::MergeStyle::Merge, + mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks, + timeStep); + + // m_CmbInterpolation->setCurrentIndex(0); this->Show3DInterpolationResult(false); - std::string name = segmentationDataNode->GetName() + "_3D-interpolation"; + std::string name = segmentationDataNode->GetName() + " 3D-interpolation - " + activeLabelName; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); - std::array rgb; - segmentationDataNode->GetColor(rgb.data()); - interpolatedSurfaceDataNode->SetColor(rgb.data()); - + interpolatedSurfaceDataNode->SetColor(activeLabelColor); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } -void ::QmitkSlicesInterpolator::OnSuggestPlaneClicked() -{ - if (m_PlaneWatcher.isRunning()) - m_PlaneWatcher.waitForFinished(); - m_PlaneFuture = QtConcurrent::run(this, &QmitkSlicesInterpolator::RunPlaneSuggestion); - m_PlaneWatcher.setFuture(m_PlaneFuture); -} - -void ::QmitkSlicesInterpolator::RunPlaneSuggestion() -{ - if (m_FirstRun) - mitk::ProgressBar::GetInstance()->AddStepsToDo(7); - else - mitk::ProgressBar::GetInstance()->AddStepsToDo(3); - - m_EdgeDetector->SetSegmentationMask(m_Segmentation); - m_EdgeDetector->SetInput(dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData())); - m_EdgeDetector->Update(); - - mitk::UnstructuredGrid::Pointer uGrid = mitk::UnstructuredGrid::New(); - uGrid->SetVtkUnstructuredGrid(m_EdgeDetector->GetOutput()->GetVtkUnstructuredGrid()); - - mitk::ProgressBar::GetInstance()->Progress(); - - mitk::Surface::Pointer surface = dynamic_cast(m_InterpolatedSurfaceNode->GetData()); - - vtkSmartPointer vtkpoly = surface->GetVtkPolyData(); - vtkSmartPointer vtkpoints = vtkpoly->GetPoints(); - - vtkSmartPointer vGrid = vtkSmartPointer::New(); - vtkSmartPointer verts = vtkSmartPointer::New(); - - verts->GetPointIds()->SetNumberOfIds(vtkpoints->GetNumberOfPoints()); - for (int i = 0; i < vtkpoints->GetNumberOfPoints(); i++) - { - verts->GetPointIds()->SetId(i, i); - } - - vGrid->Allocate(1); - vGrid->InsertNextCell(verts->GetCellType(), verts->GetPointIds()); - vGrid->SetPoints(vtkpoints); - - mitk::UnstructuredGrid::Pointer interpolationGrid = mitk::UnstructuredGrid::New(); - interpolationGrid->SetVtkUnstructuredGrid(vGrid); - - m_PointScorer->SetInput(0, uGrid); - m_PointScorer->SetInput(1, interpolationGrid); - m_PointScorer->Update(); - - mitk::UnstructuredGrid::Pointer scoredGrid = mitk::UnstructuredGrid::New(); - scoredGrid = m_PointScorer->GetOutput(); - - mitk::ProgressBar::GetInstance()->Progress(); - - double spacing = mitk::SurfaceInterpolationController::GetInstance()->GetDistanceImageSpacing(); - mitk::UnstructuredGridClusteringFilter::Pointer clusterFilter = mitk::UnstructuredGridClusteringFilter::New(); - clusterFilter->SetInput(scoredGrid); - clusterFilter->SetMeshing(false); - clusterFilter->SetMinPts(4); - clusterFilter->Seteps(spacing); - clusterFilter->Update(); - - mitk::ProgressBar::GetInstance()->Progress(); - - // Create plane suggestion - // TODO: "stdmulti.widget0" needs to be removed, see T29203 - // Currently this feature is disabled, so we don't care, see T28261 - mitk::BaseRenderer::Pointer br = - mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); - mitk::PlaneProposer planeProposer; - std::vector grids = clusterFilter->GetAllClusters(); - - planeProposer.SetUnstructuredGrids(grids); - mitk::SliceNavigationController::Pointer snc = br->GetSliceNavigationController(); - planeProposer.SetSliceNavigationController(snc); - planeProposer.SetUseDistances(true); - try - { - planeProposer.CreatePlaneInfo(); - } - catch (const mitk::Exception &e) - { - MITK_ERROR << e.what(); - } - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - m_FirstRun = false; -} void QmitkSlicesInterpolator::OnReinit3DInterpolation() { + // Step 1. Load from the isContourPlaneGeometry nodes the contourNodes. mitk::NodePredicateProperty::Pointer pred = - mitk::NodePredicateProperty::New("3DContourContainer", mitk::BoolProperty::New(true)); + 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) { - m_BtnApply3D->setEnabled(true); - m_3DContourNode = contourNodes->at(0); - mitk::Surface::Pointer contours = dynamic_cast(m_3DContourNode->GetData()); - if (contours) - mitk::SurfaceInterpolationController::GetInstance()->ReinitializeInterpolation(contours); - m_BtnReinit3DInterpolation->setEnabled(false); + std::vector contourPlanes; + std::vector contourList; + if (m_ToolManager->GetWorkingData(0) != nullptr) + { + try + { + auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + auto activeLayerID = labelSetImage->GetActiveLayer(); + const auto timePoint = m_LastSNC->GetSelectedTimePoint(); + if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_ERROR << "Invalid time point requested for interpolation pipeline."; + return; + } + + // Adding layer, label and timeStep information for the contourNodes. + for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) + { + auto contourNode = it->Value(); + auto layerID = dynamic_cast(contourNode->GetProperty("layerID"))->GetValue(); + auto labelID = dynamic_cast(contourNode->GetProperty("labelID"))->GetValue(); + auto timeStep = dynamic_cast(contourNode->GetProperty("timeStep"))->GetValue(); + + auto px = dynamic_cast(contourNode->GetProperty("px"))->GetValue(); + auto py = dynamic_cast(contourNode->GetProperty("py"))->GetValue(); + auto pz = dynamic_cast(contourNode->GetProperty("pz"))->GetValue(); + + // auto layerImage = labelSetImage->GetLayerImage(layerID); + + auto planeGeometry = dynamic_cast(contourNode->GetData())->GetPlaneGeometry(); + labelSetImage->SetActiveLayer(layerID); + auto sliceImage = ExtractSliceFromImage(labelSetImage, planeGeometry, timeStep); + labelSetImage->SetActiveLayer(activeLayerID); + 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; + + vtkSmartPointer intArray = vtkSmartPointer::New(); + intArray->InsertNextValue(labelID); + intArray->InsertNextValue(layerID); + intArray->InsertNextValue(timeStep); + contour->GetVtkPolyData()->GetFieldData()->AddArray(intArray); + vtkSmartPointer doubleArray = vtkSmartPointer::New(); + doubleArray->InsertNextValue(px); + doubleArray->InsertNextValue(py); + doubleArray->InsertNextValue(pz); + contour->GetVtkPolyData()->GetFieldData()->AddArray(doubleArray); + contour->DisconnectPipeline(); + contourList.push_back(contour); + contourPlanes.push_back(planeGeometry); + } + labelSetImage->SetActiveLayer(activeLayerID); + // size_t activeLayer = labelSetImage->GetActiveLayer(); + for (size_t l = 0; l < labelSetImage->GetNumberOfLayers(); ++l) + { + this->OnAddLabelSetConnection(l); + } + // labelSetImage->SetActiveLayer(activeLayer); + m_SurfaceInterpolator->CompleteReinitialization(contourList, contourPlanes); + } + 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 { std::map::const_iterator iter = ACTION_TO_SLICEDIMENSION.find(action); if (iter != ACTION_TO_SLICEDIMENSION.end()) { mitk::SliceNavigationController *slicer = iter->second; 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(); // additional error message on std::cerr 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) { mitk::Image *segmentation = dynamic_cast(workingNode->GetData()); + + mitk::Image::Pointer activeLabelImage; + try + { + auto labelSetImage = dynamic_cast(workingNode->GetData()); + activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(), true, 0); + } + catch (const std::exception& e) + { + MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; + } + if (segmentation) { - m_Interpolator->SetSegmentationVolume(segmentation); + m_Interpolator->SetSegmentationVolume(activeLabelImage); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } - - UpdateVisibleSuggestion(); + this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { m_SurfaceInterpolator->Interpolate(); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { - m_Timer->stop(); - m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); + if(m_ToolManager) + { + auto* workingNode = m_ToolManager->GetWorkingData(0); + auto activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabelSet()->GetActiveLabel()->GetColor(); + m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); + m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); + } - mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_3DWINDOWS); + m_Timer->stop(); + mitk::RenderingManager::GetInstance()->RequestUpdate( + mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); - if (currentColor[2] == SURFACE_COLOR_RGB[2]) - { - m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(1.0f, 1.0f, 1.0f)); - } - else - { 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) +void QmitkSlicesInterpolator::PrepareInputsFor3DInterpolation() { - m_3DInterpolationEnabled = on; - - this->CheckSupportedImageDimension(); - try + if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) { - if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) + auto *workingNode = m_ToolManager->GetWorkingData(0); + if (workingNode != nullptr) { - mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); - - if (nullptr != workingNode) + if ((workingNode->IsVisible(mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))))) { - if (workingNode->IsVisible(nullptr)) + int ret = QMessageBox::Yes; + + if (m_SurfaceInterpolator->EstimatePortionOfNeededMemory() > 0.5) { - int ret = QMessageBox::Yes; + QMessageBox msgBox; + msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); + msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); + msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); + ret = msgBox.exec(); + } - if (m_SurfaceInterpolator->EstimatePortionOfNeededMemory() > 0.5) - { - QMessageBox msgBox; - msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); - msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); - msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); - ret = msgBox.exec(); - } + auto labelSetImage = dynamic_cast(workingNode->GetData()); + auto activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); - if (m_Watcher.isRunning()) - m_Watcher.waitForFinished(); + m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(activeLabel); - if (ret == QMessageBox::Yes) - { - m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); - m_Watcher.setFuture(m_Future); - } - else - { - m_CmbInterpolation->setCurrentIndex(0); - } + if (m_Watcher.isRunning()) + m_Watcher.waitForFinished(); + + if (ret == QMessageBox::Yes) + { + // Maybe set the segmentation node here + m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); + m_Watcher.setFuture(m_Future); + } + else + { + m_CmbInterpolation->setCurrentIndex(0); } - } - else - { - QWidget::setEnabled(false); - m_ChkShowPositionNodes->setEnabled(m_3DInterpolationEnabled); } } - if (!m_3DInterpolationEnabled) + else { - this->Show3DInterpolationResult(false); - m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); - - // T28261 - // m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); + QWidget::setEnabled(false); + m_ChkShowPositionNodes->setEnabled(m_3DInterpolationEnabled); } + + } + if (!m_3DInterpolationEnabled) + { + this->Show3DInterpolationResult(false); + m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); + // T28261 + // m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); + } + + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +} + +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 - On3DInterpolationActivated(on); + 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 - UpdateVisibleSuggestion(); + 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) { - if (m_Watcher.isRunning()) - m_Watcher.waitForFinished(); + m_3DContourNode->SetData(nullptr); + m_InterpolatedSurfaceNode->SetData(nullptr); + auto *workingNode = m_ToolManager->GetWorkingData(0); + auto labelSetImage = dynamic_cast(workingNode->GetData()); + auto activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); + m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(activeLabel); 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); + try{ + auto labelSetImage = dynamic_cast(workingNode->GetData()); + for (size_t layerID = 0; layerID < labelSetImage->GetNumberOfLayers(); ++layerID) + { + this->OnAddLabelSetConnection(layerID); + } + } + catch (std::exception &e) + { + MITK_ERROR << e.what() << "\n"; + } + if (workingNode) { QWidget::setEnabled(true); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); // 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(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } + + // Sets up the surface interpolator to accept time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::Vector3D spacing = workingNode->GetData()->GetGeometry(time_position)->GetSpacing(); - double minSpacing(100); - double maxSpacing(0); + 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); - mitk::Image *segmentationImage = dynamic_cast(workingNode->GetData()); + mitk::Image::Pointer segmentationImage; + segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); - - if (m_3DInterpolationEnabled) - { - if (m_Watcher.isRunning()) - m_Watcher.waitForFinished(); - m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); - m_Watcher.setFuture(m_Future); - } } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); if (m_3DContourNode.IsNotNull()) { auto allRenderWindows = mitk::BaseRenderer::GetAll3DRenderWindows(); for (auto mapit = allRenderWindows.begin(); mapit != allRenderWindows.end(); ++mapit) { m_3DContourNode->SetVisibility(status, mapit->second); } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } +void QmitkSlicesInterpolator::OnActiveLabelChanged(mitk::Label::PixelType) +{ + m_3DContourNode->SetData(nullptr); + m_FeedbackNode->SetData(nullptr); + m_InterpolatedSurfaceNode->SetData(nullptr); + + if (m_Watcher.isRunning()) + m_Watcher.waitForFinished(); + + if (m_3DInterpolationEnabled) + { + m_SurfaceInterpolator->Modified(); + } + + if (m_2DInterpolationEnabled) + { + m_FeedbackNode->SetData(nullptr); + this->OnInterpolationActivated(true); + + m_LastSNC->SendSlice(); + } + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + this->UpdateVisibleSuggestion(); +} + void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) + { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - /*if (m_3DInterpolationEnabled && m_Segmentation && m_Segmentation->GetDimension() != 3) - { - QMessageBox info; - info.setWindowTitle("3D Interpolation Process"); - info.setIcon(QMessageBox::Information); - info.setText("3D Interpolation is only supported for 3D images at the moment!"); - info.exec(); - m_CmbInterpolation->setCurrentIndex(0); - }*/ + if (m_3DInterpolationEnabled && m_Segmentation && ((m_Segmentation->GetDimension() != 3) || (m_Segmentation->GetDimension() != 4)) ) + { + QMessageBox info; + info.setWindowTitle("3D Interpolation Process"); + info.setIcon(QMessageBox::Information); + info.setText("3D Interpolation is only supported for 3D/4D images at the moment!"); + info.exec(); + m_CmbInterpolation->setCurrentIndex(0); + } + } } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToTimeObserverTag.remove(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_3DContourNode || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } + +void QmitkSlicesInterpolator::OnAddLabelSetConnection(unsigned int layerID) +{ + if (m_ToolManager->GetWorkingData(0) != nullptr) + { + try + { + auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + auto labelSet = workingImage->GetLabelSet(layerID); + labelSet->RemoveLabelEvent += mitk::MessageDelegate( + this, &QmitkSlicesInterpolator::OnRemoveLabel); + labelSet->ActiveLabelEvent += mitk::MessageDelegate1( + this, &QmitkSlicesInterpolator::OnActiveLabelChanged); + workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( + this, &QmitkSlicesInterpolator::OnLayerChanged); + m_SurfaceInterpolator->AddLabelSetConnection(layerID); + } + catch(const std::exception& e) + { + MITK_ERROR << e.what() << '\n'; + } + } +} + + + +void QmitkSlicesInterpolator::OnAddLabelSetConnection() +{ + if (m_ToolManager->GetWorkingData(0) != nullptr) + { + try + { + auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( + this, &QmitkSlicesInterpolator::OnRemoveLabel); + workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1( + this, &QmitkSlicesInterpolator::OnActiveLabelChanged); + workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( + this, &QmitkSlicesInterpolator::OnLayerChanged); + m_SurfaceInterpolator->AddLabelSetConnection(); + } + catch(const std::exception& e) + { + MITK_ERROR << e.what() << '\n'; + } + } +} + +void QmitkSlicesInterpolator::OnRemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID) +{ + size_t previousLayerID = labelSetImage->GetActiveLayer(); + labelSetImage->SetActiveLayer(layerID); + labelSetImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( + this, &QmitkSlicesInterpolator::OnRemoveLabel); + labelSetImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( + this, &QmitkSlicesInterpolator::OnActiveLabelChanged); + labelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( + this, &QmitkSlicesInterpolator::OnLayerChanged); + m_SurfaceInterpolator->RemoveLabelSetConnection(labelSetImage, layerID); + labelSetImage->SetActiveLayer(previousLayerID); +} + +void QmitkSlicesInterpolator::OnRemoveLabelSetConnection() +{ + if (m_ToolManager->GetWorkingData(0) != nullptr) + { + try + { + auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( + this, &QmitkSlicesInterpolator::OnRemoveLabel); + workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( + this, &QmitkSlicesInterpolator::OnActiveLabelChanged); + workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( + this, &QmitkSlicesInterpolator::OnLayerChanged); + } + catch(const std::exception& e) + { + MITK_ERROR << e.what() << '\n'; + } + } +} + +void QmitkSlicesInterpolator::OnRemoveLabel() +{ + if (m_ToolManager->GetWorkingData(0) != nullptr) + { + try + { + auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + auto currentLayerID = labelSetImage->GetActiveLayer(); + auto numTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); + for (size_t t = 0; t < numTimeSteps; ++t) + { + m_SurfaceInterpolator->RemoveContours(m_PreviousActiveLabelValue,t,currentLayerID); + } + } + catch(const std::exception& e) + { + MITK_ERROR << "Bad cast error for labelSetImage"; + } + } +} + +void QmitkSlicesInterpolator::OnModifyLabelChanged(const itk::Object *caller, + const itk::EventObject & /*event*/) +{ + auto *tempImage = dynamic_cast(const_cast(caller) ) ; + if( tempImage == nullptr) + { + MITK_ERROR << "Unable to cast caller to LabelSetImage."; + return; + } + + ModifyLabelActionTrigerred actionTriggered = ModifyLabelActionTrigerred::Null; + if(m_ToolManager->GetWorkingData(0) != nullptr) + { + auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + if (labelSetImage == tempImage) + { + const auto timePoint = m_LastSNC->GetSelectedTimePoint(); + if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) + { + MITK_ERROR << "Invalid time point requested for interpolation pipeline."; + return; + } + auto timeStep = labelSetImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); + + auto numLayersInCurrentSegmentation = m_SurfaceInterpolator->GetNumberOfLayersInCurrentSegmentation(); + // This handles the add layer or remove layer operation. + if (labelSetImage->GetNumberOfLayers() != numLayersInCurrentSegmentation) + { + bool addLayer = (labelSetImage->GetNumberOfLayers() == (numLayersInCurrentSegmentation +1) ); + bool removeLayer = (labelSetImage->GetNumberOfLayers() == (numLayersInCurrentSegmentation - 1) ); + + m_SurfaceInterpolator->SetNumberOfLayersInCurrentSegmentation(labelSetImage->GetNumberOfLayers()); + + if (addLayer) + { + m_SurfaceInterpolator->OnAddLayer(); + this->OnAddLabelSetConnection(); + } + if (removeLayer) + { + m_SurfaceInterpolator->OnRemoveLayer(); + } + return; + } + + // Get the pixels present in the image. + // This portion of the code deals with the merge and erase labels operations. + auto imageDimension = labelSetImage->GetDimension(); + if (imageDimension == 4) + { + actionTriggered = ModifyLabelProcessing<4>(labelSetImage, m_SurfaceInterpolator, timeStep); + } + else + { + actionTriggered = ModifyLabelProcessing<3>(labelSetImage, m_SurfaceInterpolator, timeStep); + } + + if (actionTriggered == ModifyLabelActionTrigerred::Erase) + { + m_InterpolatedSurfaceNode->SetData(nullptr); + } + + auto currentLayerID = labelSetImage->GetActiveLayer(); + if (actionTriggered == ModifyLabelActionTrigerred::Merge) + { + this->MergeContours(timeStep, currentLayerID); + m_SurfaceInterpolator->Modified(); + } + } + } +} + +void QmitkSlicesInterpolator::MergeContours(unsigned int timeStep, + unsigned int layerID) +{ + std::vector& contours = + m_SurfaceInterpolator->GetContours(timeStep,layerID); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + for (size_t i = 0; i < contours.size(); ++i) + { + for (size_t j = i+1; j < contours.size(); ++j) + { + // And Labels are the same and Layers are the same. + bool areContoursCoplanar = AreContoursCoplanar(contours[i],contours[j]); + + if ( areContoursCoplanar && (contours[i].LabelValue == contours[j].LabelValue) ) + { + // Update the contour by re-extracting the slice from the corresponding plane. + mitk::Image::Pointer slice = ExtractSliceFromImage(m_Segmentation, contours[i].plane, timeStep); + mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); + contourExtractor->SetInput(slice); + contourExtractor->SetContourValue(contours[i].LabelValue); + contourExtractor->Update(); + mitk::Surface::Pointer contour = contourExtractor->GetOutput(); + contours[i].Contour = contour; + + // Update the interior point of the contour + contours[i].ContourPoint = m_SurfaceInterpolator->ComputeInteriorPointOfContour(contours[i],dynamic_cast(m_Segmentation)); + + // Setting the contour polygon data to an empty vtkPolyData, + // as source label is empty after merge operation. + contours[j].Contour->SetVtkPolyData(vtkSmartPointer::New()); + } + } + } + + auto segmentationNode = m_SurfaceInterpolator->GetSegmentationImageNode(); + + if (segmentationNode == nullptr) + { + MITK_ERROR << "segmentation Image Node not found\n"; + } + + auto isContourPlaneGeometry = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); + + mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = + m_DataStorage->GetDerivations(segmentationNode, isContourPlaneGeometry); + + // Remove empty contour nodes. + auto isContourEmpty = [] (const mitk::SurfaceInterpolationController::ContourPositionInformation& contour) + { + return (contour.Contour->GetVtkPolyData()->GetNumberOfPoints() == 0); + }; + + auto it = std::remove_if(contours.begin(), contours.end(), isContourEmpty); + contours.erase(it, contours.end()); +} \ No newline at end of file diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h index a069e29020..20b772841d 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h @@ -1,298 +1,423 @@ -/*============================================================================ + /*============================================================================ 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_Included #define QmitkSlicesInterpolator_h_Included #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkSegmentationInterpolationController.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceInterpolationController.h" #include "mitkToolManager.h" #include #include "mitkFeatureBasedEdgeDetectionFilter.h" #include "mitkPointCloudScoringFilter.h" #include #include #include #include #include #include #include #include "mitkVtkRepresentationProperty.h" #include "vtkProperty.h" // For running 3D interpolation in background #include #include #include #include namespace mitk { class PlaneGeometry; class SliceNavigationController; } class QPushButton; +enum ModifyLabelActionTrigerred +{ + Null, + Erase, + Merge +}; + /** \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. \todo show/hide feedback on demand Last contributor: $Author: maleike $ */ 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 &controllers); + /** + * @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(); + /** 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); + + /** + * @brief Function that reacts to a change in the layer. + * + */ + void OnLayerChanged(); + + /** + * @brief Function that handles label removal from the segmentation image. + * + */ + void OnRemoveLabel(); + + /** + * @brief Function that to changes in the segmentation image. It handles the layer removal, addition, label erasure, + * + */ + void OnModifyLabelChanged(const itk::Object *caller, + const itk::EventObject & /*event*/); + + /** + * @brief Add the necessary subscribers to the label set image, for UI responsiveness. + * It deals with remove label, change active label, layer changes and change in the label. + * + */ + void OnAddLabelSetConnection(); + + /** + * @brief Add the necessary subscribers to the current LabelSetImage at the layer input, for UI responsiveness. + * It deals with remove label, change active label, layer changes and change in the label. + * + * @param layerID + */ + void OnAddLabelSetConnection(unsigned int layerID); + + /** + * @brief Remove the subscribers for the different events to the segmentation image. + * + */ + void OnRemoveLabelSetConnection(); + + /** + * @brief Merge contours for the current layerID and current timeStep. + * + * @param timeStep + * @param layerID + */ + void MergeContours(unsigned int timeStep, unsigned int layerID); + + + /** + * @brief Prepare Inputs for 3D Interpolation. + * + */ + void PrepareInputsFor3DInterpolation(); + 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(); - void OnSuggestPlaneClicked(); - /* * 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(); - void RunPlaneSuggestion(); - + /** + * @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(); + /** + * @brief Removes all observers to the labelSetImage at the layerID specified. + * Is used when changing the segmentation image. + * + * @param labelSetImage + * @param layerID + */ + void OnRemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID); + protected: const std::map createActionToSliceDimension(); std::map ACTION_TO_SLICEDIMENSION; 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 slicer the SliceNavigationController */ bool TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer); /** 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, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer); // void InterpolateSurface(); /** Called internally to update the interpolation suggestion. Finds out about the focused render window and requests an interpolation. */ void UpdateVisibleSuggestion(); void SetCurrentContourListID(); private: 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; QHash m_ControllerToTimeObserverTag; QHash m_ControllerToSliceObserverTag; QHash m_ControllerToDeleteObserverTag; + std::map m_SegmentationObserverTags; + 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::DataNode::Pointer m_3DContourNode; mitk::Image *m_Segmentation; mitk::SliceNavigationController *m_LastSNC; unsigned int m_LastSliceIndex; QHash m_TimePoints; bool m_2DInterpolationEnabled; bool m_3DInterpolationEnabled; - // unsigned int m_CurrentListID; + + unsigned int m_numTimesLabelSetConnectionAdded; mitk::DataStorage::Pointer m_DataStorage; QFuture m_Future; QFutureWatcher m_Watcher; + + QFuture m_ModifyFuture; + QFutureWatcher m_ModifyWatcher; + QTimer *m_Timer; QFuture m_PlaneFuture; QFutureWatcher m_PlaneWatcher; + mitk::Label::PixelType m_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 f270c48b3d..dabdddde12 100644 --- a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp +++ b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp @@ -1,824 +1,1095 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include "mitkImagePixelWriteAccessor.h" #include "mitkImageTimeSelector.h" class mitkSurfaceInterpolationControllerTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkSurfaceInterpolationControllerTestSuite); MITK_TEST(TestSingleton); MITK_TEST(TestSetCurrentInterpolationSession); MITK_TEST(TestReplaceInterpolationSession); MITK_TEST(TestRemoveAllInterpolationSessions); MITK_TEST(TestRemoveInterpolationSession); MITK_TEST(TestOnSegmentationDeleted); MITK_TEST(TestSetCurrentInterpolationSession4D); MITK_TEST(TestReplaceInterpolationSession4D); MITK_TEST(TestRemoveAllInterpolationSessions4D); MITK_TEST(TestRemoveInterpolationSession4D); MITK_TEST(TestOnSegmentationDeleted4D); /// \todo Workaround for memory leak in TestAddNewContour. Bug 18096. vtkDebugLeaks::SetExitError(0); MITK_TEST(TestAddNewContour); MITK_TEST(TestRemoveContour); 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(); newImage->Initialize(p_type, 3, dimensions); 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(); 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->SetCurrentTimePoint(0.); vtkSmartPointer polygonSource = vtkSmartPointer::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::Image::Pointer segmentation_1 = createImage(dimensions1); + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; - mitk::Image::Pointer segmentation_2 = createImage(dimensions2); + mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); + MITK_ASSERT_EQUAL( m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not equal"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == 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"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == 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"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == 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"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); - // Test 5 + // // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().IsNull()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } + mitk::PlaneGeometry::Pointer GetPlaneGeometry() + { + mitk::Point3D origin; + mitk::Vector3D right, bottom, normal, spacing; + mitk::ScalarType width, height; + mitk::ScalarType widthInMM, heightInMM, thicknessInMM; + + auto planegeometry = mitk::PlaneGeometry::New(); + width = 100; + widthInMM = width; + height = 200; + heightInMM = height; + thicknessInMM = 1.0; + mitk::FillVector3D(origin, 4.5, 7.3, 11.2); + mitk::FillVector3D(right, widthInMM, 0, 0); + mitk::FillVector3D(bottom, 0, heightInMM, 0); + mitk::FillVector3D(normal, 0, 0, thicknessInMM); + mitk::FillVector3D(spacing, 1.0, 1.0, thicknessInMM); + + planegeometry->InitializeStandardPlane(right, bottom); + planegeometry->SetOrigin(origin); + planegeometry->SetSpacing(spacing); + return planegeometry; + } + void TestReplaceInterpolationSession() { // Create segmentation image unsigned int dimensions1[] = {10, 10, 10}; - mitk::Image::Pointer segmentation_1 = createImage(dimensions1); + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); + m_Controller->SetCurrentInterpolationSession(segmentation_1); // Create some contours double center_1[3] = {1.25f, 3.43f, 4.44f}; double normal_1[3] = {0.25f, 1.76f, 0.93f}; vtkSmartPointer p_source = vtkSmartPointer::New(); p_source->SetNumberOfSides(20); 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); + vtkSmartPointer int1Array = vtkSmartPointer::New(); + int1Array->InsertNextValue(1); + int1Array->InsertNextValue(0); + int1Array->InsertNextValue(0); + surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); + vtkSmartPointer double1Array = vtkSmartPointer::New(); + double1Array->InsertNextValue(center_1[0]); + double1Array->InsertNextValue(center_1[1]); + double1Array->InsertNextValue(center_1[2]); + surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); double center_2[3] = {4.0f, 4.0f, 4.0f}; double normal_2[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_2 = vtkSmartPointer::New(); p_source_2->SetNumberOfSides(80); p_source_2->SetCenter(center_2); p_source_2->SetRadius(4); p_source_2->SetNormal(normal_2); p_source_2->Update(); vtkPolyData *poly_2 = p_source_2->GetOutput(); mitk::Surface::Pointer surf_2 = mitk::Surface::New(); surf_2->SetVtkPolyData(poly_2); + vtkSmartPointer int2Array = vtkSmartPointer::New(); + int2Array->InsertNextValue(1); + int2Array->InsertNextValue(0); + int2Array->InsertNextValue(0); + surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); + vtkSmartPointer doubleArray = vtkSmartPointer::New(); + doubleArray->InsertNextValue(center_2[0]); + doubleArray->InsertNextValue(center_2[1]); + doubleArray->InsertNextValue(center_2[2]); + surf_2->GetVtkPolyData()->GetFieldData()->AddArray(doubleArray); + + std::vector surfaces; + surfaces.push_back(surf_1); + surfaces.push_back(surf_2); + + const mitk::PlaneGeometry * planeGeometry1 = GetPlaneGeometry(); + const mitk::PlaneGeometry * planeGeometry2 = GetPlaneGeometry(); + + std::vector planeGeometries; + planeGeometries.push_back(planeGeometry1); + planeGeometries.push_back(planeGeometry2); // Add contours - m_Controller->AddNewContour(surf_1); - m_Controller->AddNewContour(surf_2); + m_Controller->AddNewContours(surfaces, planeGeometries, true); // Check if all contours are there mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; - contourInfo1.contourNormal = normal_1; - contourInfo1.contourPoint = center_1; + + mitk::ScalarType n[3]; + vtkPolygon::ComputeNormal(surf_1->GetVtkPolyData()->GetPoints(), n); + contourInfo1.ContourNormal = n; + contourInfo1.ContourPoint = center_1; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; - contourInfo2.contourNormal = normal_2; - contourInfo2.contourPoint = center_2; - mitk::Image::Pointer segmentation_2 = createImage(dimensions1); - bool success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_2); + vtkPolygon::ComputeNormal(surf_2->GetVtkPolyData()->GetPoints(), n); + + contourInfo2.ContourNormal = n; + contourInfo2.ContourPoint = center_2; + + const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); - CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == true); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); + CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); + + mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions1); + bool success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_2); + + CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == true); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); unsigned int dimensions2[] = {10, 20, 10}; - mitk::Image::Pointer segmentation_3 = createImage(dimensions2); - success = m_Controller->ReplaceInterpolationSession(segmentation_2, segmentation_3); + mitk::Image::Pointer segmentation_3 = createLabelSetImage(dimensions2); + success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_3); CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == false); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); } void TestRemoveAllInterpolationSessions() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; - mitk::Image::Pointer segmentation_1 = createImage(dimensions1); + auto segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; - mitk::Image::Pointer segmentation_2 = createImage(dimensions2); + 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::Image::Pointer segmentation_1 = createImage(dimensions1); + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; - mitk::Image::Pointer segmentation_2 = createImage(dimensions2); + mitk::Image::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().GetPointer() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", m_Controller->GetCurrentSegmentation().IsNotNull()); 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().IsNull()); } void TestOnSegmentationDeleted() { { + m_Controller->RemoveAllInterpolationSessions(); // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; - mitk::Image::Pointer segmentation_1 = createImage(dimensions1); + mitk::Image::Pointer segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); } CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestAddNewContour() { + m_Controller->RemoveAllInterpolationSessions(); // Create segmentation image unsigned int dimensions1[] = {10, 10, 10}; - mitk::Image::Pointer segmentation_1 = createImage(dimensions1); + mitk::Image::Pointer segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); // Create some contours double center_1[3] = {1.25f, 3.43f, 4.44f}; double normal_1[3] = {0.25f, 1.76f, 0.93f}; vtkSmartPointer p_source = vtkSmartPointer::New(); p_source->SetNumberOfSides(20); 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); + vtkSmartPointer int1Array = vtkSmartPointer::New(); + int1Array->InsertNextValue(1); + int1Array->InsertNextValue(0); + int1Array->InsertNextValue(0); + surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); + vtkSmartPointer double1Array = vtkSmartPointer::New(); + double1Array->InsertNextValue(center_1[0]); + double1Array->InsertNextValue(center_1[1]); + double1Array->InsertNextValue(center_1[2]); + surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); double center_2[3] = {4.0f, 4.0f, 4.0f}; double normal_2[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_2 = vtkSmartPointer::New(); p_source_2->SetNumberOfSides(80); p_source_2->SetCenter(center_2); p_source_2->SetRadius(4); p_source_2->SetNormal(normal_2); p_source_2->Update(); vtkPolyData *poly_2 = p_source_2->GetOutput(); mitk::Surface::Pointer surf_2 = mitk::Surface::New(); surf_2->SetVtkPolyData(poly_2); + vtkSmartPointer int2Array = vtkSmartPointer::New(); + int2Array->InsertNextValue(1); + int2Array->InsertNextValue(0); + int2Array->InsertNextValue(0); + surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); + vtkSmartPointer double2Array = vtkSmartPointer::New(); + double2Array->InsertNextValue(center_2[0]); + double2Array->InsertNextValue(center_2[1]); + double2Array->InsertNextValue(center_2[2]); + surf_2->GetVtkPolyData()->GetFieldData()->AddArray(double2Array); double center_3[3] = {4.0f, 4.0f, 3.0f}; double normal_3[3] = {0.0f, 0.0f, 1.0f}; vtkSmartPointer p_source_3 = vtkSmartPointer::New(); p_source_3->SetNumberOfSides(10); p_source_3->SetCenter(center_3); p_source_3->SetRadius(4); p_source_3->SetNormal(normal_3); p_source_3->Update(); vtkPolyData *poly_3 = p_source_3->GetOutput(); mitk::Surface::Pointer surf_3 = mitk::Surface::New(); surf_3->SetVtkPolyData(poly_3); + vtkSmartPointer int3Array = vtkSmartPointer::New(); + int3Array->InsertNextValue(1); + int3Array->InsertNextValue(0); + int3Array->InsertNextValue(0); + surf_3->GetVtkPolyData()->GetFieldData()->AddArray(int3Array); + vtkSmartPointer double3Array = vtkSmartPointer::New(); + double3Array->InsertNextValue(center_3[0]); + double3Array->InsertNextValue(center_3[1]); + double3Array->InsertNextValue(center_3[2]); + surf_3->GetVtkPolyData()->GetFieldData()->AddArray(double3Array); + + std::vector surfaces; + surfaces.push_back(surf_1); + surfaces.push_back(surf_2); + surfaces.push_back(surf_3); + + const mitk::PlaneGeometry * planeGeometry1 = GetPlaneGeometry(); + const mitk::PlaneGeometry * planeGeometry2 = GetPlaneGeometry(); + const mitk::PlaneGeometry * planeGeometry3 = GetPlaneGeometry(); + + std::vector planeGeometries; + planeGeometries.push_back(planeGeometry1); + planeGeometries.push_back(planeGeometry2); + planeGeometries.push_back(planeGeometry3); // Add contours - m_Controller->AddNewContour(surf_1); - m_Controller->AddNewContour(surf_2); - m_Controller->AddNewContour(surf_3); + m_Controller->AddNewContours(surfaces, planeGeometries, true); + + mitk::ScalarType n[3]; // Check if all contours are there mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; - contourInfo1.contourNormal = normal_1; - contourInfo1.contourPoint = center_1; + vtkPolygon::ComputeNormal(surf_1->GetVtkPolyData()->GetPoints(), n); + contourInfo1.ContourNormal = n; + contourInfo1.ContourPoint = center_1; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; - contourInfo2.contourNormal = normal_2; - contourInfo2.contourPoint = center_2; + vtkPolygon::ComputeNormal(surf_2->GetVtkPolyData()->GetPoints(), n); + contourInfo2.ContourNormal = n; + contourInfo2.ContourPoint = center_2; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; - contourInfo3.contourNormal = normal_3; - contourInfo3.contourPoint = center_3; + vtkPolygon::ComputeNormal(surf_3->GetVtkPolyData()->GetPoints(), n); + contourInfo3.ContourNormal = n; + contourInfo3.ContourPoint = center_3; const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); const mitk::Surface *contour_3 = m_Controller->GetContour(contourInfo3); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_3->GetVtkPolyData()), 0.000001, true)); // Create another segmentation image unsigned int dimensions2[] = {20, 20, 20}; - mitk::Image::Pointer segmentation_2 = createImage(dimensions2); + mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); m_Controller->SetCurrentInterpolationSession(segmentation_2); // Create some contours double center_4[3] = {10.0f, 10.0f, 10.0f}; double normal_4[3] = {0.0f, 1.0f, 0.0f}; vtkSmartPointer p_source_4 = vtkSmartPointer::New(); p_source_4->SetNumberOfSides(8); p_source_4->SetCenter(center_4); p_source_4->SetRadius(5); p_source_4->SetNormal(normal_4); p_source_4->Update(); vtkPolyData *poly_4 = p_source_4->GetOutput(); mitk::Surface::Pointer surf_4 = mitk::Surface::New(); surf_4->SetVtkPolyData(poly_4); + vtkSmartPointer int4Array = vtkSmartPointer::New(); + int4Array->InsertNextValue(2); + int4Array->InsertNextValue(0); + int4Array->InsertNextValue(0); + surf_4->GetVtkPolyData()->GetFieldData()->AddArray(int4Array); + vtkSmartPointer double4Array = vtkSmartPointer::New(); + double4Array->InsertNextValue(center_4[0]); + double4Array->InsertNextValue(center_4[1]); + double4Array->InsertNextValue(center_4[2]); + surf_4->GetVtkPolyData()->GetFieldData()->AddArray(double4Array); double center_5[3] = {3.0f, 10.0f, 10.0f}; double normal_5[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_5 = vtkSmartPointer::New(); p_source_5->SetNumberOfSides(16); p_source_5->SetCenter(center_5); p_source_5->SetRadius(8); p_source_5->SetNormal(normal_5); p_source_5->Update(); vtkPolyData *poly_5 = p_source_5->GetOutput(); mitk::Surface::Pointer surf_5 = mitk::Surface::New(); surf_5->SetVtkPolyData(poly_5); + vtkSmartPointer int5Array = vtkSmartPointer::New(); + int5Array->InsertNextValue(2); + int5Array->InsertNextValue(0); + int5Array->InsertNextValue(0); + surf_5->GetVtkPolyData()->GetFieldData()->AddArray(int5Array); + vtkSmartPointer double5Array = vtkSmartPointer::New(); + double5Array->InsertNextValue(center_5[0]); + double5Array->InsertNextValue(center_5[1]); + double5Array->InsertNextValue(center_5[2]); + surf_5->GetVtkPolyData()->GetFieldData()->AddArray(double5Array); double center_6[3] = {10.0f, 10.0f, 3.0f}; double normal_6[3] = {0.0f, 0.0f, 1.0f}; vtkSmartPointer p_source_6 = vtkSmartPointer::New(); p_source_6->SetNumberOfSides(100); p_source_6->SetCenter(center_6); p_source_6->SetRadius(5); p_source_6->SetNormal(normal_6); p_source_6->Update(); vtkPolyData *poly_6 = p_source_6->GetOutput(); mitk::Surface::Pointer surf_6 = mitk::Surface::New(); surf_6->SetVtkPolyData(poly_6); + vtkSmartPointer int6Array = vtkSmartPointer::New(); + int6Array->InsertNextValue(2); + int6Array->InsertNextValue(0); + int6Array->InsertNextValue(0); + surf_6->GetVtkPolyData()->GetFieldData()->AddArray(int6Array); + vtkSmartPointer double6Array = vtkSmartPointer::New(); + double6Array->InsertNextValue(center_6[0]); + double6Array->InsertNextValue(center_6[1]); + double6Array->InsertNextValue(center_6[2]); + surf_6->GetVtkPolyData()->GetFieldData()->AddArray(double6Array); mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo4; - contourInfo4.contourNormal = normal_4; - contourInfo4.contourPoint = center_4; + vtkPolygon::ComputeNormal(surf_4->GetVtkPolyData()->GetPoints(), n); + contourInfo4.ContourNormal = n; + contourInfo4.ContourPoint = center_4; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo5; - contourInfo5.contourNormal = normal_5; - contourInfo5.contourPoint = center_5; + vtkPolygon::ComputeNormal(surf_5->GetVtkPolyData()->GetPoints(), n); + contourInfo5.ContourNormal = n; + contourInfo5.ContourPoint = center_5; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo6; - contourInfo6.contourNormal = normal_6; - contourInfo6.contourPoint = center_6; + vtkPolygon::ComputeNormal(surf_6->GetVtkPolyData()->GetPoints(), n); + contourInfo6.ContourNormal = n; + contourInfo6.ContourPoint = center_6; - // Add contours - m_Controller->AddNewContour(surf_4); - m_Controller->AddNewContour(surf_5); - m_Controller->AddNewContour(surf_6); + const mitk::PlaneGeometry * planeGeometry4 = GetPlaneGeometry(); + const mitk::PlaneGeometry * planeGeometry5 = GetPlaneGeometry(); + const mitk::PlaneGeometry * planeGeometry6 = GetPlaneGeometry(); + + std::vector surfaces2; + surfaces2.push_back(surf_4); + surfaces2.push_back(surf_5); + surfaces2.push_back(surf_6); + + std::vector planeGeometries2; + planeGeometries2.push_back(planeGeometry4); + planeGeometries2.push_back(planeGeometry5); + planeGeometries2.push_back(planeGeometry6); + + m_Controller->AddNewContours(surfaces2, planeGeometries2, true); // Check if all contours are there auto contour_4 = m_Controller->GetContour(contourInfo4); auto contour_5 = m_Controller->GetContour(contourInfo5); auto contour_6 = m_Controller->GetContour(contourInfo6); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_4->GetVtkPolyData()), *(contour_4->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_5->GetVtkPolyData()), *(contour_5->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_6->GetVtkPolyData()), *(contour_6->GetVtkPolyData()), 0.000001, true)); // Modify some contours vtkSmartPointer p_source_7 = vtkSmartPointer::New(); p_source_7->SetNumberOfSides(200); p_source_7->SetCenter(3.0, 10.0, 10.0); p_source_7->SetRadius(5); p_source_7->SetNormal(1, 0, 0); p_source_7->Update(); vtkPolyData *poly_7 = p_source_7->GetOutput(); mitk::Surface::Pointer surf_7 = mitk::Surface::New(); surf_7->SetVtkPolyData(poly_7); + vtkSmartPointer int7Array = vtkSmartPointer::New(); + int7Array->InsertNextValue(2); + int7Array->InsertNextValue(0); + int7Array->InsertNextValue(0); + surf_7->GetVtkPolyData()->GetFieldData()->AddArray(int7Array); + vtkSmartPointer double7Array = vtkSmartPointer::New(); + double7Array->InsertNextValue(3.0); + double7Array->InsertNextValue(10.0); + double7Array->InsertNextValue(10.0); + surf_7->GetVtkPolyData()->GetFieldData()->AddArray(double7Array); + + + std::vector surfaces3; + surfaces3.push_back(surf_7); + + const mitk::PlaneGeometry * planeGeometry7 = GetPlaneGeometry(); + std::vector planeGeometries3; + planeGeometries3.push_back(planeGeometry7); + + m_Controller->AddNewContours(surfaces3, planeGeometries3, true); + + mitk::ScalarType center_7[3]; + center_7[0] = 3.0; + center_7[1] = 10.0; + center_7[2] = 10.0; + vtkPolygon::ComputeNormal(surf_7->GetVtkPolyData()->GetPoints(), n); + contourInfo5.ContourNormal = n; + contourInfo5.ContourPoint = center_7; - m_Controller->AddNewContour(surf_7); auto contour_7 = m_Controller->GetContour(contourInfo5); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_7->GetVtkPolyData()), *(contour_7->GetVtkPolyData()), 0.000001, true)); // Change session and test if all contours are available m_Controller->SetCurrentInterpolationSession(segmentation_1); auto contour_8 = m_Controller->GetContour(contourInfo1); auto contour_9 = m_Controller->GetContour(contourInfo2); auto contour_10 = m_Controller->GetContour(contourInfo3); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_8->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_9->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_10->GetVtkPolyData()), 0.000001, true)); } void TestRemoveContour() { + m_Controller->RemoveAllInterpolationSessions(); // Create segmentation image - unsigned int dimensions1[] = {10, 10, 10}; - mitk::Image::Pointer segmentation_1 = createImage(dimensions1); + unsigned int dimensions1[] = {12, 12, 12}; + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); // Create some contours double center_1[3] = {4.0f, 4.0f, 4.0f}; double normal_1[3] = {0.0f, 1.0f, 0.0f}; vtkSmartPointer p_source = vtkSmartPointer::New(); p_source->SetNumberOfSides(20); 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); + vtkSmartPointer int1Array = vtkSmartPointer::New(); + int1Array->InsertNextValue(1); + int1Array->InsertNextValue(0); + int1Array->InsertNextValue(0); + surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); + vtkSmartPointer double1Array = vtkSmartPointer::New(); + double1Array->InsertNextValue(center_1[0]); + double1Array->InsertNextValue(center_1[1]); + double1Array->InsertNextValue(center_1[2]); + surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); double center_2[3] = {4.0f, 4.0f, 4.0f}; double normal_2[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_2 = vtkSmartPointer::New(); p_source_2->SetNumberOfSides(80); p_source_2->SetCenter(center_2); p_source_2->SetRadius(4); p_source_2->SetNormal(normal_2); p_source_2->Update(); vtkPolyData *poly_2 = p_source_2->GetOutput(); mitk::Surface::Pointer surf_2 = mitk::Surface::New(); surf_2->SetVtkPolyData(poly_2); - - // Add contours - m_Controller->AddNewContour(surf_1); - m_Controller->AddNewContour(surf_2); + vtkSmartPointer int2Array = vtkSmartPointer::New(); + int2Array->InsertNextValue(1); + int2Array->InsertNextValue(0); + int2Array->InsertNextValue(0); + surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); + vtkSmartPointer double2Array = vtkSmartPointer::New(); + double2Array->InsertNextValue(center_2[0]); + double2Array->InsertNextValue(center_2[1]); + double2Array->InsertNextValue(center_2[2]); + surf_2->GetVtkPolyData()->GetFieldData()->AddArray(double2Array); + + std::vector surfaces; + surfaces.push_back(surf_1); + surfaces.push_back(surf_2); + + const mitk::PlaneGeometry * planeGeometry1 = GetPlaneGeometry(); + const mitk::PlaneGeometry * planeGeometry2 = GetPlaneGeometry(); + std::vector planeGeometries; + planeGeometries.push_back(planeGeometry1); + planeGeometries.push_back(planeGeometry2); + + m_Controller->AddNewContours(surfaces, planeGeometries, true); + // // Add contours CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; - contourInfo3.contour = surf_1->Clone(); - contourInfo3.contourNormal = normal_1; - contourInfo3.contourPoint = center_1; + contourInfo3.Contour = surf_1->Clone(); + contourInfo3.ContourNormal = normal_1; + contourInfo3.ContourPoint = center_1; // Shift the new contour so that it is different - contourInfo3.contourPoint += 0.5; + contourInfo3.ContourPoint += 0.5; bool success = m_Controller->RemoveContour(contourInfo3); CPPUNIT_ASSERT_MESSAGE("Remove failed - contour was unintentionally removed!", (m_Controller->GetNumberOfContours() == 2) && !success); mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; - contourInfo2.contourNormal = normal_2; - contourInfo2.contourPoint = center_2; - contourInfo2.contour = surf_2; + contourInfo2.ContourNormal = normal_2; + contourInfo2.ContourPoint = center_2; + contourInfo2.Contour = surf_2; success = m_Controller->RemoveContour(contourInfo2); CPPUNIT_ASSERT_MESSAGE("Remove failed - contour was not removed!", (m_Controller->GetNumberOfContours() == 1) && success); - // Let's see if the other contour No. 1 is still there - contourInfo3.contourPoint -= 0.5; + // // Let's see if the other contour No. 1 is still there + contourInfo3.ContourPoint -= 0.5; const mitk::Surface *remainingContour = m_Controller->GetContour(contourInfo3); CPPUNIT_ASSERT_MESSAGE( "Remove failed - contour was accidentally removed!", (m_Controller->GetNumberOfContours() == 1) && mitk::Equal(*(surf_1->GetVtkPolyData()), *(remainingContour->GetVtkPolyData()), 0.000001, true) && success); } - bool AssertImagesEqual4D(mitk::Image *img1, mitk::Image *img2) + bool AssertImagesEqual4D(mitk::LabelSetImage *img1, mitk::LabelSetImage *img2) { mitk::ImageTimeSelector::Pointer selector1 = mitk::ImageTimeSelector::New(); selector1->SetInput(img1); selector1->SetChannelNr(0); mitk::ImageTimeSelector::Pointer 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 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() { - /*unsigned int testDimensions[] = {10, 10, 10, 5}; - mitk::Image::Pointer testSeg = createImage4D(testDimensions); - mitk::Image::Pointer testSegClone = testSeg->Clone(); - int testTs = testSeg->GetTimeSteps(); - - MITK_ASSERT_EQUAL(testSeg, testSegClone, "Segmentation images are not equal");*/ - // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 5}; - mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); + // mitk::Image * segmentationImage_1 = dynamic_cast(segmentation_1.GetPointer()); unsigned int dimensions2[] = {20, 10, 30, 4}; - mitk::Image::Pointer segmentation_2 = createImage4D(dimensions2); + mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); - AssertImagesEqual4D(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone()); + auto currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation().GetPointer()); + AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == 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"); - AssertImagesEqual4D(m_Controller->GetCurrentSegmentation(), segmentation_2->Clone()); + currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation().GetPointer()); + // AssertImagesEqual4D(currentSegmentation, segmentation_2->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); + 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"); - AssertImagesEqual4D(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone()); + currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation().GetPointer()); + AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == 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"); - AssertImagesEqual4D(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone()); + currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation().GetPointer()); + AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == 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().IsNull()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } void TestReplaceInterpolationSession4D() { + m_Controller->RemoveAllInterpolationSessions(); // Create segmentation image unsigned int dimensions1[] = {10, 10, 10, 5}; - mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); + m_Controller->SetCurrentTimePoint(0); + // Create some contours double center_1[3] = {1.25f, 3.43f, 4.44f}; double normal_1[3] = {0.25f, 1.76f, 0.93f}; vtkSmartPointer p_source = vtkSmartPointer::New(); p_source->SetNumberOfSides(20); 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); + vtkSmartPointer int1Array = vtkSmartPointer::New(); + int1Array->InsertNextValue(1); + int1Array->InsertNextValue(0); + int1Array->InsertNextValue(0); + surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); + vtkSmartPointer double1Array = vtkSmartPointer::New(); + double1Array->InsertNextValue(center_1[0]); + double1Array->InsertNextValue(center_1[1]); + double1Array->InsertNextValue(center_1[2]); + surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); double center_2[3] = {4.0f, 4.0f, 4.0f}; double normal_2[3] = {1.0f, 0.0f, 0.0f}; vtkSmartPointer p_source_2 = vtkSmartPointer::New(); p_source_2->SetNumberOfSides(80); p_source_2->SetCenter(center_2); p_source_2->SetRadius(4); p_source_2->SetNormal(normal_2); p_source_2->Update(); vtkPolyData *poly_2 = p_source_2->GetOutput(); mitk::Surface::Pointer surf_2 = mitk::Surface::New(); surf_2->SetVtkPolyData(poly_2); - + vtkSmartPointer int2Array = vtkSmartPointer::New(); + int2Array->InsertNextValue(1); + int2Array->InsertNextValue(0); + int2Array->InsertNextValue(0); + surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); + vtkSmartPointer double2Array = vtkSmartPointer::New(); + double2Array->InsertNextValue(center_2[0]); + double2Array->InsertNextValue(center_2[1]); + double2Array->InsertNextValue(center_2[2]); + surf_2->GetVtkPolyData()->GetFieldData()->AddArray(double2Array); + + + std::vector surfaces; + surfaces.push_back(surf_1); + surfaces.push_back(surf_2); + + const mitk::PlaneGeometry * planeGeometry1 = GetPlaneGeometry(); + const mitk::PlaneGeometry * planeGeometry2 = GetPlaneGeometry(); + std::vector planeGeometries; + planeGeometries.push_back(planeGeometry1); + planeGeometries.push_back(planeGeometry2); // Add contours - m_Controller->AddNewContour(surf_1); - m_Controller->AddNewContour(surf_2); + m_Controller->AddNewContours(surfaces, planeGeometries, true); + // Add contours for another timestep m_Controller->SetCurrentTimePoint(2); double center_3[3] = {1.3f, 3.5f, 4.6f}; double normal_3[3] = {0.20f, 1.6f, 0.8f}; vtkSmartPointer p_source_3 = vtkSmartPointer::New(); p_source_3->SetNumberOfSides(20); p_source_3->SetCenter(center_3); p_source_3->SetRadius(4); p_source_3->SetNormal(normal_3); p_source_3->Update(); vtkPolyData *poly_3 = p_source_3->GetOutput(); mitk::Surface::Pointer surf_3 = mitk::Surface::New(); surf_3->SetVtkPolyData(poly_3); + vtkSmartPointer int3Array = vtkSmartPointer::New(); + int3Array->InsertNextValue(1); + int3Array->InsertNextValue(0); + int3Array->InsertNextValue(2); + surf_3->GetVtkPolyData()->GetFieldData()->AddArray(int3Array); + vtkSmartPointer double3Array = vtkSmartPointer::New(); + double3Array->InsertNextValue(center_3[0]); + double3Array->InsertNextValue(center_3[1]); + double3Array->InsertNextValue(center_3[2]); + surf_3->GetVtkPolyData()->GetFieldData()->AddArray(double3Array); double center_4[3] = {1.32f, 3.53f, 4.8f}; double normal_4[3] = {0.22f, 1.5f, 0.85f}; vtkSmartPointer p_source_4 = vtkSmartPointer::New(); p_source_4->SetNumberOfSides(20); p_source_4->SetCenter(center_4); p_source_4->SetRadius(4); p_source_4->SetNormal(normal_4); p_source_4->Update(); vtkPolyData *poly_4 = p_source_4->GetOutput(); mitk::Surface::Pointer surf_4 = mitk::Surface::New(); surf_4->SetVtkPolyData(poly_4); - - m_Controller->AddNewContour(surf_3); - m_Controller->AddNewContour(surf_4); + vtkSmartPointer int4Array = vtkSmartPointer::New(); + int4Array->InsertNextValue(1); + int4Array->InsertNextValue(0); + int4Array->InsertNextValue(2); + surf_4->GetVtkPolyData()->GetFieldData()->AddArray(int4Array); + vtkSmartPointer double4Array = vtkSmartPointer::New(); + double4Array->InsertNextValue(center_4[0]); + double4Array->InsertNextValue(center_4[1]); + double4Array->InsertNextValue(center_4[2]); + surf_4->GetVtkPolyData()->GetFieldData()->AddArray(double4Array); + + + std::vector surfaces2; + surfaces2.push_back(surf_3); + surfaces2.push_back(surf_4); + + const mitk::PlaneGeometry * planeGeometry3 = GetPlaneGeometry(); + const mitk::PlaneGeometry * planeGeometry4 = GetPlaneGeometry(); + std::vector planeGeometries2; + planeGeometries2.push_back(planeGeometry3); + planeGeometries2.push_back(planeGeometry4); + // Add contours + m_Controller->AddNewContours(surfaces2, planeGeometries2, true); m_Controller->SetCurrentTimePoint(0); // Check if all contours are there mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; - contourInfo1.contourNormal = normal_1; - contourInfo1.contourPoint = center_1; + contourInfo1.ContourNormal = normal_1; + contourInfo1.ContourPoint = center_1; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; - contourInfo2.contourNormal = normal_2; - contourInfo2.contourPoint = center_2; + contourInfo2.ContourNormal = normal_2; + contourInfo2.ContourPoint = center_2; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; - contourInfo3.contourNormal = normal_3; - contourInfo3.contourPoint = center_3; + mitk::ScalarType n[3]; + vtkPolygon::ComputeNormal(surf_3->GetVtkPolyData()->GetPoints(), n); + contourInfo3.ContourNormal = n; + contourInfo3.ContourPoint = center_3; mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo4; - contourInfo4.contourNormal = normal_4; - contourInfo4.contourPoint = center_4; - - mitk::Image::Pointer segmentation_2 = createImage4D(dimensions1); - bool success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_2); - - CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == true); - CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", - m_Controller->GetNumberOfInterpolationSessions() == 1); - CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); + // mitk::ScalarType n[3]; + vtkPolygon::ComputeNormal(surf_4->GetVtkPolyData()->GetPoints(), n); + contourInfo4.ContourNormal = n; + contourInfo4.ContourPoint = center_4; const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); m_Controller->SetCurrentTimePoint(2); - - // CPPUNIT_ASSERT_MESSAGE("Contour accessed from outside of timestep!", m_Controller->GetNumberOfContours() == 0); - contour_1 = m_Controller->GetContour(contourInfo1); - contour_2 = m_Controller->GetContour(contourInfo2); - CPPUNIT_ASSERT_MESSAGE("Contour accessed from outside of timestep!", contour_1 == nullptr && contour_2 == nullptr); - const mitk::Surface *contour_3 = m_Controller->GetContour(contourInfo3); const mitk::Surface *contour_4 = m_Controller->GetContour(contourInfo4); CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_3->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", mitk::Equal(*(surf_4->GetVtkPolyData()), *(contour_4->GetVtkPolyData()), 0.000001, true)); - unsigned int dimensions2[] = {10, 20, 10, 4}; - mitk::Image::Pointer segmentation_3 = createImage4D(dimensions2); - success = m_Controller->ReplaceInterpolationSession(segmentation_2, segmentation_3); - CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == false); + mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions1); + bool success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_2); + + CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == true); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); - - m_Controller->SetCurrentTimePoint(1); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 0); - - m_Controller->SetCurrentTimePoint(0); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); - - m_Controller->SetCurrentTimePoint(4); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 0); - - m_Controller->SetCurrentTimePoint(2); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); } void TestRemoveAllInterpolationSessions4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 4}; - mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 5}; - mitk::Image::Pointer segmentation_2 = createImage4D(dimensions2); + 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::Image::Pointer segmentation_1 = createImage4D(dimensions1); + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 6}; - mitk::Image::Pointer segmentation_2 = createImage4D(dimensions2); + 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().GetPointer() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", m_Controller->GetCurrentSegmentation().IsNotNull()); 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().IsNull()); } void TestOnSegmentationDeleted4D() { { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 7}; - mitk::Image::Pointer segmentation_1 = createImage4D(dimensions1); + 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/mitkSurfaceBasedInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceBasedInterpolationController.cpp index 2c511af95f..dc2c2fa36c 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceBasedInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceBasedInterpolationController.cpp @@ -1,299 +1,301 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSurfaceBasedInterpolationController.h" -#include "mitkColorProperty.h" +#include #include "mitkComputeContourSetNormalsFilter.h" -#include "mitkContourModelToSurfaceFilter.h" -#include "mitkIOUtil.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkImageToSurfaceFilter.h" -#include "mitkInteractionConst.h" -#include "mitkMemoryUtilities.h" -#include "mitkProperties.h" -#include "mitkRestorePlanePositionOperation.h" - -#include "mitkVtkRepresentationProperty.h" -#include "vtkAppendPolyData.h" -#include "vtkPolyData.h" -#include "vtkProperty.h" -#include "vtkSmartPointer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include mitk::SurfaceBasedInterpolationController::SurfaceBasedInterpolationController() : m_MinSpacing(1.0), m_MaxSpacing(1.0), m_WorkingImage(nullptr), m_ActiveLabel(0) { this->Initialize(); } mitk::SurfaceBasedInterpolationController::~SurfaceBasedInterpolationController() { auto it = m_MapOfContourLists.begin(); for (; it != m_MapOfContourLists.end(); it++) { for (unsigned int j = 0; j < m_MapOfContourLists[(*it).first].size(); ++j) { delete (m_MapOfContourLists[(*it).first].at(j).second); } m_MapOfContourLists.erase(it); } } void mitk::SurfaceBasedInterpolationController::Initialize() { m_InterpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); m_InterpolateSurfaceFilter->SetUseProgressBar(false); m_Contours = Surface::New(); m_InterpolationResult = nullptr; } mitk::SurfaceBasedInterpolationController *mitk::SurfaceBasedInterpolationController::GetInstance() { static mitk::SurfaceBasedInterpolationController *m_Instance; if (m_Instance == nullptr) { m_Instance = new SurfaceBasedInterpolationController(); } return m_Instance; } void mitk::SurfaceBasedInterpolationController::AddNewContour(mitk::ContourModel::Pointer newContour, RestorePlanePositionOperation *op) { if (m_ActiveLabel == 0) return; AffineTransform3D::Pointer transform = AffineTransform3D::New(); transform = op->GetTransform(); mitk::Vector3D direction = op->GetDirectionVector(); - int pos(-1); + int pos = -1; for (unsigned int i = 0; i < m_MapOfContourLists[m_ActiveLabel].size(); i++) { itk::Matrix diffM = transform->GetMatrix() - m_MapOfContourLists[m_ActiveLabel].at(i).second->GetTransform()->GetMatrix(); bool isSameMatrix(true); for (unsigned int j = 0; j < 3; j++) { if (fabs(diffM[j][0]) > 0.0001 && fabs(diffM[j][1]) > 0.0001 && fabs(diffM[j][2]) > 0.0001) { isSameMatrix = false; break; } } itk::Vector diffV = m_MapOfContourLists[m_ActiveLabel].at(i).second->GetTransform()->GetOffset() - transform->GetOffset(); if (isSameMatrix && m_MapOfContourLists[m_ActiveLabel].at(i).second->GetPos() == op->GetPos() && (fabs(diffV[0]) < 0.0001 && fabs(diffV[1]) < 0.0001 && fabs(diffV[2]) < 0.0001)) { pos = i; break; } } if (pos == -1 && newContour->GetNumberOfVertices() > 0) // add a new contour { mitk::RestorePlanePositionOperation *newOp = new mitk::RestorePlanePositionOperation( OpRESTOREPLANEPOSITION, op->GetWidth(), op->GetHeight(), op->GetSpacing(), op->GetPos(), direction, transform); ContourPositionPair newData = std::make_pair(newContour, newOp); m_MapOfContourLists[m_ActiveLabel].push_back(newData); } else if (pos != -1) // replace existing contour { m_MapOfContourLists[m_ActiveLabel].at(pos).first = newContour; } this->Modified(); } void mitk::SurfaceBasedInterpolationController::Interpolate() { if (m_MapOfContourLists[m_ActiveLabel].size() < 2) { // If no interpolation is possible reset the interpolation result m_InterpolationResult = nullptr; return; } m_InterpolateSurfaceFilter->Reset(); // MLI TODO // m_InterpolateSurfaceFilter->SetReferenceImage( m_WorkingImage ); // CreateDistanceImageFromSurfaceFilter::Pointer interpolateSurfaceFilter = // CreateDistanceImageFromSurfaceFilter::New(); vtkSmartPointer polyDataAppender = vtkSmartPointer::New(); for (unsigned int i = 0; i < m_MapOfContourLists[m_ActiveLabel].size(); i++) { mitk::ContourModelToSurfaceFilter::Pointer converter = mitk::ContourModelToSurfaceFilter::New(); converter->SetInput(m_MapOfContourLists[m_ActiveLabel].at(i).first); converter->Update(); mitk::Surface::Pointer surface = converter->GetOutput(); surface->DisconnectPipeline(); ReduceContourSetFilter::Pointer reduceFilter = ReduceContourSetFilter::New(); reduceFilter->SetUseProgressBar(false); reduceFilter->SetInput(surface); reduceFilter->SetMinSpacing(m_MinSpacing); reduceFilter->SetMaxSpacing(m_MaxSpacing); reduceFilter->Update(); ComputeContourSetNormalsFilter::Pointer normalsFilter = ComputeContourSetNormalsFilter::New(); normalsFilter->SetUseProgressBar(false); normalsFilter->SetInput(reduceFilter->GetOutput()); normalsFilter->SetMaxSpacing(m_MaxSpacing); // MLI TODO // normalsFilter->SetSegmentationBinaryImage(m_WorkingImage, m_ActiveLabel); // normalsFilter->Update(); m_InterpolateSurfaceFilter->SetInput(i, normalsFilter->GetOutput()); polyDataAppender->AddInputData(reduceFilter->GetOutput()->GetVtkPolyData()); } polyDataAppender->Update(); m_Contours->SetVtkPolyData(polyDataAppender->GetOutput()); // update the filter and get the resulting distance-image m_InterpolateSurfaceFilter->Update(); Image::Pointer distanceImage = m_InterpolateSurfaceFilter->GetOutput(); if (distanceImage.IsNull()) { // If no interpolation is possible reset the interpolation result m_InterpolationResult = nullptr; return; } // create a surface from the distance-image mitk::ImageToSurfaceFilter::Pointer imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); imageToSurfaceFilter->SetInput(distanceImage); imageToSurfaceFilter->SetThreshold(0); imageToSurfaceFilter->SetSmooth(true); imageToSurfaceFilter->SetSmoothIteration(20); imageToSurfaceFilter->Update(); m_InterpolationResult = imageToSurfaceFilter->GetOutput(); m_InterpolationResult->DisconnectPipeline(); } mitk::Surface::Pointer mitk::SurfaceBasedInterpolationController::GetInterpolationResult() { return m_InterpolationResult; } mitk::Surface *mitk::SurfaceBasedInterpolationController::GetContoursAsSurface() { return m_Contours; } void mitk::SurfaceBasedInterpolationController::SetMinSpacing(double minSpacing) { m_MinSpacing = minSpacing; } void mitk::SurfaceBasedInterpolationController::SetMaxSpacing(double maxSpacing) { m_MaxSpacing = maxSpacing; } void mitk::SurfaceBasedInterpolationController::SetDistanceImageVolume(unsigned int value) { m_DistanceImageVolume = value; } void mitk::SurfaceBasedInterpolationController::SetWorkingImage(Image *workingImage) { m_WorkingImage = workingImage; } mitk::Image *mitk::SurfaceBasedInterpolationController::GetImage() { return m_InterpolateSurfaceFilter->GetOutput(); } double mitk::SurfaceBasedInterpolationController::EstimatePortionOfNeededMemory() { double numberOfPoints = 0.0; for (unsigned int i = 0; i < m_MapOfContourLists[m_ActiveLabel].size(); i++) { numberOfPoints += m_MapOfContourLists[m_ActiveLabel].at(i).first->GetNumberOfVertices() * 3; } double sizeOfPoints = pow(numberOfPoints, 2) * sizeof(double); double totalMem = mitk::MemoryUtilities::GetTotalSizeOfPhysicalRam(); double percentage = sizeOfPoints / totalMem; return percentage; } void mitk::SurfaceBasedInterpolationController::SetActiveLabel(int activeLabel) { if (m_ActiveLabel == activeLabel) return; if (activeLabel == 0) return; m_ActiveLabel = activeLabel; auto it = m_MapOfContourLists.find(m_ActiveLabel); if (it == m_MapOfContourLists.end()) { ContourPositionPairList newList; - m_MapOfContourLists.insert(std::pair(m_ActiveLabel, newList)); + + m_MapOfContourLists[m_ActiveLabel] = newList; + m_InterpolationResult = nullptr; } this->Modified(); } /* void mitk::SurfaceBasedInterpolationController::RemoveSegmentationFromContourList(mitk::Image *segmentation) { if (segmentation != 0) { m_MapOfContourLists.erase(segmentation); if (m_SelectedSegmentation == segmentation) { SetSegmentationImage(nullptr); m_SelectedSegmentation = 0; } } } */ /* void mitk::SurfaceBasedInterpolationController::OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event) { mitk::Image* tempImage = dynamic_cast(const_cast(caller)); if (tempImage) { RemoveSegmentationFromContourList(tempImage); if (tempImage == m_SelectedSegmentation) { SetSegmentationImage(nullptr); m_SelectedSegmentation = 0; } } } */ diff --git a/Modules/SurfaceInterpolation/mitkSurfaceBasedInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceBasedInterpolationController.h index f1012acea2..4f7e70febb 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceBasedInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceBasedInterpolationController.h @@ -1,144 +1,145 @@ /*============================================================================ 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 mitkSurfaceBasedInterpolationController_h_Included #define mitkSurfaceBasedInterpolationController_h_Included //#include "mitkCommon.h" #include "mitkContourModel.h" -#include "mitkRestorePlanePositionOperation.h" -#include "mitkSurface.h" +#include +#include #include #include "mitkCreateDistanceImageFromSurfaceFilter.h" #include "mitkReduceContourSetFilter.h" -#include "mitkProgressBar.h" +#include namespace mitk { class RestorePlanePositionOperation; class MITKSURFACEINTERPOLATION_EXPORT SurfaceBasedInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceBasedInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); static SurfaceBasedInterpolationController *GetInstance(); /** * Adds a new extracted contour to the list */ void AddNewContour(ContourModel::Pointer newContour, RestorePlanePositionOperation *op); /** * Launches the interpolation method. A surface mesh is generated out of the given extracted contours. */ void Interpolate(); /** * Retrieves a surface mesh resulting from the interpolation of the given extracted contours. */ mitk::Surface::Pointer GetInterpolationResult(); /** * Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before, are used to interpolate the surface */ void SetMinSpacing(double minSpacing); /** * Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before, are used to interpolate the surface */ 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 value); /** * Sets the working image used by the interpolation method. * This is needed because the calculation of the normals needs to now wheather a normal points toward the inside of * a segmentation or not */ void SetWorkingImage(Image *workingImage); /** * Retrieves the input contours as a mitk::Surface */ Surface *GetContoursAsSurface(); /** * Sets the current list of contour points which is used for the surface interpolation * @param activeLabel The active label in the current working image */ void SetActiveLabel(int activeLabel); mitk::Image *GetImage(); /** * Estimates the memory that is needed to build up the equation system for the interpolation. * \returns The percentage of the real memory which will be used by the interpolation calculation */ double EstimatePortionOfNeededMemory(); + protected: SurfaceBasedInterpolationController(); ~SurfaceBasedInterpolationController() override; void Initialize(); private: // void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); /* struct ContourPositionPair { ContourModel::Pointer contour; RestorePlanePositionOperation* position; }; */ typedef std::pair ContourPositionPair; typedef std::vector ContourPositionPairList; typedef std::map ContourListMap; // ReduceContourSetFilter::Pointer m_ReduceFilter; // ComputeContourSetNormalsFilter::Pointer m_NormalsFilter; CreateDistanceImageFromSurfaceFilter::Pointer m_InterpolateSurfaceFilter; double m_MinSpacing; double m_MaxSpacing; unsigned int m_DistanceImageVolume; Image *m_WorkingImage; Surface::Pointer m_Contours; // vtkSmartPointer m_PolyData; ContourListMap m_MapOfContourLists; mitk::Surface::Pointer m_InterpolationResult; // unsigned int m_CurrentNumberOfReducedContours; int m_ActiveLabel; // std::map m_SegmentationObserverTags; }; } #endif diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp index 7555fcef9a..62457e4589 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp @@ -1,731 +1,1421 @@ /*============================================================================ 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 "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkMemoryUtilities.h" - -#include "mitkImageToSurfaceFilter.h" +#include +#include +#include +#include +#include +#include + +#include //#include "vtkXMLPolyDataWriter.h" -#include "vtkPolyDataWriter.h" +#include +#include +#include +#include +#include +#include +#include +#include // Check whether the given contours are coplanar bool ContoursCoplanar(mitk::SurfaceInterpolationController::ContourPositionInformation leftHandSide, mitk::SurfaceInterpolationController::ContourPositionInformation rightHandSide) { // Here we check two things: // 1. Whether the normals of both contours are at least parallel // 2. Whether both contours lie in the same plane // Check for coplanarity: // a. Span a vector between two points one from each contour // b. Calculate dot product for the vector and one of the normals // c. If the dot is zero the two vectors are orthogonal and the contours are coplanar double vec[3]; - vec[0] = leftHandSide.contourPoint[0] - rightHandSide.contourPoint[0]; - vec[1] = leftHandSide.contourPoint[1] - rightHandSide.contourPoint[1]; - vec[2] = leftHandSide.contourPoint[2] - rightHandSide.contourPoint[2]; + vec[0] = leftHandSide.ContourPoint[0] - rightHandSide.ContourPoint[0]; + vec[1] = leftHandSide.ContourPoint[1] - rightHandSide.ContourPoint[1]; + vec[2] = leftHandSide.ContourPoint[2] - rightHandSide.ContourPoint[2]; double n[3]; - n[0] = rightHandSide.contourNormal[0]; - n[1] = rightHandSide.contourNormal[1]; - n[2] = rightHandSide.contourNormal[2]; + n[0] = rightHandSide.ContourNormal[0]; + n[1] = rightHandSide.ContourNormal[1]; + n[2] = rightHandSide.ContourNormal[2]; double dot = vtkMath::Dot(n, vec); double n2[3]; - n2[0] = leftHandSide.contourNormal[0]; - n2[1] = leftHandSide.contourNormal[1]; - n2[2] = leftHandSide.contourNormal[2]; + n2[0] = leftHandSide.ContourNormal[0]; + n2[1] = leftHandSide.ContourNormal[1]; + n2[2] = leftHandSide.ContourNormal[2]; // The normals of both contours have to be parallel but not of the same orientation - double lengthLHS = leftHandSide.contourNormal.GetNorm(); - double lengthRHS = rightHandSide.contourNormal.GetNorm(); + double lengthLHS = leftHandSide.ContourNormal.GetNorm(); + double lengthRHS = rightHandSide.ContourNormal.GetNorm(); double dot2 = vtkMath::Dot(n, n2); bool contoursParallel = mitk::Equal(fabs(lengthLHS * lengthRHS), fabs(dot2), 0.001); if (mitk::Equal(dot, 0.0, 0.001) && contoursParallel) return true; else return false; } mitk::SurfaceInterpolationController::ContourPositionInformation CreateContourPositionInformation( - mitk::Surface::Pointer contour) + mitk::Surface::Pointer contour, const mitk::PlaneGeometry* planeGeometry) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; - contourInfo.contour = contour; - double n[3]; - double p[3]; - contour->GetVtkPolyData()->GetPoints()->GetPoint(0, p); + contourInfo.Contour = contour; + mitk::ScalarType n[3]; vtkPolygon::ComputeNormal(contour->GetVtkPolyData()->GetPoints(), n); - contourInfo.contourNormal = n; - contourInfo.contourPoint = p; + contourInfo.ContourNormal = n; + contourInfo.Pos = -1; + contourInfo.TimeStep = std::numeric_limits::max(); + contourInfo.plane = const_cast(planeGeometry); + + auto contourIntArray = vtkIntArray::SafeDownCast( contour->GetVtkPolyData()->GetFieldData()->GetAbstractArray(0) ); + + if (contourIntArray->GetSize() < 2) + { + MITK_ERROR << "In CreateContourPositionInformation. The contourIntArray is empty."; + } + contourInfo.LabelValue = contourIntArray->GetValue(0); + contourInfo.LayerValue = contourIntArray->GetValue(1); + + if (contourIntArray->GetSize() >= 3) + { + contourInfo.TimeStep = contourIntArray->GetValue(2); + } + + contourInfo.SliceIndex = 0; + return contourInfo; -} +}; mitk::SurfaceInterpolationController::SurfaceInterpolationController() - : m_SelectedSegmentation(nullptr), m_CurrentTimePoint(0.) + : m_SelectedSegmentation(nullptr), + m_CurrentTimePoint(0.), + m_ContourIndex(0), + m_ContourPosIndex(0), + m_NumberOfLayersInCurrentSegmentation(0), + m_PreviousActiveLabelValue(0), + m_CurrentActiveLabelValue(0), + m_PreviousLayerIndex(0), + m_CurrentLayerIndex(0) { m_DistanceImageSpacing = 0.0; m_ReduceFilter = ReduceContourSetFilter::New(); m_NormalsFilter = ComputeContourSetNormalsFilter::New(); m_InterpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); // m_TimeSelector = ImageTimeSelector::New(); m_ReduceFilter->SetUseProgressBar(false); // m_ReduceFilter->SetProgressStepSize(1); m_NormalsFilter->SetUseProgressBar(true); m_NormalsFilter->SetProgressStepSize(1); m_InterpolateSurfaceFilter->SetUseProgressBar(true); m_InterpolateSurfaceFilter->SetProgressStepSize(7); m_Contours = Surface::New(); m_PolyData = vtkSmartPointer::New(); vtkSmartPointer points = vtkSmartPointer::New(); m_PolyData->SetPoints(points); + m_NumberOfConnectionsAdded = 0; + m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; } mitk::SurfaceInterpolationController::~SurfaceInterpolationController() +{ + // Removing all observers + this->RemoveObservers(); +} + +void mitk::SurfaceInterpolationController::RemoveObservers() { // Removing all observers auto dataIter = m_SegmentationObserverTags.begin(); for (; dataIter != m_SegmentationObserverTags.end(); ++dataIter) { (*dataIter).first->RemoveObserver((*dataIter).second); } m_SegmentationObserverTags.clear(); } 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::AddNewContour(mitk::Surface::Pointer newContour) { if (newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) { - ContourPositionInformation contourInfo = CreateContourPositionInformation(newContour); + ContourPositionInformation contourInfo = CreateContourPositionInformation(newContour, nullptr); this->AddToInterpolationPipeline(contourInfo); - this->Modified(); } } -void mitk::SurfaceInterpolationController::AddNewContours(std::vector newContours) +void mitk::SurfaceInterpolationController::AddNewContours(const std::vector& newContours, + std::vector& contourPlanes, + bool reinitializationAction) { - for (unsigned int i = 0; i < newContours.size(); ++i) + if (newContours.size() != contourPlanes.size()) { - if (newContours.at(i)->GetVtkPolyData()->GetNumberOfPoints() > 0) + MITK_ERROR << "SurfaceInterpolationController::AddNewContours. contourPlanes and newContours are not of the same size."; + } + + for (size_t i = 0; i < newContours.size(); ++i) + { + const auto &newContour = newContours[i]; + + const mitk::PlaneGeometry * planeGeometry = contourPlanes[i]; + if (newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) { - ContourPositionInformation contourInfo = CreateContourPositionInformation(newContours.at(i)); - this->AddToInterpolationPipeline(contourInfo); + auto contourInfo = CreateContourPositionInformation(newContour, planeGeometry); + if (!reinitializationAction) + { + contourInfo.ContourPoint = this->ComputeInteriorPointOfContour(contourInfo, + dynamic_cast(m_SelectedSegmentation) ); + } + else + { + auto vtkPolyData = contourInfo.Contour->GetVtkPolyData(); + auto pointVtkArray = vtkDoubleArray::SafeDownCast(vtkPolyData->GetFieldData()->GetAbstractArray(1)); + mitk::ScalarType *ptArr = new mitk::ScalarType[3]; + for (int i = 0; i < pointVtkArray->GetSize(); ++i) + ptArr[i] = pointVtkArray->GetValue(i); + + mitk::Point3D pt3D; + pt3D.FillPoint(ptArr); + contourInfo.ContourPoint = pt3D; + } + + this->AddToInterpolationPipeline(contourInfo, reinitializationAction); } } this->Modified(); } -void mitk::SurfaceInterpolationController::AddToInterpolationPipeline(ContourPositionInformation contourInfo) +mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() { - if (!m_SelectedSegmentation) + DataNode* segmentationNode = nullptr; + mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(m_SelectedSegmentation->GetUID()); + auto dataNodeObjects = m_DataStorage->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; +} + +void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) +{ + auto planeGeometry = contourInfo.plane; + auto planeGeometryData = mitk::PlanarCircle::New(); + planeGeometryData->SetPlaneGeometry(planeGeometry); + mitk::Point2D p1; + planeGeometry->Map(planeGeometry->GetCenter(), p1); + planeGeometryData->PlaceFigure(p1); + planeGeometryData->SetCurrentControlPoint(p1); + if (planeGeometry) { - return; + auto segmentationNode = this->GetSegmentationImageNode(); + auto isContourPlaneGeometry = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); + + mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = + m_DataStorage->GetDerivations(segmentationNode, isContourPlaneGeometry); + + auto contourFound = false; + + // Go through the pre-existing contours and check if the contour position matches them. + for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) + { + auto layerID = dynamic_cast(it->Value()->GetProperty("layerID"))->GetValue(); + auto labelID = dynamic_cast(it->Value()->GetProperty("labelID"))->GetValue(); + auto posID = dynamic_cast(it->Value()->GetProperty("position"))->GetValue(); + bool sameLayer = (layerID == contourInfo.LayerValue); + bool sameLabel = (labelID == contourInfo.LabelValue); + bool samePos = (posID == contourInfo.Pos); + + if (samePos & sameLabel & sameLayer) + { + contourFound = true; + it->Value()->SetData(planeGeometryData); + break; + } + } + + if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + { + MITK_ERROR << "Invalid time point requested in AddPlaneGeometryNodeToDataStorage."; + return; + } + + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + + // Go through the contourPlaneGeometry Data and add the segmentationNode to it. + if (!contourFound) + { + std::string contourName = "contourPlane " + std::to_string(m_ContourIndex); + + auto 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("layerID", mitk::UIntProperty::New(contourInfo.LayerValue)); + contourPlaneGeometryDataNode->SetProperty("labelID", mitk::UShortProperty::New(contourInfo.LabelValue)); + contourPlaneGeometryDataNode->SetProperty("position", mitk::IntProperty::New(contourInfo.Pos)); + contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(currentTimeStep)); + + contourPlaneGeometryDataNode->SetProperty("px", mitk::DoubleProperty::New(contourInfo.ContourPoint[0])); + contourPlaneGeometryDataNode->SetProperty("py", mitk::DoubleProperty::New(contourInfo.ContourPoint[1])); + contourPlaneGeometryDataNode->SetProperty("pz", mitk::DoubleProperty::New(contourInfo.ContourPoint[2])); + + m_DataStorage->Add(contourPlaneGeometryDataNode, segmentationNode); + } } +} + +void mitk::SurfaceInterpolationController::AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction) +{ + if (!m_SelectedSegmentation) + return; - int pos(-1); if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - ContourPositionInformationVec2D currentContours = m_ListOfInterpolationSessions[m_SelectedSegmentation]; - ContourPositionInformationList currentContourList = currentContours[currentTimeStep]; + // Get current time step either from the + auto GetCurrentTimeStep = [=](ContourPositionInformation contourInfo) + { + if (reinitializationAction) + { + return contourInfo.TimeStep; + } + return static_cast(m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint)); + }; + const auto currentTimeStep = GetCurrentTimeStep(contourInfo); + auto GetContourLayerID = [=](ContourPositionInformation contourInfo) + { + unsigned int currentLayerID; + if(reinitializationAction) + { + if (contourInfo.LayerValue == std::numeric_limits::max()) + { + MITK_ERROR << "In mitk::SurfaceInterpolationController::AddToInterpolationPipeline. Problem in finding layerID"; + } + currentLayerID = contourInfo.LayerValue; + } + else + { + try + { + currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); + } + catch (const std::exception& e) + { + MITK_ERROR << "Unable to cast image to LabelSetImage. " << e.what() << '\n'; + } + } + return currentLayerID; + }; + + unsigned int currentLayerID = GetContourLayerID(contourInfo); + + ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); + ContourPositionInformationVec2D ¤tTimeStepContoursList = currentImageContours.at(currentTimeStep); + ContourPositionInformationList ¤tContourList = currentTimeStepContoursList.at(currentLayerID); - mitk::Surface *newContour = contourInfo.contour; - for (unsigned int i = 0; i < currentContourList.size(); i++) + int replacementIndex = -1; + int pos = -1; + mitk::Surface* newContour = contourInfo.Contour; + + for (size_t i = 0; i < currentContourList.size(); i++) { - ContourPositionInformation contourFromList = currentContourList.at(i); - if (ContoursCoplanar(contourInfo, contourFromList)) + auto& contourFromList = currentContourList.at(i); + bool contoursAreCoplanar = ContoursCoplanar(contourInfo, contourFromList); + bool contoursHaveSameLabel = contourInfo.LabelValue == contourFromList.LabelValue; + + // Coplanar contours have the same "pos". + if (contoursAreCoplanar) { - pos = i; - break; + pos = contourFromList.Pos; + if (contoursHaveSameLabel) + { + replacementIndex = i; + } } } + // The current contour has the same label and position as the current slice and a replacement is done. + if (replacementIndex != -1) + { + contourInfo.Pos = pos; + m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).at(replacementIndex) = contourInfo; + + if (!reinitializationAction) + { + this->AddPlaneGeometryNodeToDataStorage(contourInfo); + } + return; + } - // Don't save a new empty contour - if (pos == -1 && newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) + // Case that there is no contour in the current slice with the current label + if (pos == -1) + pos = m_ContourPosIndex++; + + m_ContourIndex++; + contourInfo.Pos = pos; + m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).push_back(contourInfo); + + if (contourInfo.plane == nullptr) { - m_ReduceFilter->SetInput(m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].size(), - newContour); - m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].push_back(contourInfo); + MITK_ERROR << "contourInfo plane is null."; } - else if (pos != -1 && newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) + if (!reinitializationAction) { - m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].at(pos) = contourInfo; - m_ReduceFilter->SetInput(pos, newContour); + this->AddPlaneGeometryNodeToDataStorage(contourInfo); } - else if (newContour->GetVtkPolyData()->GetNumberOfPoints() == 0) + + if (newContour->GetVtkPolyData()->GetNumberOfPoints() == 0) { this->RemoveContour(contourInfo); + if (m_ContourIndex > 0) + m_ContourIndex--; + if (m_ContourIndex > 0) + m_ContourIndex--; } } bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo) { if (!m_SelectedSegmentation) { return false; } if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return false; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + unsigned int currentLayerID = 0; + try + { + currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); + } + catch (const std::exception& e) + { + MITK_ERROR << e.what() << '\n'; + } - auto it = m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].begin(); - while (it != m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].end()) + auto it = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).begin(); + while (it != m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).end()) { - ContourPositionInformation currentContour = (*it); + const ContourPositionInformation ¤tContour = (*it); if (ContoursCoplanar(currentContour, contourInfo)) { - m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].erase(it); + m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).erase(it); this->ReinitializeInterpolation(); return true; } ++it; } return false; } -const mitk::Surface *mitk::SurfaceInterpolationController::GetContour(ContourPositionInformation contourInfo) +const mitk::Surface *mitk::SurfaceInterpolationController::GetContour(const ContourPositionInformation &contourInfo) { if (!m_SelectedSegmentation) { return nullptr; } if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return nullptr; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + const auto activeLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); + const auto &contourList = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(activeLayerID); - ContourPositionInformationList contourList = m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep]; - for (unsigned int i = 0; i < contourList.size(); ++i) + for (auto ¤tContour : contourList) { - ContourPositionInformation currentContour = contourList.at(i); if (ContoursCoplanar(contourInfo, currentContour)) - return currentContour.contour; + { + return currentContour.Contour; + } } return nullptr; } unsigned int mitk::SurfaceInterpolationController::GetNumberOfContours() { if (!m_SelectedSegmentation) { return -1; } if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return -1; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + auto contourDoubleList = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep); + + unsigned int numContours = 0; + for (auto& contourList : contourDoubleList) + { + + numContours += contourList.size(); + } - return m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].size(); + return numContours; +} + +void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel) +{ + this->ReinitializeInterpolation(); + + if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + { + MITK_ERROR << "Invalid time point requested for interpolation pipeline."; + return; + } + const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + + unsigned int currentLayerID = 0; + try + { + currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); + } + catch (const std::exception& e) + { + MITK_ERROR << e.what() << '\n'; + } + + ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); + + if (currentImageContours.size() <= currentTimeStep) + { + MITK_INFO << "Contours for current time step don't exist."; + return; + } + ContourPositionInformationVec2D ¤tTimeStepContoursList = currentImageContours.at(currentTimeStep); + + if (currentTimeStepContoursList.size() <= currentLayerID) + { + MITK_INFO << "Contours for current layer don't exist."; + return; + } + ContourPositionInformationList ¤tContours = currentTimeStepContoursList.at(currentLayerID); + + for (size_t i = 0; i < currentContours.size(); ++i) + { + if (currentContours.at(i).LabelValue == activeLabel) + { + m_ListOfInterpolationSessions.at(m_SelectedSegmentation).at(currentTimeStep).push_back(currentContours.at(i)); + m_ReduceFilter->SetInput(m_ListOfInterpolationSessions.at(m_SelectedSegmentation).at(currentTimeStep).size()-1, currentContours.at(i).Contour); + } + } } void mitk::SurfaceInterpolationController::Interpolate() { if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { 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; } const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - m_ReduceFilter->Update(); - m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); + if (m_CurrentNumberOfReducedContours == 1) { vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); if (tmp == nullptr) { m_CurrentNumberOfReducedContours = 0; } } + // We use the timeSelector to get the segmentation image for the current segmentation. mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_SelectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); + mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); + itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); + AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); - for (unsigned int i = 0; i < m_CurrentNumberOfReducedContours; i++) + + for (size_t i = 0; i < m_CurrentNumberOfReducedContours; ++i) { mitk::Surface::Pointer reducedContour = m_ReduceFilter->GetOutput(i); reducedContour->DisconnectPipeline(); m_NormalsFilter->SetInput(i, reducedContour); m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); } if (m_CurrentNumberOfReducedContours < 2) { // If no interpolation is possible reset the interpolation result + MITK_WARN << "No interpolation is possible. Too few reduced contours."; m_InterpolationResult = nullptr; return; } // 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(20); + imageToSurfaceFilter->SetSmoothIteration(1); imageToSurfaceFilter->Update(); mitk::Surface::Pointer interpolationResult = mitk::Surface::New(); interpolationResult->Expand(m_SelectedSegmentation->GetTimeSteps()); auto geometry = m_SelectedSegmentation->GetTimeGeometry()->Clone(); geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); interpolationResult->SetTimeGeometry(geometry); interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), currentTimeStep); - m_InterpolationResult = interpolationResult; m_DistanceImageSpacing = m_InterpolateSurfaceFilter->GetDistanceImageSpacing(); - vtkSmartPointer polyDataAppender = vtkSmartPointer::New(); - for (unsigned int i = 0; i < m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].size(); i++) - { - polyDataAppender->AddInputData( - m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].at(i).contour->GetVtkPolyData()); - } - polyDataAppender->Update(); - m_Contours->SetVtkPolyData(polyDataAppender->GetOutput()); - auto* contoursGeometry = static_cast(m_Contours->GetTimeGeometry()); auto timeBounds = geometry->GetTimeBounds(currentTimeStep); contoursGeometry->SetFirstTimePoint(timeBounds[0]); contoursGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Last progress step mitk::ProgressBar::GetInstance()->Progress(20); - m_InterpolationResult->DisconnectPipeline(); } mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult() { return m_InterpolationResult; } mitk::Surface *mitk::SurfaceInterpolationController::GetContoursAsSurface() { return m_Contours; } + 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); } mitk::Image::Pointer mitk::SurfaceInterpolationController::GetCurrentSegmentation() { return m_SelectedSegmentation; } mitk::Image *mitk::SurfaceInterpolationController::GetImage() { return m_InterpolateSurfaceFilter->GetOutput(); } double mitk::SurfaceInterpolationController::EstimatePortionOfNeededMemory() { double numberOfPointsAfterReduction = m_ReduceFilter->GetNumberOfPointsAfterReduction() * 3; double sizeOfPoints = pow(numberOfPointsAfterReduction, 2) * sizeof(double); double totalMem = mitk::MemoryUtilities::GetTotalSizeOfPhysicalRam(); double percentage = sizeOfPoints / totalMem; return percentage; } unsigned int mitk::SurfaceInterpolationController::GetNumberOfInterpolationSessions() { return m_ListOfInterpolationSessions.size(); } template void mitk::SurfaceInterpolationController::GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result) { result->Graft(input); } void mitk::SurfaceInterpolationController::SetCurrentSegmentationInterpolationList(mitk::Image::Pointer segmentation) { this->SetCurrentInterpolationSession(segmentation); } void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::Image::Pointer currentSegmentationImage) { if (currentSegmentationImage.GetPointer() == m_SelectedSegmentation) + { return; + } if (currentSegmentationImage.IsNull()) { m_SelectedSegmentation = nullptr; return; } - m_SelectedSegmentation = currentSegmentationImage.GetPointer(); - auto it = m_ListOfInterpolationSessions.find(currentSegmentationImage.GetPointer()); - // If the session does not exist yet create a new ContourPositionPairList otherwise reinitialize the interpolation - // pipeline - if (it == m_ListOfInterpolationSessions.end()) + try + { + auto labelSetImage = dynamic_cast(m_SelectedSegmentation); + auto it = m_ListOfContours.find(currentSegmentationImage.GetPointer()); + // If the session does not exist yet create a new ContourPositionPairList otherwise reinitialize the interpolation + // pipeline + if (it == m_ListOfContours.end()) + { + ContourPositionInformationVec3D newList; + + auto numTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); + + for (size_t t = 0; t < numTimeSteps; ++t) + { + auto twoDList = ContourPositionInformationVec2D(); + auto contourList = ContourPositionInformationList(); + twoDList.push_back(contourList); + newList.push_back(twoDList); + } + + m_ListOfContours[m_SelectedSegmentation] = newList; + + m_InterpolationResult = nullptr; + m_CurrentNumberOfReducedContours = 0; + + auto command = itk::MemberCommand::New(); + command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); + + m_NumberOfLayersInCurrentSegmentation = labelSetImage->GetNumberOfLayers(); + } + + // auto labelSetImage = dynamic_cast(m_SelectedSegmentation); + auto numLayersInSelectedSegmentation = labelSetImage->GetNumberOfLayers(); + // Maybe this has to change. + for (size_t layerID = 0; layerID < numLayersInSelectedSegmentation; ++layerID) + { + this->AddLabelSetConnection(layerID); + } + } + catch (const std::exception &e) + { + MITK_ERROR << "Unable to cast image as LabelSetImage"; + } + + auto it2 = m_ListOfInterpolationSessions.find(currentSegmentationImage.GetPointer()); + if (it2 == m_ListOfInterpolationSessions.end()) { ContourPositionInformationVec2D newList; - m_ListOfInterpolationSessions.insert( - std::pair(m_SelectedSegmentation, newList)); + m_ListOfInterpolationSessions[m_SelectedSegmentation] = newList; m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; - - itk::MemberCommand::Pointer command = - itk::MemberCommand::New(); - command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); - m_SegmentationObserverTags.insert(std::pair( - m_SelectedSegmentation, m_SelectedSegmentation->AddObserver(itk::DeleteEvent(), command))); } this->ReinitializeInterpolation(); } bool mitk::SurfaceInterpolationController::ReplaceInterpolationSession(mitk::Image::Pointer oldSession, mitk::Image::Pointer newSession) { if (oldSession.IsNull() || newSession.IsNull()) return false; if (oldSession.GetPointer() == newSession.GetPointer()) return false; if (!mitk::Equal(*(oldSession->GetGeometry()), *(newSession->GetGeometry()), mitk::eps, false)) return false; auto it = m_ListOfInterpolationSessions.find(oldSession.GetPointer()); if (it == m_ListOfInterpolationSessions.end()) return false; if (!newSession->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_WARN << "Interpolation session cannot be replaced. Currently selected timepoint is not in the time bounds of the new session. Time point: " << m_CurrentTimePoint; return false; } ContourPositionInformationVec2D oldList = (*it).second; - m_ListOfInterpolationSessions.insert( - std::pair(newSession.GetPointer(), oldList)); + + m_ListOfInterpolationSessions[newSession.GetPointer()] = oldList; + itk::MemberCommand::Pointer command = itk::MemberCommand::New(); + command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); - m_SegmentationObserverTags.insert( - std::pair(newSession, newSession->AddObserver(itk::DeleteEvent(), command))); + + m_SegmentationObserverTags[newSession] = newSession->AddObserver(itk::DeleteEvent(), command); if (m_SelectedSegmentation == oldSession) m_SelectedSegmentation = newSession; const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_SelectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); + m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); this->RemoveInterpolationSession(oldSession); return true; } void mitk::SurfaceInterpolationController::RemoveSegmentationFromContourList(mitk::Image *segmentation) { this->RemoveInterpolationSession(segmentation); } void mitk::SurfaceInterpolationController::RemoveInterpolationSession(mitk::Image::Pointer segmentationImage) { if (segmentationImage) { if (m_SelectedSegmentation == segmentationImage) { m_NormalsFilter->SetSegmentationBinaryImage(nullptr); m_SelectedSegmentation = nullptr; } m_ListOfInterpolationSessions.erase(segmentationImage); + m_ListOfContours.erase(segmentationImage); + // Remove observer auto pos = m_SegmentationObserverTags.find(segmentationImage); if (pos != m_SegmentationObserverTags.end()) { segmentationImage->RemoveObserver((*pos).second); m_SegmentationObserverTags.erase(pos); } } } void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() { // Removing all observers auto dataIter = m_SegmentationObserverTags.begin(); while (dataIter != m_SegmentationObserverTags.end()) { mitk::Image *image = (*dataIter).first; image->RemoveObserver((*dataIter).second); ++dataIter; } m_SegmentationObserverTags.clear(); m_SelectedSegmentation = nullptr; m_ListOfInterpolationSessions.clear(); + m_ListOfContours.clear(); } -void mitk::SurfaceInterpolationController::ReinitializeInterpolation(mitk::Surface::Pointer contours) -{ - // 1. detect coplanar contours - // 2. merge coplanar contours into a single surface - // 4. add contour to pipeline - - // Split the surface into separate polygons - vtkSmartPointer existingPolys; - vtkSmartPointer existingPoints; - existingPolys = contours->GetVtkPolyData()->GetPolys(); - existingPoints = contours->GetVtkPolyData()->GetPoints(); - existingPolys->InitTraversal(); - - vtkSmartPointer ids = vtkSmartPointer::New(); - typedef std::pair PointNormalPair; - std::vector list; - std::vector> pointsList; - int count(0); - for (existingPolys->InitTraversal(); existingPolys->GetNextCell(ids);) - { - // Get the points - vtkSmartPointer points = vtkSmartPointer::New(); - existingPoints->GetPoints(ids, points); - ++count; - pointsList.push_back(points); - - PointNormalPair p_n; - double n[3]; - vtkPolygon::ComputeNormal(points, n); - p_n.first = n; - double p[3]; +template +std::vector GetPixelValuesPresentInImage(mitk::LabelSetImage* labelSetImage) +{ + mitk::ImagePixelReadAccessor readAccessor(labelSetImage); + std::vector pixelsPresent; - existingPoints->GetPoint(ids->GetId(0), p); - p_n.second = p; + std::size_t numberOfPixels = 1; + for (int dim = 0; dim < static_cast(VImageDimension); ++dim) + numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); - ContourPositionInformation p_info; - p_info.contourNormal = n; - p_info.contourPoint = p; - list.push_back(p_info); - continue; - } - - // Detect and sort coplanar polygons - auto outer = list.begin(); - std::vector>> relatedPoints; - while (outer != list.end()) + auto src = readAccessor.GetData(); + for (std::size_t i = 0; i < numberOfPixels; ++i) { - auto inner = outer; - ++inner; - std::vector> rel; - auto pointsIter = pointsList.begin(); - rel.push_back((*pointsIter)); - pointsIter = pointsList.erase(pointsIter); - - while (inner != list.end()) + mitk::Label::PixelType pixelVal = *(src + i); + if ( (std::find(pixelsPresent.begin(), pixelsPresent.end(), pixelVal) == pixelsPresent.end()) && (pixelVal != 0) ) { - if (ContoursCoplanar((*outer), (*inner))) - { - inner = list.erase(inner); - rel.push_back((*pointsIter)); - pointsIter = pointsList.erase(pointsIter); - } - else - { - ++inner; - ++pointsIter; - } + pixelsPresent.push_back(pixelVal); } - relatedPoints.push_back(rel); - ++outer; } + return pixelsPresent; +} - // Build the separate surfaces again - std::vector finalSurfaces; - for (unsigned int i = 0; i < relatedPoints.size(); ++i) +void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label, + unsigned int timeStep, + unsigned int layerID) +{ + auto isContourEqualToLabelValue = [label] (ContourPositionInformation& contour) -> bool { - vtkSmartPointer contourSurface = vtkSmartPointer::New(); - vtkSmartPointer points = vtkSmartPointer::New(); - vtkSmartPointer polygons = vtkSmartPointer::New(); - unsigned int pointId(0); - for (unsigned int j = 0; j < relatedPoints.at(i).size(); ++j) - { - unsigned int numPoints = relatedPoints.at(i).at(j)->GetNumberOfPoints(); - vtkSmartPointer polygon = vtkSmartPointer::New(); - polygon->GetPointIds()->SetNumberOfIds(numPoints); - polygon->GetPoints()->SetNumberOfPoints(numPoints); - vtkSmartPointer currentPoints = relatedPoints.at(i).at(j); - for (unsigned k = 0; k < numPoints; ++k) - { - points->InsertPoint(pointId, currentPoints->GetPoint(k)); - polygon->GetPointIds()->SetId(k, pointId); - ++pointId; - } - polygons->InsertNextCell(polygon); - } - contourSurface->SetPoints(points); - contourSurface->SetPolys(polygons); - contourSurface->BuildLinks(); - mitk::Surface::Pointer surface = mitk::Surface::New(); - surface->SetVtkPolyData(contourSurface); - finalSurfaces.push_back(surface); - } - - // Add detected contours to interpolation pipeline - this->AddNewContours(finalSurfaces); + return (contour.LabelValue == label); + }; + + ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); + ContourPositionInformationList ¤tContourList = currentImageContours.at(timeStep).at(layerID); + unsigned int numContoursBefore = currentContourList.size(); + auto it = std::remove_if(currentContourList.begin(), currentContourList.end(), isContourEqualToLabelValue); + currentContourList.erase(it, currentContourList.end()); + unsigned int numContoursAfter = currentContourList.size(); + unsigned int numContours = numContoursAfter - numContoursBefore; + m_ContourIndex -= numContours; } void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject & /*event*/) { auto *tempImage = dynamic_cast(const_cast(caller)); if (tempImage) { if (m_SelectedSegmentation == tempImage) { m_NormalsFilter->SetSegmentationBinaryImage(nullptr); m_SelectedSegmentation = nullptr; } m_SegmentationObserverTags.erase(tempImage); + m_ListOfContours.erase(tempImage); m_ListOfInterpolationSessions.erase(tempImage); } } void mitk::SurfaceInterpolationController::ReinitializeInterpolation() { // If session has changed reset the pipeline m_ReduceFilter->Reset(); m_NormalsFilter->Reset(); m_InterpolateSurfaceFilter->Reset(); + // Empty out the listOfInterpolationSessions + m_ListOfInterpolationSessions[m_SelectedSegmentation].clear(); + itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); if (m_SelectedSegmentation) { if (!m_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 = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); + // Set reference image for interpolation surface filter mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_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()); + // Resize listofinterpolationsessions and listofcontours to numTimeSteps unsigned int numTimeSteps = m_SelectedSegmentation->GetTimeSteps(); unsigned int size = m_ListOfInterpolationSessions[m_SelectedSegmentation].size(); + if (size != numTimeSteps) { - m_ListOfInterpolationSessions[m_SelectedSegmentation].resize(numTimeSteps); + m_ListOfInterpolationSessions.at(m_SelectedSegmentation).resize(numTimeSteps); + } + } +} + +void mitk::SurfaceInterpolationController::AddLabelSetConnection(unsigned int layerID) +{ + if (m_SelectedSegmentation != nullptr) + { + try + { + auto workingImage = dynamic_cast(m_SelectedSegmentation); + auto previousLayerID = workingImage->GetActiveLayer(); + workingImage->SetActiveLayer(layerID); + auto activeLabelSet = workingImage->GetLabelSet(layerID); + activeLabelSet->RemoveLabelEvent += mitk::MessageDelegate( + this, &mitk::SurfaceInterpolationController::OnRemoveLabel); + activeLabelSet->ActiveLabelEvent += mitk::MessageDelegate1( + this, &mitk::SurfaceInterpolationController::OnActiveLabel); + workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( + this, &mitk::SurfaceInterpolationController::OnLayerChanged); + m_NumberOfConnectionsAdded += 1; + workingImage->SetActiveLayer(previousLayerID); + } + catch(const std::exception& e) + { + MITK_ERROR << e.what() << '\n'; + } + } +} + +void mitk::SurfaceInterpolationController::AddLabelSetConnection() +{ + if (m_SelectedSegmentation != nullptr) + { + try + { + auto workingImage = dynamic_cast(m_SelectedSegmentation); + auto activeLabelSet = workingImage->GetActiveLabelSet(); + activeLabelSet->RemoveLabelEvent += mitk::MessageDelegate( + this, &mitk::SurfaceInterpolationController::OnRemoveLabel); + workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1( + this, &mitk::SurfaceInterpolationController::OnActiveLabel); + workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( + this, &mitk::SurfaceInterpolationController::OnLayerChanged); + m_NumberOfConnectionsAdded += 1; + } + catch(const std::exception& e) + { + MITK_ERROR << e.what() << '\n'; } + } +} - if (currentTimeStep < numTimeSteps) +void mitk::SurfaceInterpolationController::RemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID) +{ + labelSetImage->SetActiveLayer(layerID); + labelSetImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( + this, &mitk::SurfaceInterpolationController::OnRemoveLabel); + // labelSetImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( + // this, &mitk::SurfaceInterpolationController::OnActiveLabel); + labelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( + this, &mitk::SurfaceInterpolationController::OnLayerChanged); + m_NumberOfConnectionsAdded -= 1; +} + +void mitk::SurfaceInterpolationController::RemoveLabelSetConnection() +{ + if (m_SelectedSegmentation != nullptr) + { + try + { + auto workingImage = dynamic_cast(m_SelectedSegmentation); + workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( + this, &mitk::SurfaceInterpolationController::OnRemoveLabel); + workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( + this, &mitk::SurfaceInterpolationController::OnActiveLabel); + workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( + this, &mitk::SurfaceInterpolationController::OnLayerChanged); + } + catch (const std::exception& e) { - unsigned int numContours = m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep].size(); - for (unsigned int c = 0; c < numContours; ++c) + std::cerr << e.what() << '\n'; + } + } +} + +void mitk::SurfaceInterpolationController::OnRemoveLabel() +{ + if (m_SelectedSegmentation != nullptr) + { + auto numTimeSteps = m_SelectedSegmentation->GetTimeGeometry()->CountTimeSteps(); + try + { + auto labelSetImage = dynamic_cast(m_SelectedSegmentation); + auto currentLayerID = labelSetImage->GetActiveLayer(); + + for(unsigned int t = 0; t < numTimeSteps; ++t) { - m_ReduceFilter->SetInput(c, - m_ListOfInterpolationSessions[m_SelectedSegmentation][currentTimeStep][c].contour); + this->RemoveContours(m_PreviousActiveLabelValue,t,currentLayerID); } - m_ReduceFilter->Update(); + } + catch(const std::exception& e) + { + std::cerr << e.what() << '\n'; + } + } +} + +void mitk::SurfaceInterpolationController::OnActiveLabel(mitk::Label::PixelType newActiveLabelValue) +{ + m_PreviousActiveLabelValue = m_CurrentActiveLabelValue; + m_CurrentActiveLabelValue = newActiveLabelValue; +} + +unsigned int mitk::SurfaceInterpolationController::GetNumberOfLayersInCurrentSegmentation() const +{ + return m_NumberOfLayersInCurrentSegmentation; +} + +void mitk::SurfaceInterpolationController::SetNumberOfLayersInCurrentSegmentation(unsigned int numLayers) +{ + m_NumberOfLayersInCurrentSegmentation = numLayers; +} + +void mitk::SurfaceInterpolationController::OnAddLayer() +{ + assert(m_SelectedSegmentation != nullptr); + auto& contoursForSegmentation = m_ListOfContours.at(m_SelectedSegmentation); + // Push an information list for each time step. + for(size_t t = 0; t < contoursForSegmentation.size(); ++t) + { + contoursForSegmentation.at(t).push_back( ContourPositionInformationList() ); + } +} + +void mitk::SurfaceInterpolationController::OnRemoveLayer() +{ + assert(m_SelectedSegmentation != nullptr); + auto& contoursForSegmentation = m_ListOfContours.at(m_SelectedSegmentation); + // Erase the layers in each of the time steps. - m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); - if (m_CurrentNumberOfReducedContours == 1) + // The previous layer is removed + for (size_t t = 0; t < contoursForSegmentation.size(); ++t) + { + assert(m_PreviousLayerIndex < contoursForSegmentation.at(t).size()); + auto& contoursAtTimeStep = contoursForSegmentation.at(t); + for (size_t c = m_CurrentLayerIndex+1; c < contoursAtTimeStep.size(); ++c) + { + auto& contoursInCurrentLayer = contoursAtTimeStep.at(c); + for (auto& contour : contoursInCurrentLayer) { - vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); - if (tmp == nullptr) + contour.LayerValue = contour.LayerValue - 1; + } + } + } + + for (size_t t = 0; t < contoursForSegmentation.size(); ++t) + { + assert (m_CurrentLayerIndex < contoursForSegmentation.at(t).size()); + contoursForSegmentation.at(t).erase(contoursForSegmentation.at(t).begin() + m_PreviousLayerIndex); + } + this->PrintListOfContours(); + this->Modified(); +} + +void mitk::SurfaceInterpolationController::OnLayerChanged() +{ + auto currentLayer = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); + m_PreviousLayerIndex = m_CurrentLayerIndex; + m_CurrentLayerIndex = currentLayer; +} + +void mitk::SurfaceInterpolationController::PrintListOfContours() +{ + std::cout << "----------------------------------------------\n"; + auto contourStruct = m_ListOfContours.at(m_SelectedSegmentation); + std::cout << "numTimeSteps: " << m_ListOfContours.at(m_SelectedSegmentation).size() << "\n"; + for (size_t t = 0; t < contourStruct.size(); ++t) + { + auto contourStruct2 = contourStruct[t]; + std::cout << "numLayers at current time step: " << contourStruct2.size() << "\n"; + for (size_t j = 0; j < contourStruct2.size(); ++j) + { + std::cout << "num contours at layer: " << contourStruct2[j].size() << "\n"; + auto a_contourList = contourStruct2[j]; + for (size_t c = 0; c < a_contourList.size(); ++c) + { + std::cout << a_contourList[c] << "\n"; + } + // std::cout << "\n"; + } + std::cout << "\n"; + } + std::cout << "----------------------------------------------\n"; +} + +mitk::SurfaceInterpolationController::ContourPositionInformationList& mitk::SurfaceInterpolationController::GetContours(unsigned int timeStep, unsigned int layerID) +{ + if (m_SelectedSegmentation == nullptr) + { + MITK_ERROR << "Invalid segmentation from mitk::SurfaceInterpolationController::GetContours"; + } + if (timeStep >= m_ListOfContours.at(m_SelectedSegmentation).size()) + { + MITK_ERROR << "Invalid timeStep from mitk::SurfaceInterpolationController::GetContours"; + } + if (layerID >= m_ListOfContours.at(m_SelectedSegmentation).at(timeStep).size()) + { + MITK_ERROR << "Invalid timeStep from mitk::SurfaceInterpolationController::GetContours"; + } + return m_ListOfContours.at(m_SelectedSegmentation).at(timeStep).at(layerID); +} + +void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector& contourList, + std::vector& contourPlanes) +{ + this->ClearInterpolationSession(); + + auto labelSetImage = dynamic_cast(m_SelectedSegmentation); + auto numLayers = labelSetImage->GetNumberOfLayers(); + + // Add layers to the m_ListOfContours + for (size_t layer = 0; layer < numLayers; ++layer) + { + this->OnAddLayer(); + } + + // Now the layers should be empty and the new layers can be added. + this->AddNewContours(contourList, contourPlanes, true); +} + +void mitk::SurfaceInterpolationController::ClearInterpolationSession() +{ + if (m_SelectedSegmentation != nullptr) + { + auto it = m_ListOfContours.find(m_SelectedSegmentation); + if (it != m_ListOfContours.end()) + { + auto timeSteps = m_ListOfContours[m_SelectedSegmentation].size(); + try + { + auto labelSetImage = dynamic_cast(m_SelectedSegmentation); + auto labelSetImageTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); + + if (timeSteps != labelSetImageTimeSteps) { - m_CurrentNumberOfReducedContours = 0; + MITK_ERROR << "Time steps are not the same."; } + + for (size_t t = 0; t < timeSteps; ++t) + { + m_ListOfContours[m_SelectedSegmentation][t].clear(); + } + + } + catch(std::bad_cast& e) + { + MITK_ERROR << "Unable to cast m_SelectedSegmentation to labelSetImage in ClearInterpolationSession"; } + } + } +} + +std::vector< mitk::Point3D > mitk::ContourExt::GetBoundingBoxGridPoints( + size_t planeDimension, + double startDim1, + size_t numPointsToSampleDim1, + double deltaDim1, + double startDim2, + size_t numPointsToSampleDim2, + double deltaDim2, + double valuePlaneDim) +{ + std::vector< mitk::Point3D > gridPoints; + for (size_t i = 0; i < numPointsToSampleDim1; ++i) + { + for (size_t j = 0; j < numPointsToSampleDim2; ++j) + { + mitk::ScalarType *ptVec = new mitk::ScalarType[3]; - for (unsigned int i = 0; i < m_CurrentNumberOfReducedContours; i++) + if (planeDimension == 0) { - m_NormalsFilter->SetInput(i, m_ReduceFilter->GetOutput(i)); - m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); + ptVec[0] = valuePlaneDim; + ptVec[1] = startDim1 + deltaDim1 * i; + ptVec[2] = startDim2 + deltaDim2 * j; } + else if (planeDimension == 1) + { + ptVec[0] = startDim1 + deltaDim1 * i; + ptVec[1] = valuePlaneDim; + ptVec[2] = startDim2 + deltaDim2 * j; + + } + else if (planeDimension == 2) + { + ptVec[0] = startDim1 + deltaDim1 * i; + ptVec[1] = startDim2 + deltaDim2 * j; + ptVec[2] = valuePlaneDim; + } + + mitk::Point3D pt3D; + pt3D.FillPoint(ptVec); + gridPoints.push_back(pt3D); + } + } + + return gridPoints; +} + +mitk::Point3D mitk::SurfaceInterpolationController::ComputeInteriorPointOfContour( + const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, + mitk::LabelSetImage * labelSetImage) +{ + if (labelSetImage->GetDimension() == 4) + { + return mitk::ContourExt::ComputeInteriorPointOfContour<4>(contour, labelSetImage, m_CurrentTimePoint); + } + else + { + return mitk::ContourExt::ComputeInteriorPointOfContour<3>(contour, labelSetImage, m_CurrentTimePoint); + } +} + +template +mitk::Point3D mitk::ContourExt::ComputeInteriorPointOfContour( + const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, + mitk::LabelSetImage * labelSetImage, + mitk::TimePointType currentTimePoint) +{ + mitk::ImagePixelReadAccessor readAccessor(labelSetImage); + + if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) + { + MITK_ERROR << "Invalid time point requested for interpolation pipeline."; + mitk::Point3D pt; + return pt; + } + + std::vector pixelsPresent; + const auto currentTimeStep = labelSetImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); + + auto polyData = contour.Contour->GetVtkPolyData(); + + polyData->ComputeCellsBounds(); + mitk::ScalarType cellBounds[6]; + polyData->GetCellsBounds(cellBounds); + + size_t numPointsToSample = 10; + mitk::ScalarType StartX = cellBounds[0]; + mitk::ScalarType StartY = cellBounds[2]; + mitk::ScalarType StartZ = cellBounds[4]; + + size_t deltaX = (cellBounds[1] - cellBounds[0]) / numPointsToSample; + size_t deltaY = (cellBounds[3] - cellBounds[2]) / numPointsToSample; + size_t deltaZ = (cellBounds[5] - cellBounds[4]) / numPointsToSample; + + auto planeOrientation = mitk::ContourExt::GetContourOrientation(contour.ContourNormal); + + std::vector points; + if (planeOrientation == 0) + { + points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, + StartY, numPointsToSample, deltaY, + StartZ, numPointsToSample, deltaZ, + StartX); + } + else if (planeOrientation == 1) + { + points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, + StartX, numPointsToSample, deltaX, + StartZ, numPointsToSample, deltaZ, + StartY); + } + else if (planeOrientation == 2) + { + points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, + StartX, numPointsToSample, deltaX, + StartY, numPointsToSample, deltaY, + StartZ); + } + mitk::Label::PixelType pixelVal; + mitk::Point3D pt3D; + std::vector pixelVals; + for (size_t i = 0; i < points.size(); ++i) + { + pt3D = points[i]; + itk::Index<3> itkIndex; + labelSetImage->GetGeometry()->WorldToIndex(pt3D, itkIndex); + + if (VImageDimension == 4) + { + itk::Index time3DIndex; + for (size_t i = 0; i < itkIndex.size(); ++i) + time3DIndex[i] = itkIndex[i]; + time3DIndex[3] = currentTimeStep; + + pixelVal = readAccessor.GetPixelByIndexSafe(time3DIndex); + } + else if (VImageDimension == 3) + { + itk::Index geomIndex; + for (size_t i=0;i mitk::eps) + { + planeOrientation = 2; + } + else if (fabs(dotY) > mitk::eps) + { + planeOrientation = 1; + } + else if(fabs(dotX) > mitk::eps) + { + planeOrientation = 0; } + return planeOrientation; } diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h index c808672918..5b274786df 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h @@ -1,250 +1,503 @@ /*============================================================================ 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_Included #define mitkSurfaceInterpolationController_h_Included -#include "mitkColorProperty.h" -#include "mitkCommon.h" -#include "mitkInteractionConst.h" -#include "mitkProperties.h" -#include "mitkRestorePlanePositionOperation.h" -#include "mitkSurface.h" +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include "mitkComputeContourSetNormalsFilter.h" #include "mitkCreateDistanceImageFromSurfaceFilter.h" #include "mitkReduceContourSetFilter.h" -#include "mitkDataNode.h" -#include "mitkDataStorage.h" +#include +#include -#include "vtkAppendPolyData.h" -#include "vtkCellArray.h" -#include "vtkPoints.h" -#include "vtkPolyData.h" -#include "vtkPolygon.h" -#include "vtkSmartPointer.h" +#include +#include +#include +#include +#include +#include -#include "mitkImageTimeSelector.h" -#include "mitkVtkRepresentationProperty.h" -#include "vtkImageData.h" -#include "vtkMarchingCubes.h" -#include "vtkProperty.h" +#include +#include +#include +#include +#include -#include "mitkProgressBar.h" +#include namespace mitk { class MITKSURFACEINTERPOLATION_EXPORT SurfaceInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); itkGetMacro(DistanceImageSpacing, double); struct ContourPositionInformation { - Surface::Pointer contour; - Vector3D contourNormal; - Point3D contourPoint; + int Pos; + unsigned int SliceIndex; + Surface::Pointer Contour; + Vector3D ContourNormal; + Point3D ContourPoint; + mitk::PlaneGeometry* plane; + mitk::Label::PixelType LabelValue; + unsigned int LayerValue; + size_t TimeStep; + + ContourPositionInformation(): + Pos(-1), + LabelValue(std::numeric_limits::max()), + LayerValue(std::numeric_limits::max()), + TimeStep(std::numeric_limits::max()) + {} + + friend std::ostream& operator << (std::ostream& os, ContourPositionInformation contour) + { + os << "contour lbl: " << contour.LabelValue << " lyr: " << contour.LayerValue; + os << " t: " << contour.TimeStep << " nPt: " << contour.Contour->GetVtkPolyData()->GetNumberOfPoints() << " "; + os << " Pt: " << contour.ContourPoint << " N: " << contour.ContourNormal << "\n"; + return os; + } }; typedef std::vector ContourPositionInformationList; typedef std::vector ContourPositionInformationVec2D; - typedef std::map ContourListMap; + + // first index is the current time step. second index is the layerID. third index is the contour index. + typedef std::vector ContourPositionInformationVec3D; + + typedef std::map ContourListMap; + typedef std::map ContourContainer; 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 a new extracted contour to the list * @param newContour the contour to be added. If a contour at that position * already exists the related contour will be updated */ void AddNewContour(Surface::Pointer newContour); - /** - * @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); - /** * @brief Adds new extracted contours to the list. If one or more contours at a given position * already exist they will be updated respectively * @param newContours the list of the contours */ - void AddNewContours(std::vector newContours); + void AddNewContours(const std::vector& newContours, std::vector& contourPlanes, bool reinitializeAction = false); /** * @brief Returns the contour for a given plane for the current selected segmenation * @param contourInfo the contour which should be returned * @return the contour as an mitk::Surface. If no contour is available at the give position nullptr is returned */ - const mitk::Surface *GetContour(ContourPositionInformation contourInfo); + const mitk::Surface *GetContour(const ContourPositionInformation& contourInfo); + + /** + * @brief Computes an interior point of the input contour. It's used to detect merge and erase operations. + * + * @param contour Contour for which to compute the contour + * @param labelSetImage LabelSetImage used input to check contour Label. + * @return mitk::Point3D 3D Interior point of the contour returned. + */ + mitk::Point3D ComputeInteriorPointOfContour(const ContourPositionInformation& contour, + mitk::LabelSetImage * labelSetImage); + + /** + * @brief Make the surface interpolator responsive to the segmentation image by subscribing to events from the image. + * + */ + void AddLabelSetConnection(); + + /** + * @brief Make the surface interpolator responsive to the segmentation image by stopping subscription to events from the image. + * + */ + void RemoveLabelSetConnection(); + + void RemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID); + + + /** + * @brief Resets the pipeline for interpolation. The various filters used are reset. + * + */ + void ReinitializeInterpolation(); + + void RemoveObservers(); + + void AddLabelSetConnection(unsigned int layerID); + + void UnsetSelectedImage() + { + m_SelectedSegmentation = nullptr; + } + + /** + * @brief Returns the number of layers in the current segmentation image. + * + */ + unsigned int GetNumberOfLayersInCurrentSegmentation() const; + + /** + * @brief Set the number of layers in the current segmentation image. + * + */ + void SetNumberOfLayersInCurrentSegmentation(unsigned int); + + /** + * @brief Function that does the data management when a layer is removed. + * + */ + void OnRemoveLayer(); + + /** + * @brief Function that does the data management when a layer is added. + * + */ + void OnAddLayer(); /** * @brief Returns the number of available contours for the current selected segmentation * @return the number of contours */ unsigned int GetNumberOfContours(); /** - * Interpolates the 3D surface from the given extracted contours + * @brief Performs the interpolation. + * */ void Interpolate(); + /** + * @brief Get the Result of the interpolation operation. + * + * @return mitk::Surface::Pointer + */ mitk::Surface::Pointer GetInterpolationResult(); /** - * 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 + * @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); /** - * Sets the minimum spacing of the current selected segmentation + * @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::Image::Pointer GetCurrentSegmentation(); Surface *GetContoursAsSurface(); void SetDataStorage(DataStorage::Pointer ds); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param segmentation The current selected segmentation * \deprecatedSince{2014_03} */ DEPRECATED(void SetCurrentSegmentationInterpolationList(mitk::Image::Pointer segmentation)); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param currentSegmentationImage The current selected segmentation */ void SetCurrentInterpolationSession(mitk::Image::Pointer currentSegmentationImage); /** * Removes the segmentation and all its contours from the list * @param segmentation The segmentation to be removed * \deprecatedSince{2014_03} */ DEPRECATED(void RemoveSegmentationFromContourList(mitk::Image *segmentation)); /** * @brief Remove interpolation session * @param segmentationImage the session to be removed */ void RemoveInterpolationSession(mitk::Image::Pointer segmentationImage); /** * Replaces the current interpolation session with a new one. All contours form the old * session will be applied to the new session. This only works if the two images have the * geometry * @param oldSession the session which should be replaced * @param newSession the new session which replaces the old one * @return true it the the replacement was successful, false if not (e.g. the image's geometry differs) */ bool ReplaceInterpolationSession(mitk::Image::Pointer oldSession, mitk::Image::Pointer newSession); /** * @brief Removes all sessions */ void RemoveAllInterpolationSessions(); + mitk::Image *GetImage(); + /** - * @brief Reinitializes the interpolation using the provided contour data - * @param contours a mitk::Surface which contains the contours as polys in the vtkPolyData + * @brief Get the Contours at a certain timeStep and layerID. + * + * @param timeStep Time Step from which to get the contours. + * @param layerID Layer from which to get the contours. + * @return std::vector Returns contours. */ - void ReinitializeInterpolation(mitk::Surface::Pointer contours); + ContourPositionInformationList& GetContours(unsigned int timeStep, unsigned int layerID); - mitk::Image *GetImage(); + /** + * @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& contourList, + std::vector& contourPlanes); + + /** + * @brief Removes contours of a particular label, at a given time step and layerID. + * + * @param label Label of contour to remove. + * @param timeStep Time step in which to remove the contours. + * @param layerID Layer in which the contour should be removed. + */ + void RemoveContours(mitk::Label::PixelType label, unsigned int timeStep, unsigned int layerID); /** * Estimates the memory which is needed to build up the equationsystem for the interpolation. * \returns The percentage of the real memory which will be used by the interpolation */ double EstimatePortionOfNeededMemory(); + /** + * Adds Contours from the active Label to the interpolation pipeline + */ + void AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel); + unsigned int GetNumberOfInterpolationSessions(); + /** + * @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); + + /** + * @brief Get the Segmentation Image Node object + * + * @return DataNode* returns the DataNode containing the segmentation image. + */ + mitk::DataNode* GetSegmentationImageNode(); + + + protected: SurfaceInterpolationController(); ~SurfaceInterpolationController() override; template void GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result); private: - void ReinitializeInterpolation(); + /** + * @brief + * + * @param caller + * @param event + */ void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); - void AddToInterpolationPipeline(ContourPositionInformation contourInfo); + /** + * @brief Function that removes contours of a particular label when the "Remove Label" event is trigerred in the labelSetImage. + * + */ + void OnRemoveLabel(); + + /** + * @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); + + /** + * @brief Function that toggles active label, when the active label is changed. + * + */ + void OnActiveLabel(mitk::Label::PixelType); + + /** + * @brief Clears the interpolation data structures. Called from CompleteReinitialization(). + * + */ + void ClearInterpolationSession(); + + /** + * @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 AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction = false); + + /** + * @brief Function to respond to layer changed + * + */ + void OnLayerChanged(); + + + /** + * @brief PrintListOfContoursPresentInStruct + * + */ + void PrintListOfContours(); + ReduceContourSetFilter::Pointer m_ReduceFilter; ComputeContourSetNormalsFilter::Pointer m_NormalsFilter; CreateDistanceImageFromSurfaceFilter::Pointer m_InterpolateSurfaceFilter; - Surface::Pointer m_Contours; + mitk::Surface::Pointer m_Contours; double m_DistanceImageSpacing; vtkSmartPointer m_PolyData; mitk::DataStorage::Pointer m_DataStorage; - ContourListMap m_ListOfInterpolationSessions; + ContourContainer m_ListOfInterpolationSessions; + ContourListMap m_ListOfContours; mitk::Surface::Pointer m_InterpolationResult; unsigned int m_CurrentNumberOfReducedContours; + unsigned int m_NumberOfConnectionsAdded; mitk::Image *m_SelectedSegmentation; std::map m_SegmentationObserverTags; mitk::TimePointType m_CurrentTimePoint; + + unsigned int m_ContourIndex; + unsigned int m_ContourPosIndex; + unsigned int m_NumberOfLayersInCurrentSegmentation; + + mitk::Label::PixelType m_PreviousActiveLabelValue; + mitk::Label::PixelType m_CurrentActiveLabelValue; + + unsigned int m_PreviousLayerIndex; + unsigned int m_CurrentLayerIndex; }; + + namespace ContourExt + { + /** + * @brief Returns the plane the contour belongs to. + * + * @param ContourNormal + * @return size_t + */ + size_t GetContourOrientation(const mitk::Vector3D& ContourNormal); + + /** + * @brief Function used to compute an interior point of the contour. + * Used to react to the merge label and erase label actions. + * + * + * @tparam VImageDimension Dimension of the image + * @param contour Contour for which to compute the interior point + * @param labelSetImage Label Set Image For which to find the contour + * @param currentTimePoint Current Time Point of the Image + * @return mitk::Point3D The returned point in the interior of the contour.s + */ + template + mitk::Point3D ComputeInteriorPointOfContour(const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, + mitk::LabelSetImage * labelSetImage, + mitk::TimePointType currentTimePoint); + /** + * @brief Get a Grid points within the bounding box of the contour at a certain spacing. + * + * @param planeDimension Plane orientation (Sagittal, Coronal, Axial) + * @param startDim1 Starting coordinate along dimension 1 to start the grid point sampling from + * @param numPointsToSampleDim1 Number of points to sample along dimension 1 + * @param deltaDim1 Spacing for dimension 1 at which points should be sampled + * @param startDim2 Starting coordinate along dimension 2 to start the grid point sampling from + * @param numPointsToSampleDim2 Number of points to sample along dimension 2 + * @param deltaDim2 Spacing for dimension 1 at which points should be sampled + * @param valuePlaneDim Slice index of the plane in the volume + * @return std::vector< mitk::Point3D > The computed grid points are returned by the function. + */ + std::vector< mitk::Point3D > GetBoundingBoxGridPoints(size_t planeDimension, + double startDim1, + size_t numPointsToSampleDim1, + double deltaDim1, + double startDim2, + size_t numPointsToSampleDim2, + double deltaDim2, + double valuePlaneDim); + }; + } + #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 6da2e33956..ded0670f04 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1128 +1,1125 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include // us #include #include // Qt #include #include #include // vtk #include #include const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) , m_SelectionChangeIsAlreadyBeingHandled(false) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_SegmentationInteractor = mitk::SegmentationInteractor::New(); // add observer for the 'SegmentationInteractionEvent' itk::ReceptorMemberCommand::Pointer geometryNotAlignedCommand = itk::ReceptorMemberCommand::New(); geometryNotAlignedCommand->SetCallbackFunction(this, &QmitkSegmentationView::ValidateRendererGeometry); m_SegmentationInteractor->AddObserver(mitk::SegmentationInteractionEvent(nullptr, true), geometryNotAlignedCommand); m_SegmentationInteractor->Disable(); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { this->LooseLabelSetConnection(); // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnAnySelectionChanged() { // When only a segmentation has been selected and the method is then called by a reference image selection, // the already selected segmentation may not match the geometry predicate of the new reference image anymore. // This will trigger a recursive call of this method further below. While it would be resolved gracefully, we // can spare the extra call with an early-out. The original call of this method will handle the segmentation // selection change afterwards anyway. if (m_SelectionChangeIsAlreadyBeingHandled) return; auto selectedReferenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); bool referenceNodeChanged = false; m_ToolManager->ActivateTool(-1); if (m_ReferenceNode != selectedReferenceNode) { referenceNodeChanged = true; // Remove visibility observer for the current reference node if (m_ReferenceDataObserverTags.find(m_ReferenceNode) != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } // Set new reference node m_ReferenceNode = selectedReferenceNode; m_ToolManager->SetReferenceData(m_ReferenceNode); // Prepare for a potential recursive call when changing node predicates of the working node selector m_SelectionChangeIsAlreadyBeingHandled = true; if (m_ReferenceNode.IsNull()) { // Without a reference image, allow all segmentations to be selected m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_SelectionChangeIsAlreadyBeingHandled = false; } else { // With a reference image, only allow segmentations that fit the geometry of the reference image to be selected. m_Controls->workingNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry()), m_SegmentationPredicate.GetPointer())); m_SelectionChangeIsAlreadyBeingHandled = false; this->ApplySelectionModeOnReferenceNode(); // Add visibility observer for the new reference node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } auto selectedWorkingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool workingNodeChanged = false; if (m_WorkingNode != selectedWorkingNode) { workingNodeChanged = true; // Remove visibility observer for the current working node if (m_WorkingDataObserverTags.find(m_WorkingNode) != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } // Disconnect from current label set image this->LooseLabelSetConnection(); // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Connect to new label set image this->EstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); // Add visibility observer for the new segmentation node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } // Reset camera if any selection changed but only if both reference node and working node are set if ((referenceNodeChanged || workingNodeChanged) && (m_ReferenceNode.IsNotNull() && m_WorkingNode.IsNotNull())) { if (nullptr != m_RenderWindowPart) { m_RenderWindowPart->SetReferenceGeometry(m_ReferenceNode->GetData()->GetTimeGeometry(), false); } } this->UpdateGUI(); } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::LabelSetIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) { QmitkNewSegmentationDialog dialog(m_Parent); dialog.SetName(QString::fromStdString(newLabel->GetName())); dialog.SetColor(newLabel->GetColor()); if (QDialog::Rejected == dialog.exec()) return; auto name = dialog.GetName(); if (!name.isEmpty()) newLabel->SetName(name.toStdString()); newLabel->SetColor(dialog.GetColor()); } newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnLayersChanged() { this->EstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); } void QmitkSegmentationView::OnShowLabelTable(bool value) { m_Controls->labelSetWidget->setVisible(value); } void QmitkSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelSetWidgetReset() { this->ValidateSelectionInput(); } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut"); #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()); m_Controls->toolSelectionBox2D->SetLayoutColumns(3); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->toolSelectionBox3D->SetLayoutColumns(3); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); connect(m_Controls->layersWidget, &QmitkLayersWidget::LayersChanged, this, &QmitkSegmentationView::OnLayersChanged); connect(m_Controls->labelsWidget, &QmitkLabelsWidget::ShowLabelTable, this, &QmitkSegmentationView::OnShowLabelTable); // *------------------------ // * LABELSETWIDGET // *------------------------ connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::goToLabel, this, &QmitkSegmentationView::OnGoToLabel); connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::LabelSetWidgetReset, this, &QmitkSegmentationView::OnLabelSetWidgetReset); m_Controls->labelSetWidget->SetDataStorage(this->GetDataStorage()); m_Controls->labelSetWidget->hide(); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr == activeTool) { // no tool activated, deactivate the segmentation interactor m_SegmentationInteractor->Disable(); return; } // activate segmentation interactor to get informed about render window entered / left events m_SegmentationInteractor->Enable(); } void QmitkSegmentationView::ValidateRendererGeometry(const itk::EventObject& event) { if (!mitk::SegmentationInteractionEvent().CheckEvent(&event)) { return; } const auto* segmentationInteractionEvent = dynamic_cast(&event); const mitk::BaseRenderer::Pointer sendingRenderer = segmentationInteractionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } bool entered = segmentationInteractionEvent->HasEnteredRenderWindow(); if (entered) { // mouse cursor of tool inside render window // check if tool can be used: reference geometry needs to be aligned with renderer geometry const auto* referenceDataNode = m_ToolManager->GetReferenceData(0); if (nullptr != referenceDataNode) { const auto workingImage = dynamic_cast(referenceDataNode->GetData()); if (nullptr != workingImage) { const mitk::TimeGeometry* workingImageGeometry = workingImage->GetTimeGeometry(); if (nullptr != workingImageGeometry) { bool isGeometryAligned = false; try { isGeometryAligned = mitk::BaseRendererHelper::IsRendererAlignedWithSegmentation(sendingRenderer, workingImageGeometry); } catch (const mitk::Exception& e) { MITK_ERROR << "Unable to validate renderer geometry\n" << "Reason: " << e.GetDescription(); this->ShowRenderWindowWarning(sendingRenderer, true); this->UpdateWarningLabel( tr("Unable to validate renderer geometry. Please see log!")); return; } if (!isGeometryAligned) { this->ShowRenderWindowWarning(sendingRenderer, true); this->UpdateWarningLabel( tr("Please perform a reinit on the segmentation image inside the entered Render Window!")); return; } } } } } this->ShowRenderWindowWarning(sendingRenderer, false); this->UpdateWarningLabel(tr("")); } void QmitkSegmentationView::ShowRenderWindowWarning(mitk::BaseRenderer* baseRenderer, bool show) { const auto* rendererName = baseRenderer->GetName(); auto* renderWindow = m_RenderWindowPart->GetQmitkRenderWindow(rendererName); renderWindow->ShowOverlayMessage(show); } 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) { // tell the interpolation about tool manager, data storage and render window part QList controllers; controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, controllers); if (!m_RenderWindowPart->HasCoupledRenderWindows()) { // react if the active tool changed, only if a render window part with decoupled render windows is used m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); } } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } // remove message-connection to make sure no message is processed if no render window part is available m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); m_Controls->slicesInterpolator->Uninitialize(); } void QmitkSegmentationView::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->labelsWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool slimView = prefs->GetBool("slim view", false); m_Controls->toolSelectionBox2D->SetShowNames(!slimView); m_Controls->toolSelectionBox3D->SetShowNames(!slimView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = QString::fromStdString(prefs->Get("label set preset", "")); this->ApplyDisplayOptions(); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) this->ApplyDisplayOptions(const_cast(node)); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } void QmitkSegmentationView::EstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) return; auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) return; workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls->labelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::LooseLabelSetConnection() { if (m_WorkingNode.IsNull()) return; auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) return; workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls->labelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr == labelSetImage) { return; } // the outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplySelectionMode() { if (!m_SelectionMode) return; this->ApplySelectionModeOnReferenceNode(); this->ApplySelectionModeOnWorkingNode(); } void QmitkSegmentationView::ApplySelectionModeOnReferenceNode() { this->ApplySelectionMode(m_ReferenceNode, m_ReferencePredicate); } void QmitkSegmentationView::ApplySelectionModeOnWorkingNode() { this->ApplySelectionMode(m_WorkingNode, m_SegmentationPredicate); } void QmitkSegmentationView::ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate) { if (!m_SelectionMode || node == nullptr || predicate == nullptr) return; auto nodes = this->GetDataStorage()->GetSubset(predicate); for (auto iter = nodes->begin(); iter != nodes->end(); ++iter) (*iter)->SetVisibility(*iter == node); } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } m_Controls->layersWidget->UpdateGUI(); m_Controls->labelsWidget->UpdateGUI(); 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->layersWidget->setEnabled(hasWorkingNode); m_Controls->labelsWidget->setEnabled(hasWorkingNode); m_Controls->labelSetWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (!referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (!workingNode->IsVisible(nullptr)) { warning += (!warning.isEmpty() ? "
" : "") + tr("The selected segmentation is currently not visible!"); toolSelectionBoxesEnabled = false; } - auto labelSetImage = dynamic_cast(workingNode->GetData()); - if (nullptr != labelSetImage) - { - auto activeLayer = labelSetImage->GetActiveLayer(); - numberOfLabels = labelSetImage->GetNumberOfLabels(activeLayer); - - if (2 == numberOfLabels) - { - m_Controls->slicesInterpolator->setEnabled(true); - } - else if (2 < numberOfLabels) - { - m_Controls->interpolatorWarningLabel->setText( - "Interpolation only works for single label segmentations."); - m_Controls->interpolatorWarningLabel->show(); - } - } + m_ToolManager->SetReferenceData(referenceNode); + m_ToolManager->SetWorkingData(workingNode); + m_Controls->layersWidget->setEnabled(true); + m_Controls->labelsWidget->setEnabled(true); + m_Controls->labelSetWidget->setEnabled(true); + m_Controls->toolSelectionBox2D->setEnabled(true); + m_Controls->toolSelectionBox3D->setEnabled(true); + + auto labelSetImage = dynamic_cast(workingNode->GetData()); + auto activeLayer = labelSetImage->GetActiveLayer(); + numberOfLabels = labelSetImage->GetNumberOfLabels(activeLayer); + + if (numberOfLabels > 1) + m_Controls->slicesInterpolator->setEnabled(true); } toolSelectionBoxesEnabled &= numberOfLabels > 1; // 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. if (toolSelectionBoxesEnabled && nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows()) { const mitk::BaseGeometry* workingNodeGeometry = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* renderWindowGeometry = m_RenderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeometry && nullptr != renderWindowGeometry) { if (!mitk::Equal(*workingNodeGeometry->GetBoundingBox(), *renderWindowGeometry->GetBoundingBox(), mitk::eps, true)) { warning += (!warning.isEmpty() ? "
" : "") + tr("Please reinitialize the selected segmentation image!"); toolSelectionBoxesEnabled = false; } } } m_Controls->toolSelectionBox2D->setEnabled(toolSelectionBoxesEnabled); m_Controls->toolSelectionBox3D->setEnabled(toolSelectionBoxesEnabled); this->UpdateWarningLabel(warning); m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.isEmpty()) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->setText("" + text + ""); m_Controls->selectionWarningLabel->show(); } -} +} \ No newline at end of file