diff --git a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp index 3bc01722ad..ea074a7f31 100644 --- a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp +++ b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp @@ -1,135 +1,157 @@ /*============================================================================ 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 <mitkITKImageImport.h> #include <itkFastChamferDistanceImageFilter.h> #include <itkInvertIntensityImageFilter.h> #include <itkIsoContourDistanceImageFilter.h> #include <itkSubtractImageFilter.h> +#include <thread> + 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*/) { - mitk::Image::Pointer lowerDistanceImage = mitk::Image::New(); - AccessFixedDimensionByItk_1(lowerSlice, ComputeDistanceMap, 2, lowerDistanceImage); - - mitk::Image::Pointer upperDistanceImage = mitk::Image::New(); - AccessFixedDimensionByItk_1(upperSlice, ComputeDistanceMap, 2, upperDistanceImage); + 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); + 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<std::mutex> 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<std::mutex> lock(m_DistanceImageCacheMutex); + + m_DistanceImageCache[sliceIndex] = distanceImage; + + return distanceImage; +} + template <typename TPixel, unsigned int VImageDimension> void mitk::ShapeBasedInterpolationAlgorithm::ComputeDistanceMap(const itk::Image<TPixel, VImageDimension> *binaryImage, mitk::Image::Pointer &result) { typedef itk::Image<TPixel, VImageDimension> DistanceFilterInputImageType; typedef itk::FastChamferDistanceImageFilter<DistanceFilterImageType, DistanceFilterImageType> DistanceFilterType; typedef itk::IsoContourDistanceImageFilter<DistanceFilterInputImageType, DistanceFilterImageType> IsoContourType; typedef itk::InvertIntensityImageFilter<DistanceFilterInputImageType> InvertIntensityImageFilterType; typedef itk::SubtractImageFilter<DistanceFilterImageType, DistanceFilterImageType> 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 <typename TPixel, unsigned int VImageDimension> void mitk::ShapeBasedInterpolationAlgorithm::InterpolateIntermediateSlice(itk::Image<TPixel, VImageDimension> *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<DistanceFilterImageType> 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<TPixel>(intermediatePixelVal)); ++lowerIter; } } diff --git a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.h b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.h index 934271b89b..1ba28195f6 100644 --- a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.h +++ b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.h @@ -1,65 +1,73 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkShapeBasedInterpolationAlgorithm_h_Included #define mitkShapeBasedInterpolationAlgorithm_h_Included #include "mitkSegmentationInterpolationAlgorithm.h" #include <MitkSegmentationExports.h> +#include <map> +#include <mutex> + namespace mitk { /** * \brief Shape-based binary image interpolation. * * This class uses legacy code from ipSegmentation to implement * the shape-based interpolation algorithm described in * * G.T. Herman, J. Zheng, C.A. Bucholtz: "Shape-based interpolation" * IEEE Computer Graphics & Applications, pp. 69-79,May 1992 * * Last contributor: * $Author:$ */ class MITKSEGMENTATION_EXPORT ShapeBasedInterpolationAlgorithm : public SegmentationInterpolationAlgorithm { public: mitkClassMacro(ShapeBasedInterpolationAlgorithm, SegmentationInterpolationAlgorithm); itkFactorylessNewMacro(Self); itkCloneMacro(Self); Image::Pointer Interpolate(Image::ConstPointer lowerSlice, unsigned int lowerSliceIndex, Image::ConstPointer upperSlice, unsigned int upperSliceIndex, unsigned int requestedIndex, unsigned int sliceDimension, Image::Pointer resultImage, unsigned int timeStep, Image::ConstPointer referenceImage) override; private: typedef itk::Image<mitk::ScalarType, 2> DistanceFilterImageType; template <typename TPixel, unsigned int VImageDimension> void ComputeDistanceMap(const itk::Image<TPixel, VImageDimension> *, mitk::Image::Pointer &result); + Image::Pointer ComputeDistanceMap(unsigned int sliceIndex, Image::ConstPointer slice); + template <typename TPixel, unsigned int VImageDimension> void InterpolateIntermediateSlice(itk::Image<TPixel, VImageDimension> *result, const mitk::Image::Pointer &lowerDistanceImage, const mitk::Image::Pointer &upperDistanceImage, float ratio); + + std::map<unsigned int, Image::Pointer> m_DistanceImageCache; + std::mutex m_DistanceImageCacheMutex; }; } // namespace #endif diff --git a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp index 7306f74e74..c016754e06 100644 --- a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp +++ b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp @@ -1,586 +1,602 @@ /*============================================================================ 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 "mitkSegmentationInterpolationController.h" #include "mitkImageCast.h" #include "mitkImageReadAccessor.h" #include "mitkImageTimeSelector.h" #include <mitkExtractSliceFilter.h> #include <mitkImageAccessByItk.h> //#include <mitkPlaneGeometry.h> -#include "mitkShapeBasedInterpolationAlgorithm.h" - #include <itkCommand.h> #include <itkImage.h> #include <itkImageSliceConstIteratorWithIndex.h> +#include <thread> + mitk::SegmentationInterpolationController::InterpolatorMapType mitk::SegmentationInterpolationController::s_InterpolatorForImage; // static member initialization mitk::SegmentationInterpolationController *mitk::SegmentationInterpolationController::InterpolatorForImage( const Image *image) { auto iter = s_InterpolatorForImage.find(image); if (iter != s_InterpolatorForImage.end()) { return iter->second; } else { return nullptr; } } mitk::SegmentationInterpolationController::SegmentationInterpolationController() - : m_BlockModified(false), m_2DInterpolationActivated(false) + : m_BlockModified(false), m_2DInterpolationActivated(false), m_EnableSliceImageCache(false) { } void mitk::SegmentationInterpolationController::Activate2DInterpolation(bool status) { m_2DInterpolationActivated = status; } mitk::SegmentationInterpolationController *mitk::SegmentationInterpolationController::GetInstance() { static mitk::SegmentationInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SegmentationInterpolationController::New(); } return m_Instance; } mitk::SegmentationInterpolationController::~SegmentationInterpolationController() { // remove this from the list of interpolators for (auto iter = s_InterpolatorForImage.begin(); iter != s_InterpolatorForImage.end(); ++iter) { if (iter->second == this) { s_InterpolatorForImage.erase(iter); break; } } } void mitk::SegmentationInterpolationController::OnImageModified(const itk::EventObject &) { if (!m_BlockModified && m_Segmentation.IsNotNull() && m_2DInterpolationActivated) { SetSegmentationVolume(m_Segmentation); } } void mitk::SegmentationInterpolationController::BlockModified(bool block) { m_BlockModified = block; } void mitk::SegmentationInterpolationController::SetSegmentationVolume(const Image *segmentation) { // clear old information (remove all time steps m_SegmentationCountInSlice.clear(); // delete this from the list of interpolators auto iter = s_InterpolatorForImage.find(segmentation); if (iter != s_InterpolatorForImage.end()) { s_InterpolatorForImage.erase(iter); } if (!segmentation) return; if (segmentation->GetDimension() > 4 || segmentation->GetDimension() < 3) { itkExceptionMacro("SegmentationInterpolationController needs a 3D-segmentation or 3D+t, not 2D."); } if (m_Segmentation != segmentation) { // observe Modified() event of image itk::ReceptorMemberCommand<SegmentationInterpolationController>::Pointer command = itk::ReceptorMemberCommand<SegmentationInterpolationController>::New(); command->SetCallbackFunction(this, &SegmentationInterpolationController::OnImageModified); segmentation->AddObserver(itk::ModifiedEvent(), command); } m_Segmentation = segmentation; m_SegmentationCountInSlice.resize(m_Segmentation->GetTimeSteps()); for (unsigned int timeStep = 0; timeStep < m_Segmentation->GetTimeSteps(); ++timeStep) { m_SegmentationCountInSlice[timeStep].resize(3); for (unsigned int dim = 0; dim < 3; ++dim) { m_SegmentationCountInSlice[timeStep][dim].clear(); m_SegmentationCountInSlice[timeStep][dim].resize(m_Segmentation->GetDimension(dim)); m_SegmentationCountInSlice[timeStep][dim].assign(m_Segmentation->GetDimension(dim), 0); } } s_InterpolatorForImage.insert(std::make_pair(m_Segmentation, this)); // for all timesteps // scan whole image for (unsigned int timeStep = 0; timeStep < m_Segmentation->GetTimeSteps(); ++timeStep) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->UpdateLargestPossibleRegion(); Image::Pointer segmentation3D = timeSelector->GetOutput(); AccessFixedDimensionByItk_2(segmentation3D, ScanWholeVolume, 3, m_Segmentation, timeStep); } // PrintStatus(); SetReferenceVolume(m_ReferenceImage); Modified(); } void mitk::SegmentationInterpolationController::SetReferenceVolume(const Image *referenceImage) { m_ReferenceImage = referenceImage; if (m_ReferenceImage.IsNull()) return; // no image set - ignore it then assert(m_Segmentation.IsNotNull()); // should never happen // ensure the reference image has the same dimensionality and extents as the segmentation image if (m_ReferenceImage.IsNull() || m_Segmentation.IsNull() || m_ReferenceImage->GetDimension() != m_Segmentation->GetDimension() || m_ReferenceImage->GetPixelType().GetNumberOfComponents() != 1 || m_Segmentation->GetPixelType().GetNumberOfComponents() != 1) { MITK_WARN << "Segmentation image has different image characteristics than reference image." << std::endl; m_ReferenceImage = nullptr; return; } for (unsigned int dim = 0; dim < m_Segmentation->GetDimension(); ++dim) if (m_ReferenceImage->GetDimension(dim) != m_Segmentation->GetDimension(dim)) { MITK_WARN << "original patient image does not match segmentation (different extent in dimension " << dim << "), ignoring patient image" << std::endl; m_ReferenceImage = nullptr; return; } } void mitk::SegmentationInterpolationController::SetChangedVolume(const Image *sliceDiff, unsigned int timeStep) { if (!sliceDiff) return; if (sliceDiff->GetDimension() != 3) return; AccessFixedDimensionByItk_1(sliceDiff, ScanChangedVolume, 3, timeStep); // PrintStatus(); Modified(); } void mitk::SegmentationInterpolationController::SetChangedSlice(const Image *sliceDiff, unsigned int sliceDimension, unsigned int sliceIndex, unsigned int timeStep) { if (!sliceDiff) return; if (sliceDimension > 2) return; if (timeStep >= m_SegmentationCountInSlice.size()) return; if (sliceIndex >= m_SegmentationCountInSlice[timeStep][sliceDimension].size()) return; unsigned int dim0(0); unsigned int dim1(1); // determine the other two dimensions switch (sliceDimension) { default: case 2: dim0 = 0; dim1 = 1; break; case 1: dim0 = 0; dim1 = 2; break; case 0: dim0 = 1; dim1 = 2; break; } mitk::ImageReadAccessor readAccess(sliceDiff); auto *rawSlice = (unsigned char *)readAccess.GetData(); if (!rawSlice) return; AccessFixedDimensionByItk_1( sliceDiff, ScanChangedSlice, 2, SetChangedSliceOptions(sliceDimension, sliceIndex, dim0, dim1, timeStep, rawSlice)); Modified(); } template <typename DATATYPE> void mitk::SegmentationInterpolationController::ScanChangedSlice(const itk::Image<DATATYPE, 2> *, const SetChangedSliceOptions &options) { auto *pixelData((DATATYPE *)options.pixelData); unsigned int timeStep(options.timeStep); unsigned int sliceDimension(options.sliceDimension); unsigned int sliceIndex(options.sliceIndex); if (sliceDimension > 2) return; if (sliceIndex >= m_SegmentationCountInSlice[timeStep][sliceDimension].size()) return; unsigned int dim0(options.dim0); unsigned int dim1(options.dim1); int numberOfPixels(0); // number of pixels in this slice that are not 0 unsigned int dim0max = m_SegmentationCountInSlice[timeStep][dim0].size(); unsigned int dim1max = m_SegmentationCountInSlice[timeStep][dim1].size(); // scan the slice from two directions // and set the flags for the two dimensions of the slice for (unsigned int v = 0; v < dim1max; ++v) { for (unsigned int u = 0; u < dim0max; ++u) { DATATYPE value = *(pixelData + u + v * dim0max); assert((signed)m_SegmentationCountInSlice[timeStep][dim0][u] + (signed)value >= 0); // just for debugging. This must always be true, otherwise some counting is going wrong assert((signed)m_SegmentationCountInSlice[timeStep][dim1][v] + (signed)value >= 0); m_SegmentationCountInSlice[timeStep][dim0][u] = static_cast<unsigned int>(m_SegmentationCountInSlice[timeStep][dim0][u] + value); m_SegmentationCountInSlice[timeStep][dim1][v] = static_cast<unsigned int>(m_SegmentationCountInSlice[timeStep][dim1][v] + value); numberOfPixels += static_cast<int>(value); } } // flag for the dimension of the slice itself assert((signed)m_SegmentationCountInSlice[timeStep][sliceDimension][sliceIndex] + numberOfPixels >= 0); m_SegmentationCountInSlice[timeStep][sliceDimension][sliceIndex] += numberOfPixels; // MITK_INFO << "scan t=" << timeStep << " from (0,0) to (" << dim0max << "," << dim1max << ") (" << pixelData << "-" // << pixelData+dim0max*dim1max-1 << ") in slice " << sliceIndex << " found " << numberOfPixels << " pixels" << // std::endl; } template <typename TPixel, unsigned int VImageDimension> void mitk::SegmentationInterpolationController::ScanChangedVolume(const itk::Image<TPixel, VImageDimension> *diffImage, unsigned int timeStep) { typedef itk::ImageSliceConstIteratorWithIndex<itk::Image<TPixel, VImageDimension>> IteratorType; IteratorType iter(diffImage, diffImage->GetLargestPossibleRegion()); iter.SetFirstDirection(0); iter.SetSecondDirection(1); int numberOfPixels(0); // number of pixels in this slice that are not 0 typename IteratorType::IndexType index; unsigned int x = 0; unsigned int y = 0; unsigned int z = 0; iter.GoToBegin(); while (!iter.IsAtEnd()) { while (!iter.IsAtEndOfSlice()) { while (!iter.IsAtEndOfLine()) { index = iter.GetIndex(); x = index[0]; y = index[1]; z = index[2]; TPixel value = iter.Get(); assert((signed)m_SegmentationCountInSlice[timeStep][0][x] + (signed)value >= 0); // just for debugging. This must always be true, otherwise some counting is going wrong assert((signed)m_SegmentationCountInSlice[timeStep][1][y] + (signed)value >= 0); m_SegmentationCountInSlice[timeStep][0][x] = static_cast<unsigned int>(m_SegmentationCountInSlice[timeStep][0][x] + value); m_SegmentationCountInSlice[timeStep][1][y] = static_cast<unsigned int>(m_SegmentationCountInSlice[timeStep][1][y] + value); numberOfPixels += static_cast<int>(value); ++iter; } iter.NextLine(); } assert((signed)m_SegmentationCountInSlice[timeStep][2][z] + numberOfPixels >= 0); m_SegmentationCountInSlice[timeStep][2][z] += numberOfPixels; numberOfPixels = 0; iter.NextSlice(); } } template <typename DATATYPE> void mitk::SegmentationInterpolationController::ScanWholeVolume(const itk::Image<DATATYPE, 3> *, const Image *volume, unsigned int timeStep) { if (!volume) return; if (timeStep >= m_SegmentationCountInSlice.size()) return; ImageReadAccessor readAccess(volume, volume->GetVolumeData(timeStep)); for (unsigned int slice = 0; slice < volume->GetDimension(2); ++slice) { const auto *rawVolume = static_cast<const DATATYPE *>(readAccess.GetData()); // we again promise not to change anything, we'll just count const DATATYPE *rawSlice = rawVolume + (volume->GetDimension(0) * volume->GetDimension(1) * slice); ScanChangedSlice<DATATYPE>(nullptr, SetChangedSliceOptions(2, slice, 0, 1, timeStep, rawSlice)); } } void mitk::SegmentationInterpolationController::PrintStatus() { unsigned int timeStep(0); // if needed, put a loop over time steps around everyting, but beware, output will be long MITK_INFO << "Interpolator status (timestep 0): dimensions " << m_SegmentationCountInSlice[timeStep][0].size() << " " << m_SegmentationCountInSlice[timeStep][1].size() << " " << m_SegmentationCountInSlice[timeStep][2].size() << std::endl; MITK_INFO << "Slice 0: " << m_SegmentationCountInSlice[timeStep][2][0] << std::endl; // row "x" for (unsigned int index = 0; index < m_SegmentationCountInSlice[timeStep][0].size(); ++index) { if (m_SegmentationCountInSlice[timeStep][0][index] > 0) MITK_INFO << "O"; else MITK_INFO << "."; } MITK_INFO << std::endl; // rows "y" and "z" (diagonal) for (unsigned int index = 1; index < m_SegmentationCountInSlice[timeStep][1].size(); ++index) { if (m_SegmentationCountInSlice[timeStep][1][index] > 0) MITK_INFO << "O"; else MITK_INFO << "."; if (m_SegmentationCountInSlice[timeStep][2].size() > index) // if we also have a z value here, then print it, too { for (unsigned int indent = 1; indent < index; ++indent) MITK_INFO << " "; if (m_SegmentationCountInSlice[timeStep][2][index] > 0) MITK_INFO << m_SegmentationCountInSlice[timeStep][2][index]; //"O"; else MITK_INFO << "."; } MITK_INFO << std::endl; } // z indices that are larger than the biggest y index for (unsigned int index = m_SegmentationCountInSlice[timeStep][1].size(); index < m_SegmentationCountInSlice[timeStep][2].size(); ++index) { for (unsigned int indent = 0; indent < index; ++indent) MITK_INFO << " "; if (m_SegmentationCountInSlice[timeStep][2][index] > 0) MITK_INFO << m_SegmentationCountInSlice[timeStep][2][index]; //"O"; else MITK_INFO << "."; MITK_INFO << std::endl; } } mitk::Image::Pointer mitk::SegmentationInterpolationController::Interpolate(unsigned int sliceDimension, unsigned int sliceIndex, const mitk::PlaneGeometry *currentPlane, - unsigned int timeStep) + unsigned int timeStep, + ShapeBasedInterpolationAlgorithm::Pointer algorithm) { - if (m_Segmentation.IsNull()) + if (m_Segmentation.IsNull() || nullptr == currentPlane) return nullptr; - if (!currentPlane) - { - return nullptr; - } - if (timeStep >= m_SegmentationCountInSlice.size()) return nullptr; + if (sliceDimension > 2) return nullptr; - unsigned int upperLimit = m_SegmentationCountInSlice[timeStep][sliceDimension].size(); - if (sliceIndex >= upperLimit - 1) - return nullptr; // can't interpolate first and last slice - if (sliceIndex < 1) - return nullptr; + + if (0 == sliceIndex) + return nullptr; // First slice, nothing to interpolate + + const unsigned int lastSliceIndex = m_SegmentationCountInSlice[timeStep][sliceDimension].size() - 1; + + if (lastSliceIndex <= sliceIndex) + return nullptr; // Last slice, nothing to interpolate if (m_SegmentationCountInSlice[timeStep][sliceDimension][sliceIndex] > 0) - return nullptr; // slice contains a segmentation, won't interpolate anything then + return nullptr; // Slice contains segmentation, nothing to interopolate - unsigned int lowerBound(0); - unsigned int upperBound(0); - bool bounds(false); + unsigned int lowerBound = 0; + unsigned int upperBound = 0; + bool bounds = false; - for (lowerBound = sliceIndex - 1; /*lowerBound >= 0*/; --lowerBound) + for (lowerBound = sliceIndex - 1; ; --lowerBound) { if (m_SegmentationCountInSlice[timeStep][sliceDimension][lowerBound] > 0) { bounds = true; break; } - if (lowerBound == 0) - break; // otherwise overflow and start at something like 4294967295 + if (0 == lowerBound) + break; } if (!bounds) return nullptr; bounds = false; - for (upperBound = sliceIndex + 1; upperBound < upperLimit; ++upperBound) + + for (upperBound = sliceIndex + 1; upperBound <= lastSliceIndex; ++upperBound) { if (m_SegmentationCountInSlice[timeStep][sliceDimension][upperBound] > 0) { bounds = true; break; } } if (!bounds) return nullptr; - // ok, we have found two neighboring slices with segmentations (and we made sure that the current slice does NOT - // contain anything - // MITK_INFO << "Interpolate in timestep " << timeStep << ", dimension " << sliceDimension << ": estimate slice " << - // sliceIndex << " from slices " << lowerBound << " and " << upperBound << std::endl; + // We have found two neighboring slices with segmentations and made sure that the current slice does not contain anything - mitk::Image::Pointer lowerMITKSlice; - mitk::Image::Pointer upperMITKSlice; + mitk::Image::Pointer lowerSlice; + mitk::Image::Pointer upperSlice; mitk::Image::Pointer resultImage; try { - // Setting up the ExtractSliceFilter - mitk::ExtractSliceFilter::Pointer extractor = ExtractSliceFilter::New(); - extractor->SetInput(m_Segmentation); - extractor->SetTimeStep(timeStep); - extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); - extractor->SetVtkOutputRequest(false); - - // Reslicing the current plane - extractor->SetWorldGeometry(currentPlane); - extractor->Modified(); - extractor->Update(); - resultImage = extractor->GetOutput(); - resultImage->DisconnectPipeline(); + // Extract current slice + resultImage = this->ExtractSlice(currentPlane, sliceIndex, timeStep); // Creating PlaneGeometry for lower slice - mitk::PlaneGeometry::Pointer reslicePlane = currentPlane->Clone(); + auto reslicePlane = currentPlane->Clone(); // Transforming the current origin so that it matches the lower slice - mitk::Point3D origin = currentPlane->GetOrigin(); + auto origin = currentPlane->GetOrigin(); m_Segmentation->GetSlicedGeometry(timeStep)->WorldToIndex(origin, origin); origin[sliceDimension] = lowerBound; m_Segmentation->GetSlicedGeometry(timeStep)->IndexToWorld(origin, origin); reslicePlane->SetOrigin(origin); - // Extract the lower slice - extractor = ExtractSliceFilter::New(); - extractor->SetInput(m_Segmentation); - extractor->SetTimeStep(timeStep); - extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); - extractor->SetVtkOutputRequest(false); + // Extract lower slice + lowerSlice = this->ExtractSlice(reslicePlane, lowerBound, timeStep, true); - extractor->SetWorldGeometry(reslicePlane); - extractor->Modified(); - extractor->Update(); - lowerMITKSlice = extractor->GetOutput(); - lowerMITKSlice->DisconnectPipeline(); + if (lowerSlice.IsNull()) + return nullptr; // Transforming the current origin so that it matches the upper slice m_Segmentation->GetSlicedGeometry(timeStep)->WorldToIndex(origin, origin); origin[sliceDimension] = upperBound; m_Segmentation->GetSlicedGeometry(timeStep)->IndexToWorld(origin, origin); reslicePlane->SetOrigin(origin); // Extract the upper slice - extractor = ExtractSliceFilter::New(); - extractor->SetInput(m_Segmentation); - extractor->SetTimeStep(timeStep); - extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); - extractor->SetVtkOutputRequest(false); - - extractor->SetWorldGeometry(reslicePlane); - extractor->Modified(); - extractor->Update(); - upperMITKSlice = extractor->GetOutput(); - upperMITKSlice->DisconnectPipeline(); - - if (lowerMITKSlice.IsNull() || upperMITKSlice.IsNull()) + upperSlice = this->ExtractSlice(reslicePlane, upperBound, timeStep, true); + + if (upperSlice.IsNull()) return nullptr; } catch (const std::exception &e) { MITK_ERROR << "Error in 2D interpolation: " << e.what(); return nullptr; } - // interpolation algorithm gets some inputs - // two segmentations (guaranteed to be of the same data type, but no special data type guaranteed) - // orientation (sliceDimension) of the segmentations - // position of the two slices (sliceIndices) - // one volume image (original patient image) + // Interpolation algorithm inputs: + // - Two segmentations (guaranteed to be of the same data type) + // - Orientation of the segmentations (sliceDimension) + // - Position of the two slices (sliceIndices) + // - Reference image // - // interpolation algorithm can use e.g. itk::ImageSliceConstIteratorWithIndex to - // inspect the original patient image at appropriate positions - - mitk::SegmentationInterpolationAlgorithm::Pointer algorithm = - mitk::ShapeBasedInterpolationAlgorithm::New().GetPointer(); - return algorithm->Interpolate(lowerMITKSlice.GetPointer(), - lowerBound, - upperMITKSlice.GetPointer(), - upperBound, - sliceIndex, - sliceDimension, - resultImage, - timeStep, - m_ReferenceImage); + // The interpolation algorithm can use e.g. itk::ImageSliceConstIteratorWithIndex to + // inspect the reference image at appropriate positions. + + if (algorithm.IsNull()) + algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); + + return algorithm->Interpolate( + lowerSlice.GetPointer(), + lowerBound, + upperSlice.GetPointer(), + upperBound, + sliceIndex, + sliceDimension, + resultImage, + timeStep, + m_ReferenceImage); +} + +mitk::Image::Pointer mitk::SegmentationInterpolationController::ExtractSlice(const PlaneGeometry* planeGeometry, unsigned int sliceIndex, unsigned int timeStep, bool cache) +{ + static const auto MAX_CACHE_SIZE = 2 * std::thread::hardware_concurrency(); + const auto key = std::make_pair(sliceIndex, timeStep); + + if (cache && m_EnableSliceImageCache) + { + std::lock_guard<std::mutex> lock(m_SliceImageCacheMutex); + + if (0 != m_SliceImageCache.count(key)) + return m_SliceImageCache[key]; + + if (MAX_CACHE_SIZE < m_SliceImageCache.size()) + m_SliceImageCache.clear(); + } + + auto extractor = ExtractSliceFilter::New(); + extractor->SetInput(m_Segmentation); + extractor->SetTimeStep(timeStep); + extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); + extractor->SetVtkOutputRequest(false); + extractor->SetWorldGeometry(planeGeometry); + extractor->Update(); + + if (cache && m_EnableSliceImageCache) + { + std::lock_guard<std::mutex> lock(m_SliceImageCacheMutex); + m_SliceImageCache[key] = extractor->GetOutput(); + } + + return extractor->GetOutput(); +} + +void mitk::SegmentationInterpolationController::EnableSliceImageCache() +{ + m_EnableSliceImageCache = true; +} + +void mitk::SegmentationInterpolationController::DisableSliceImageCache() +{ + m_EnableSliceImageCache = false; + m_SliceImageCache.clear(); } diff --git a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h index 30a9f8489c..5ac3839fa2 100644 --- a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h +++ b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h @@ -1,217 +1,242 @@ /*============================================================================ 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 mitkSegmentationInterpolationController_h_Included #define mitkSegmentationInterpolationController_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include <MitkSegmentationExports.h> +#include <mitkShapeBasedInterpolationAlgorithm.h> #include <itkImage.h> #include <itkObjectFactory.h> #include <map> +#include <mutex> +#include <utility> #include <vector> namespace mitk { class Image; /** \brief Generates interpolations of 2D slices. \sa QmitkSlicesInterpolator \sa QmitkInteractiveSegmentation \ingroup ToolManagerEtAl This class keeps track of the contents of a 3D segmentation image. \attention mitk::SegmentationInterpolationController assumes that the image contains pixel values of 0 and 1. After you set the segmentation image using SetSegmentationVolume(), the whole image is scanned for pixels other than 0. SegmentationInterpolationController registers as an observer to the segmentation image, and repeats the scan whenvever the image is modified. You can prevent this (time consuming) scan if you do the changes slice-wise and send difference images to SegmentationInterpolationController. For this purpose SetChangedSlice() should be used. mitk::OverwriteImageFilter already does this every time it changes a slice of an image. There is a static method InterpolatorForImage(), which can be used to find out if there already is an interpolator instance for a specified image. OverwriteImageFilter uses this to get to know its interpolator. SegmentationInterpolationController needs to maintain some information about the image slices (in every dimension). This information is stored internally in m_SegmentationCountInSlice, which is basically three std::vectors (one for each dimension). Each item describes one image dimension, each vector item holds the count of pixels in "its" slice. $Author$ */ class MITKSEGMENTATION_EXPORT SegmentationInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SegmentationInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Find interpolator for a given image. \return nullptr if there is no interpolator yet. This method is useful if several "clients" modify the same image and want to access the interpolations. Then they can share the same object. */ static SegmentationInterpolationController *InterpolatorForImage(const Image *); /** \brief Block reaction to an images Modified() events. Blocking the scan of the whole image is especially useful when you are about to change a single slice of the image. Then you would send a difference image of this single slice to SegmentationInterpolationController but call image->Modified() anyway. Before calling image->Modified() you should block SegmentationInterpolationController's reactions to this modified by using this method. */ void BlockModified(bool); /** \brief Initialize with a whole volume. Will scan the volume for segmentation pixels (values other than 0) and fill some internal data structures. You don't have to call this method every time something changes, but only when several slices at once change. When you change a single slice, call SetChangedSlice() instead. */ void SetSegmentationVolume(const Image *segmentation); /** \brief Set a reference image (original patient image) - optional. If this volume is set (must exactly match the dimensions of the segmentation), the interpolation algorithm may consider image content to improve the interpolated (estimated) segmentation. */ void SetReferenceVolume(const Image *segmentation); /** \brief Update after changing a single slice. \param sliceDiff is a 2D image with the difference image of the slice determined by sliceDimension and sliceIndex. The difference is (pixel value in the new slice minus pixel value in the old slice). \param sliceDimension Number of the dimension which is constant for all pixels of the meant slice. \param sliceIndex Which slice to take, in the direction specified by sliceDimension. Count starts from 0. \param timeStep Which time step is changed */ void SetChangedSlice(const Image *sliceDiff, unsigned int sliceDimension, unsigned int sliceIndex, unsigned int timeStep); void SetChangedVolume(const Image *sliceDiff, unsigned int timeStep); /** \brief Generates an interpolated image for the given slice. \param sliceDimension Number of the dimension which is constant for all pixels of the meant slice. \param sliceIndex Which slice to take, in the direction specified by sliceDimension. Count starts from 0. \param currentPlane \param timeStep Which time step to use + + \param algorithm Optional algorithm instance to potentially benefit from caching for repeated interpolation */ Image::Pointer Interpolate(unsigned int sliceDimension, unsigned int sliceIndex, const mitk::PlaneGeometry *currentPlane, - unsigned int timeStep); + unsigned int timeStep, + mitk::ShapeBasedInterpolationAlgorithm::Pointer algorithm = nullptr); void OnImageModified(const itk::EventObject &); /** * Activate/Deactivate the 2D interpolation. */ void Activate2DInterpolation(bool); + /** + * Enable slice extraction cache for upper and lower slices. + */ + void EnableSliceImageCache(); + + /** + * Disable slice extraction cache for upper and lower slices. + */ + void DisableSliceImageCache(); + /** \brief Get existing instance or create a new one */ static SegmentationInterpolationController *GetInstance(); protected: /** \brief Protected class of mitk::SegmentationInterpolationController. Don't use (you shouldn't be able to do so)! */ class MITKSEGMENTATION_EXPORT SetChangedSliceOptions { public: SetChangedSliceOptions( unsigned int sd, unsigned int si, unsigned int d0, unsigned int d1, unsigned int t, const void *pixels) : sliceDimension(sd), sliceIndex(si), dim0(d0), dim1(d1), timeStep(t), pixelData(pixels) { } unsigned int sliceDimension; unsigned int sliceIndex; unsigned int dim0; unsigned int dim1; unsigned int timeStep; const void *pixelData; }; typedef std::vector<unsigned int> DirtyVectorType; // typedef std::vector< DirtyVectorType[3] > TimeResolvedDirtyVectorType; // cannot work with C++, so next line is // used for implementation typedef std::vector<std::vector<DirtyVectorType>> TimeResolvedDirtyVectorType; typedef std::map<const Image *, SegmentationInterpolationController *> InterpolatorMapType; SegmentationInterpolationController(); // purposely hidden ~SegmentationInterpolationController() override; /// internal scan of a single slice template <typename DATATYPE> void ScanChangedSlice(const itk::Image<DATATYPE, 2> *, const SetChangedSliceOptions &options); template <typename TPixel, unsigned int VImageDimension> void ScanChangedVolume(const itk::Image<TPixel, VImageDimension> *, unsigned int timeStep); template <typename DATATYPE> void ScanWholeVolume(const itk::Image<DATATYPE, 3> *, const Image *volume, unsigned int timeStep); void PrintStatus(); + /** + * Extract a slice and optionally use a caching mechanism if enabled. + */ + mitk::Image::Pointer ExtractSlice(const PlaneGeometry* planeGeometry, unsigned int sliceIndex, unsigned int timeStep, bool cache = false); + /** An array of flags. One for each dimension of the image. A flag is set, when a slice in a certain dimension has at least one pixel that is not 0 (which would mean that it has to be considered by the interpolation algorithm). E.g. flags for axial slices are stored in m_SegmentationCountInSlice[0][index]. Enhanced with time steps it is now m_SegmentationCountInSlice[timeStep][0][index] */ TimeResolvedDirtyVectorType m_SegmentationCountInSlice; static InterpolatorMapType s_InterpolatorForImage; Image::ConstPointer m_Segmentation; Image::ConstPointer m_ReferenceImage; bool m_BlockModified; bool m_2DInterpolationActivated; + + bool m_EnableSliceImageCache; + std::map<std::pair<unsigned int, unsigned int>, Image::Pointer> m_SliceImageCache; + std::mutex m_SliceImageCacheMutex; }; } // namespace #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 8353719169..55cb106313 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1405 +1,1432 @@ /*============================================================================ 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 "QmitkSelectableGLWidget.h" #include "QmitkStdMultiWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkOverwriteSliceImageFilter.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 <mitkExtractSliceFilter.h> #include <mitkImageReadAccessor.h> #include <mitkImageTimeSelector.h> #include <mitkImageWriteAccessor.h> #include <mitkPlaneProposer.h> #include <mitkUnstructuredGridClusteringFilter.h> #include <mitkVtkImageOverwrite.h> +#include <mitkShapeBasedInterpolationAlgorithm.h> #include <itkCommand.h> #include <QCheckBox> #include <QCursor> #include <QMenu> #include <QMessageBox> #include <QPushButton> #include <QVBoxLayout> #include <vtkPolyVertex.h> #include <vtkUnstructuredGrid.h> #include <array> +#include <atomic> +#include <thread> +#include <vector> namespace { template <typename T = mitk::BaseData> itk::SmartPointer<T> GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast<T*>(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const std::map<QAction *, mitk::SliceNavigationController *> QmitkSlicesInterpolator::createActionToSliceDimension() { std::map<QAction *, mitk::SliceNavigationController *> actionToSliceDimension; foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) { actionToSliceDimension[new QAction(QString::fromStdString(slicer->GetViewDirectionAsString()), nullptr)] = slicer; } return actionToSliceDimension; } 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_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<QmitkSlicesInterpolator>::Pointer command = itk::ReceptorMemberCommand<QmitkSlicesInterpolator>::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand<QmitkSlicesInterpolator>::Pointer command2 = itk::ReceptorMemberCommand<QmitkSlicesInterpolator>::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); // 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, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1<QmitkSlicesInterpolator, const mitk::DataNode*>(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1<QmitkSlicesInterpolator, const mitk::DataNode*>(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList<mitk::SliceNavigationController *> &controllers) { Q_ASSERT(!controllers.empty()); if (m_Initialized) { // remove old observers Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate<QmitkSlicesInterpolator>(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate<QmitkSlicesInterpolator>( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); // 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<QmitkSlicesInterpolator>::Pointer deleteCommand = itk::MemberCommand<QmitkSlicesInterpolator>::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag.insert(slicer, slicer->AddObserver(itk::DeleteEvent(), deleteCommand)); itk::MemberCommand<QmitkSlicesInterpolator>::Pointer timeChangedCommand = itk::MemberCommand<QmitkSlicesInterpolator>::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand)); itk::MemberCommand<QmitkSlicesInterpolator>::Pointer sliceChangedCommand = itk::MemberCommand<QmitkSlicesInterpolator>::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag.insert( 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<QmitkSlicesInterpolator>(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate<QmitkSlicesInterpolator>( 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(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1<QmitkSlicesInterpolator, const mitk::DataNode*>(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(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); 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<mitk::Image *>(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_3DContourNode); m_3DContourNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); return; } // Updating the current selected segmentation for the 3D interpolation 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<const mitk::SliceNavigationController::GeometryTimeEvent *>(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast<mitk::SliceNavigationController *>(sender); Q_ASSERT(slicer); const auto timePoint = slicer->GetSelectedTimePoint(); m_TimePoints[slicer] = timePoint; m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); 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<const mitk::SliceNavigationController::GeometrySliceEvent *>(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast<mitk::SliceNavigationController *>(sender); 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<const mitk::SliceNavigationController::GeometrySliceEvent &>(e); mitk::TimeGeometry *tsg = event.GetTimeGeometry(); if (tsg && m_TimePoints.contains(slicer) && tsg->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = dynamic_cast<mitk::SlicedGeometry3D *>(tsg->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { m_LastSNC = slicer; mitk::PlaneGeometry *plane = dynamic_cast<mitk::PlaneGeometry *>(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::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<mitk::Image *>(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); // 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); 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( 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(); } } 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<mitkVtkImageOverwrite> reslice = vtkSmartPointer<mitkVtkImageOverwrite>::New(); // Set slice as input mitk::Image::Pointer slice = dynamic_cast<mitk::Image *>(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 image3D = m_Segmentation; - unsigned int timeStep(0); + mitk::Image::Pointer segmentation3D = m_Segmentation; + unsigned int timeStep = 0; const auto timePoint = slicer->GetSelectedTimePoint(); - if (m_Segmentation->GetDimension() == 4) + + if (4 == m_Segmentation->GetDimension()) { - if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) + 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; } - timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); + timeStep = geometry->TimePointToTimeStep(timePoint); + + auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); - image3D = timeSelector->GetOutput(); + + segmentation3D = timeSelector->GetOutput(); } - // create a empty diff image for the undo operation - mitk::Image::Pointer diffImage = mitk::Image::New(); - diffImage->Initialize(image3D); - // Create scope for ImageWriteAccessor so that the accessor is destroyed - // after the image is initialized. Otherwise later image access will lead to an error + // 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 imAccess(diffImage); + mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero - mitk::PixelType pixelType(mitk::MakeScalarPixelType<mitk::Tool::DefaultSegmentationDataType>()); + auto pixelType = mitk::MakeScalarPixelType<mitk::Tool::DefaultSegmentationDataType>(); // For legacy purpose support former pixel type of segmentations (before multilabel) - if (m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType() == - itk::ImageIOBase::UCHAR) - { + if (itk::ImageIOBase::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType<unsigned char>(); - } - memset(imAccess.GetData(), - 0, - (pixelType.GetBpe() >> 3) * diffImage->GetDimension(0) * diffImage->GetDimension(1) * - diffImage->GetDimension(2)); + 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 - mitk::PlaneGeometry::Pointer reslicePlane = slicer->GetCurrentPlaneGeometry()->Clone(); + 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; - int sliceDimension(-1); - int sliceIndex(-1); - mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, reslicePlane, sliceDimension, sliceIndex); + // Reuse interpolation algorithm instance for each slice to cache boundary calculations + auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); - unsigned int zslices = m_Segmentation->GetDimension(sliceDimension); - mitk::ProgressBar::GetInstance()->AddStepsToDo(zslices); + // Distribute slice interpolations to multiple threads + const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); + std::vector<std::vector<unsigned int>> sliceIndices(numThreads); - mitk::Point3D origin = reslicePlane->GetOrigin(); - unsigned int totalChangedSlices(0); + for (std::remove_const_t<decltype(numSlices)> sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) + sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); - for (unsigned int sliceIndex = 0; sliceIndex < zslices; ++sliceIndex) + std::vector<std::thread> threads; + threads.reserve(numThreads); + + // This lambda will be executed by the threads + auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { - // Transforming the current origin of the reslice plane - // so that it matches the one of the next slice - m_Segmentation->GetSlicedGeometry()->WorldToIndex(origin, origin); - origin[sliceDimension] = sliceIndex; - m_Segmentation->GetSlicedGeometry()->IndexToWorld(origin, origin); - reslicePlane->SetOrigin(origin); - // Set the slice as 'input' - mitk::Image::Pointer interpolation = - m_Interpolator->Interpolate(sliceDimension, sliceIndex, reslicePlane, timeStep); - - if (interpolation.IsNotNull()) // we don't check if interpolation is necessary/sensible - but m_Interpolator does + auto clonedPlaneGeometry = planeGeometry->Clone(); + auto origin = clonedPlaneGeometry->GetOrigin(); + + for (auto sliceIndex : sliceIndices[threadIndex]) { - // Setting up the reslicing pipeline which allows us to write the interpolation results back into - // the image volume - vtkSmartPointer<mitkVtkImageOverwrite> reslice = vtkSmartPointer<mitkVtkImageOverwrite>::New(); - - // set overwrite mode to true to write back to the image volume - reslice->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); - reslice->SetOverwriteMode(true); - reslice->Modified(); - - mitk::ExtractSliceFilter::Pointer diffslicewriter = mitk::ExtractSliceFilter::New(reslice); - diffslicewriter->SetInput(diffImage); - diffslicewriter->SetTimeStep(0); - diffslicewriter->SetWorldGeometry(reslicePlane); - diffslicewriter->SetVtkOutputRequest(true); - diffslicewriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); - - diffslicewriter->Modified(); - diffslicewriter->Update(); - ++totalChangedSlices; + slicedGeometry->WorldToIndex(origin, origin); + origin[sliceDimension] = sliceIndex; + slicedGeometry->IndexToWorld(origin, origin); + clonedPlaneGeometry->SetOrigin(origin); + + auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); + + if (interpolation.IsNotNull()) + { + // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume + auto reslicer = vtkSmartPointer<mitkVtkImageOverwrite>::New(); + + // Set overwrite mode to true to write back to the image volume + reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); + reslicer->SetOverwriteMode(true); + reslicer->Modified(); + + auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); + + diffSliceWriter->SetInput(diffImage); + diffSliceWriter->SetTimeStep(0); + diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); + diffSliceWriter->SetVtkOutputRequest(true); + diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); + diffSliceWriter->Modified(); + diffSliceWriter->Update(); + + ++totalChangedSlices; + } + + mitk::ProgressBar::GetInstance()->Progress(); } - mitk::ProgressBar::GetInstance()->Progress(); - } - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + }; + + m_Interpolator->EnableSliceImageCache(); + + for (std::remove_const_t<decltype(numThreads)> threadIndex = 0; threadIndex < numThreads; ++threadIndex) + threads.emplace_back(interpolate, threadIndex); // Run the interpolation + + for (auto& thread : threads) + thread.join(); + + m_Interpolator->DisableSliceImageCache(); if (totalChangedSlices > 0) { - // store undo stack items - if (true) - { - // create do/undo operations - mitk::ApplyDiffImageOperation *doOp = - new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); - mitk::ApplyDiffImageOperation *undoOp = - new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); - undoOp->SetFactor(-1.0); - std::stringstream comment; - comment << "Confirm all interpolations (" << totalChangedSlices << ")"; - mitk::OperationEvent *undoStackItem = - new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment.str()); - mitk::OperationEvent::IncCurrGroupEventId(); - mitk::OperationEvent::IncCurrObjectEventId(); - mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); - - // acutally apply the changes here to the original image - mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); - } + // 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); + + // Apply the changes to the original image + mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } + + 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<QAction *, mitk::SliceNavigationController *>::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<mitk::Image>(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto segmentation = GetData<mitk::Image>(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<mitk::Surface>(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::ImageIOBase::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); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + "_3D-interpolation"; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast<mitk::ProportionalTimeGeometry*>(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); std::array<float, 3> rgb; segmentationDataNode->GetColor(rgb.data()); interpolatedSurfaceDataNode->SetColor(rgb.data()); 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<mitk::Image *>(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<mitk::Surface *>(m_InterpolatedSurfaceNode->GetData()); vtkSmartPointer<vtkPolyData> vtkpoly = surface->GetVtkPolyData(); vtkSmartPointer<vtkPoints> vtkpoints = vtkpoly->GetPoints(); vtkSmartPointer<vtkUnstructuredGrid> vGrid = vtkSmartPointer<vtkUnstructuredGrid>::New(); vtkSmartPointer<vtkPolyVertex> verts = vtkSmartPointer<vtkPolyVertex>::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 mitk::BaseRenderer::Pointer br = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); mitk::PlaneProposer planeProposer; std::vector<mitk::UnstructuredGrid::Pointer> 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() { mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("3DContourContainer", 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<mitk::Surface *>(m_3DContourNode->GetData()); if (contours) mitk::SurfaceInterpolationController::GetInstance()->ReinitializeInterpolation(contours); m_BtnReinit3DInterpolation->setEnabled(false); } 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<QAction *, mitk::SliceNavigationController *>::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<mitk::Image *>(workingNode->GetData()); if (segmentation) { m_Interpolator->SetSegmentationVolume(segmentation); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast<mitk::Image *>(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } 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)); 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()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; this->CheckSupportedImageDimension(); try { if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { if ((workingNode->IsVisible(mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))))) { int ret = QMessageBox::Yes; 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(); } if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (ret == QMessageBox::Yes) { 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) { this->Show3DInterpolationResult(false); m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); // T28261 // m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); } } 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); } 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(); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { if (m_3DInterpolationEnabled) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { QWidget::setEnabled(true); 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; } time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::Vector3D spacing = workingNode->GetData()->GetGeometry(time_position)->GetSpacing(); double minSpacing(100); double maxSpacing(0); for (int i = 0; i < 3; i++) { if (spacing[i] < minSpacing) { minSpacing = spacing[i]; } if (spacing[i] > maxSpacing) { maxSpacing = spacing[i]; } } m_SurfaceInterpolator->SetMaxSpacing(maxSpacing); m_SurfaceInterpolator->SetMinSpacing(minSpacing); m_SurfaceInterpolator->SetDistanceImageVolume(50000); mitk::Image *segmentationImage = dynamic_cast<mitk::Image *>(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()) m_3DContourNode->SetVisibility( status, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) m_Segmentation = dynamic_cast<mitk::Image *>(m_ToolManager->GetWorkingData(0)->GetData()); /*if (m_3DInterpolationEnabled && m_Segmentation && m_Segmentation->GetDimension() != 3) { 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); }*/ } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast<mitk::SliceNavigationController *>(const_cast<itk::Object *>(sender)); if (slicer) { m_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(); } }