diff --git a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp index 9a88810661..7608c67820 100644 --- a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp +++ b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp @@ -1,533 +1,544 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSegmentationInterpolationController.h" #include #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include #include "mitkImageReadAccessor.h" //#include #include "mitkShapeBasedInterpolationAlgorithm.h" #include #include #include 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) { } 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::Pointer command = itk::ReceptorMemberCommand::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; } //mitkIpPicDescriptor* rawSlice = const_cast(sliceDiff)->GetSliceData()->GetPicDescriptor(); // we promise not to change anything! mitk::ImageReadAccessor readAccess(sliceDiff); unsigned char* rawSlice = (unsigned char*) readAccess.GetData(); if (!rawSlice) return; AccessFixedDimensionByItk_1( sliceDiff, ScanChangedSlice, 2, SetChangedSliceOptions(sliceDimension, sliceIndex, dim0, dim1, timeStep, rawSlice) ); //PrintStatus(); Modified(); } template < typename DATATYPE > void mitk::SegmentationInterpolationController::ScanChangedSlice( const itk::Image*, const SetChangedSliceOptions& options ) { DATATYPE* 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( m_SegmentationCountInSlice[timeStep][dim0][u] + value ); m_SegmentationCountInSlice[timeStep][dim1][v] = static_cast( m_SegmentationCountInSlice[timeStep][dim1][v] + value ); numberOfPixels += static_cast( 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* diffImage, unsigned int timeStep ) { typedef itk::ImageSliceConstIteratorWithIndex< itk::Image > 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( m_SegmentationCountInSlice[timeStep][0][x] + value ); m_SegmentationCountInSlice[timeStep][1][y] = static_cast( m_SegmentationCountInSlice[timeStep][1][y] + value ); numberOfPixels += static_cast( 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*, 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 DATATYPE* rawVolume = static_cast( 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( 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 ) { if (m_Segmentation.IsNull()) 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 ( m_SegmentationCountInSlice[timeStep][sliceDimension][sliceIndex] > 0 ) return nullptr; // slice contains a segmentation, won't interpolate anything then unsigned int lowerBound(0); unsigned int upperBound(0); bool bounds( false ); for (lowerBound = sliceIndex - 1; /*lowerBound >= 0*/; --lowerBound) { if ( m_SegmentationCountInSlice[timeStep][sliceDimension][lowerBound] > 0 ) { bounds = true; break; } if (lowerBound == 0) break; // otherwise overflow and start at something like 4294967295 } if (!bounds) return nullptr; bounds = false; for (upperBound = sliceIndex + 1 ; upperBound < upperLimit; ++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; mitk::Image::Pointer lowerMITKSlice; mitk::Image::Pointer upperMITKSlice; 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(); //Creating PlaneGeometry for lower slice mitk::PlaneGeometry::Pointer reslicePlane = currentPlane->Clone(); //Transforming the current origin so that it matches the lower slice mitk::Point3D 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); extractor->SetWorldGeometry(reslicePlane); extractor->Modified(); extractor->Update(); lowerMITKSlice = extractor->GetOutput(); lowerMITKSlice->DisconnectPipeline(); //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() ) return nullptr; } catch(const std::exception &e) { MITK_ERROR<<"Error in 2D interpolation: "<Interpolate( lowerMITKSlice.GetPointer(), lowerBound, upperMITKSlice.GetPointer(), upperBound, sliceIndex, sliceDimension, resultImage, timeStep, m_ReferenceImage ); } diff --git a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h index c86a19e709..fc09189a34 100644 --- a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h +++ b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h @@ -1,209 +1,214 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkSegmentationInterpolationController_h_Included #define mitkSegmentationInterpolationController_h_Included #include "mitkCommon.h" #include #include "mitkImage.h" #include #include #include #include namespace mitk { class Image; /** \brief Generates interpolations of 2D slices. \sa QmitkSlicesInterpolator \sa QmitkInteractiveSegmentation \ingroup ToolManagerEtAl There is a separate page describing the general design of QmitkInteractiveSegmentation: \ref QmitkInteractiveSegmentationTechnicalPage 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. This is perhaps better to understand from the following picture (where red items just mean to symbolize "there is some segmentation" - in reality there is an integer count). \image html slice_based_segmentation_interpolator.png $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 NULL 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 timeStep Which time step to use */ Image::Pointer Interpolate( unsigned int sliceDimension, unsigned int sliceIndex, const mitk::PlaneGeometry* currentPlane, unsigned int timeStep ); void OnImageModified(const itk::EventObject&); /** * Activate/Deactivate the 2D interpolation. */ void Activate2DInterpolation(bool); + /** + \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 DirtyVectorType; //typedef std::vector< DirtyVectorType[3] > TimeResolvedDirtyVectorType; // cannot work with C++, so next line is used for implementation typedef std::vector< std::vector > TimeResolvedDirtyVectorType; typedef std::map< const Image*, SegmentationInterpolationController* > InterpolatorMapType; SegmentationInterpolationController();// purposely hidden virtual ~SegmentationInterpolationController(); /// internal scan of a single slice template < typename DATATYPE > void ScanChangedSlice( const itk::Image*, const SetChangedSliceOptions& options ); template < typename TPixel, unsigned int VImageDimension > void ScanChangedVolume( const itk::Image*, unsigned int timeStep ); template < typename DATATYPE > void ScanWholeVolume( const itk::Image*, const Image* volume, unsigned int timeStep ); void PrintStatus(); /** 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; }; } // namespace #endif diff --git a/Modules/Segmentation/Testing/files.cmake b/Modules/Segmentation/Testing/files.cmake index ab0a0412a6..998fe8337d 100644 --- a/Modules/Segmentation/Testing/files.cmake +++ b/Modules/Segmentation/Testing/files.cmake @@ -1,35 +1,35 @@ set(MODULE_TESTS mitkContourMapper2DTest.cpp mitkContourTest.cpp mitkContourModelSetToImageFilterTest.cpp mitkDataNodeSegmentationTest.cpp mitkFeatureBasedEdgeDetectionFilterTest.cpp mitkImageToContourFilterTest.cpp -# mitkSegmentationInterpolationTest.cpp + mitkSegmentationInterpolationTest.cpp mitkOverwriteSliceFilterTest.cpp mitkOverwriteSliceFilterObliquePlaneTest.cpp # mitkToolManagerTest.cpp mitkToolManagerProviderTest.cpp mitkManualSegmentationToSurfaceFilterTest.cpp #new cpp unit style ) if(MITK_ENABLE_RENDERING_TESTING) #since mitkInteractionTestHelper is currently creating a vtkRenderWindow set(MODULE_TESTS ${MODULE_TESTS} mitkToolInteractionTest.cpp ) endif() set(MODULE_IMAGE_TESTS mitkOverwriteSliceImageFilterTest.cpp #only runs on images ) set(MODULE_CUSTOM_TESTS ) set(MODULE_TESTIMAGE US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) diff --git a/Modules/Segmentation/Testing/mitkSegmentationInterpolationTest.cpp b/Modules/Segmentation/Testing/mitkSegmentationInterpolationTest.cpp index a2d0a53d33..85a5e99668 100644 --- a/Modules/Segmentation/Testing/mitkSegmentationInterpolationTest.cpp +++ b/Modules/Segmentation/Testing/mitkSegmentationInterpolationTest.cpp @@ -1,343 +1,199 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#include "mitkSegmentationInterpolationController.h" -#include "mitkCoreObjectFactory.h" -#include "mitkStandardFileLocations.h" -#include "mitkDataNodeFactory.h" -#include "ipSegmentation.h" -#include "mitkCompareImageSliceTestHelper.h" - -class mitkSegmentationInterpolationTestClass +// Testing +#include +#include + +// other +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class mitkSegmentationInterpolationTestSuite : public mitk::TestFixture { - public: - mitkSegmentationInterpolationTestClass() {} - ~mitkSegmentationInterpolationTestClass() {} + CPPUNIT_TEST_SUITE(mitkSegmentationInterpolationTestSuite); + MITK_TEST(Equal_Axial_TestInterpolationAndReferenceInterpolation_ReturnsTrue); + MITK_TEST(Equal_Frontal_TestInterpolationAndReferenceInterpolation_ReturnsTrue); + MITK_TEST(Equal_Sagittal_TestInterpolationAndReferenceInterpolation_ReturnsTrue); + CPPUNIT_TEST_SUITE_END(); - bool Test(std::string filename1, std::string filename2) +private: + + // The tests all do the same, only in different directions + void testRoutine(mitk::SliceNavigationController::ViewDirection viewDirection) { - return CreateNewInterpolator() - && CreateSegmentation() - && ClearSegmentation() - && CreateTwoSlices(2) - && TestInterpolation(2) - && ClearSegmentation() - && CreateTwoSlices(1) - && TestInterpolation(1) - && ClearSegmentation() - && CreateTwoSlices(0) - && TestInterpolation(0) - && DeleteInterpolator() - && CreateNewInterpolator() - && LoadTestImages(filename1, filename2) - && CompareInterpolationsToDefinedReference(); + int dim; + switch(viewDirection) + { + case(mitk::SliceNavigationController::Axial): dim = 2; break; + case(mitk::SliceNavigationController::Frontal): dim = 1; break; + case(mitk::SliceNavigationController::Sagittal): dim = 0; break; + case(mitk::SliceNavigationController::Original): dim = -1; break; // This is just to get rid of a warning + } + + /* Fill segmentation + * + * 1st slice: 3x3 square segmentation + * 2nd slice: empty + * 3rd slice: 1x1 square segmentation in corner + * -> 2nd slice should become 2x2 square in corner + * + * put accessor in scope + */ + + itk::Index<3> currentPoint; + { + mitk::ImagePixelWriteAccessor writeAccessor(m_SegmentationImage); + + // Fill 3x3 slice + currentPoint[dim] = m_CenterPoint[dim] - 1; + for (int i=-1; i<=1; ++i) + { + for (int j=-1; j<=1; ++j) + { + currentPoint[(dim+1)%3] = m_CenterPoint[(dim+1)%3] + i; + currentPoint[(dim+2)%3] = m_CenterPoint[(dim+2)%3] + j; + writeAccessor.SetPixelByIndexSafe(currentPoint, 1); + } + } + // Now i=j=1, set point two slices up + currentPoint[dim] = m_CenterPoint[dim] + 1; + writeAccessor.SetPixelByIndexSafe(currentPoint, 1); + } + + // mitk::IOUtil::Save(m_SegmentationImage, "SOME PATH"); + + m_InterpolationController->SetSegmentationVolume(m_SegmentationImage); + m_InterpolationController->SetReferenceVolume(m_ReferenceImage); + + // This could be easier... + mitk::SliceNavigationController::Pointer navigationController = mitk::SliceNavigationController::New(); + navigationController->SetInputWorldTimeGeometry(m_SegmentationImage->GetTimeGeometry()); + navigationController->Update(viewDirection); + mitk::Point3D pointMM; + m_SegmentationImage->GetTimeGeometry()->GetGeometryForTimeStep(0)->IndexToWorld(m_CenterPoint, pointMM); + navigationController->SelectSliceByPoint(pointMM); + auto plane = navigationController->GetCurrentPlaneGeometry(); + mitk::Image::Pointer interpolationResult = m_InterpolationController->Interpolate(dim, m_CenterPoint[dim], plane, 0); + + // mitk::IOUtil::Save(interpolationResult, "SOME PATH"); + + // Write result into segmentation image + vtkSmartPointer reslicer = vtkSmartPointer::New(); + reslicer->SetInputSlice(interpolationResult->GetSliceData()->GetVtkImageAccessor(interpolationResult)->GetVtkImageData()); + reslicer->SetOverwriteMode(true); + reslicer->Modified(); + mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslicer); + extractor->SetInput(m_SegmentationImage); + extractor->SetTimeStep(0); + extractor->SetWorldGeometry(plane); + extractor->SetVtkOutputRequest(true); + extractor->SetResliceTransformByGeometry(m_SegmentationImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); + extractor->Modified(); + extractor->Update(); + + // mitk::IOUtil::Save(m_SegmentationImage, "SOME PATH"); + + // Check a 4x4 square, the center of which needs to be filled + mitk::ImagePixelReadAccessor readAccess(m_SegmentationImage); + currentPoint = m_CenterPoint; + + for (int i=-1; i<=2; ++i) + { + for (int j=-1; j<=2; ++j) + { + currentPoint[(dim+1)%3] = m_CenterPoint[(dim+1)%3] + i; + currentPoint[(dim+2)%3] = m_CenterPoint[(dim+2)%3] + j; + + if (i == -1 || i == 2 || j == -1 || j == 2) + { + CPPUNIT_ASSERT_MESSAGE("Have false positive segmentation.", readAccess.GetPixelByIndexSafe(currentPoint) == 0); + } + else + { + CPPUNIT_ASSERT_MESSAGE("Have false negative segmentation.", readAccess.GetPixelByIndexSafe(currentPoint) == 1); + } + } + } } - protected: - - bool CreateNewInterpolator(); - bool CreateSegmentation(); - bool ClearSegmentation(); - bool CreateTwoSlices(int); - bool TestInterpolation(int); - bool DeleteInterpolator(); - bool LoadTestImages(std::string filename1, std::string filename2); - bool CompareInterpolationsToDefinedReference(); - - mitk::Image::Pointer LoadImage(const std::string& filename); - - mitk::SegmentationInterpolationController::Pointer m_Interpolator; - mitk::Image::Pointer m_Image; - - mitk::Image::Pointer m_ManualSlices; - mitk::Image::Pointer m_InterpolatedSlices; - - unsigned int dim[3]; - int pad[3]; -}; - -bool mitkSegmentationInterpolationTestClass::CreateNewInterpolator() -{ - std::cout << "Instantiation" << std::endl; - -// instantiation - m_Interpolator = mitk::SegmentationInterpolationController::New(); - if (m_Interpolator.IsNotNull()) - { - std::cout << " (II) Instantiation works." << std::endl; - } - else - { - std::cout << " Instantiation test failed!" << std::endl; - return false; - } - - return true; -} - -bool mitkSegmentationInterpolationTestClass::CreateSegmentation() -{ - m_Image = mitk::Image::New(); - dim[0]=15; - dim[1]=20; - dim[2]=25; - pad[0]=2; - pad[1]=3; - pad[2]=4; - - m_Image->Initialize( mitk::MakeScalarPixelType(), 3, dim); - - return true; -} - -bool mitkSegmentationInterpolationTestClass::ClearSegmentation() -{ - int* p = (int*)m_Image->GetData(); // pointer to pixel data - int size = dim[0]*dim[1]*dim[2]; - for(int i=0; i m_CenterPoint; + mitk::SegmentationInterpolationController::Pointer m_InterpolationController; +public: -/** - * Creates a square segmentation in slices 0 and 2. -*/ -bool mitkSegmentationInterpolationTestClass::CreateTwoSlices(int slicedim) -{ - int* p = (int*)m_Image->GetData(); // pointer to pixel data - - int size = dim[0]*dim[1]*dim[2]; - for(int i=0; i()); + m_SegmentationImage->Initialize(pixelType, m_ReferenceImage->GetDimension(), m_ReferenceImage->GetDimensions()); + m_SegmentationImage->SetClonedTimeGeometry(m_ReferenceImage->GetTimeGeometry()); + unsigned int size = sizeof(mitk::Tool::DefaultSegmentationDataType); + for (unsigned int dim = 0; dim < m_SegmentationImage->GetDimension(); ++dim) + { + size *= m_SegmentationImage->GetDimension(dim); + } + mitk::ImageWriteAccessor imageAccessor(m_SegmentationImage); + memset(imageAccessor.GetData(), 0, size); + + // Work in the center of the image (Pic3D) + m_CenterPoint = {{ 127, 127, 25 }}; - if ( ((z == 0) || (z == 2)) && (x >= pad[xdim]) && (x < ( (signed) dim[xdim]-pad[xdim])) && (y >= pad[ydim]) && (y < ( (signed) dim[ydim]-pad[ydim])) ) - { - *p = 1; } - else - { - *p = 0; - } - } - - m_Interpolator->SetSegmentationVolume( m_Image ); - std::cout << " (II) SetSegmentationVolume works (slicedim " << slicedim << ")" << std::endl; - return true; -} - -/** - * Checks if interpolation would create a square in slice 1 -*/ -bool mitkSegmentationInterpolationTestClass::TestInterpolation(int slicedim) -{ - int slice = 1; - mitk::Image::Pointer interpolated = m_Interpolator->Interpolate( slicedim, slice, 0 ); // interpolate second slice axial - if (interpolated.IsNull()) - { - std::cerr << " (EE) Interpolation did not return anything for slicedim == " << slicedim << " (although it should)." << std::endl; - return false; - } - - int xdim,ydim; - switch (slicedim) - { - case 0: - xdim = 1; - ydim = 2; // different than above! - break; - case 1: - xdim = 0; - ydim = 2; - break; - case 2: - default: - xdim = 0; - ydim = 1; - break; - } - - ipMITKSegmentationTYPE* p = (ipMITKSegmentationTYPE*)interpolated->GetData(); // pointer to pixel data - - int size = dim[xdim]*dim[ydim]; - if ( (signed) interpolated->GetDimension(0) * (signed) interpolated->GetDimension(1) != size ) - { - std::cout << " (EE) Size of interpolated image differs from original segmentation..." << std::endl; - return false; - } - - for(int i=0; i= pad[xdim]) && (x < ((signed) dim[xdim]-pad[xdim])) && (y >= pad[ydim]) && (y < ((signed) dim[ydim]-pad[ydim])) && (value != 1) ) + void tearDown() override { - std::cout << " (EE) Interpolation of a square figure failed" << std::endl; - std::cout << " Value at " << x << " " << y << ": " << (int)value << std::endl; - return false; + m_ReferenceImage = nullptr; + m_SegmentationImage = nullptr; + m_CenterPoint = {{ 0, 0, 0 }}; } - } - - std::cout << " (II) Interpolation of a square figure works like expected (slicedim " << slicedim << ")" << std::endl; - return true; -} -bool mitkSegmentationInterpolationTestClass::DeleteInterpolator() -{ - std::cout << "Object destruction" << std::endl; - -// freeing - m_Interpolator = NULL; - - std::cout << " (II) Freeing works." << std::endl; - return true; -} - -bool mitkSegmentationInterpolationTestClass::LoadTestImages(std::string filename1, std::string filename2) -{ - m_ManualSlices = LoadImage( filename1 ); - m_InterpolatedSlices = LoadImage( filename2 ); - - return ( m_ManualSlices.IsNotNull() && m_InterpolatedSlices.IsNotNull() ); -} - -mitk::Image::Pointer mitkSegmentationInterpolationTestClass::LoadImage(const std::string& filename) -{ - mitk::Image::Pointer image = NULL; - mitk::DataNodeFactory::Pointer factory = mitk::DataNodeFactory::New(); - try - { - factory->SetFileName( filename ); - factory->Update(); - - if(factory->GetNumberOfOutputs()<1) - { - std::cerr<<"File " << filename << " could not be loaded [FAILED]"<GetOutput( 0 ); - image = dynamic_cast(node->GetData()); - if(image.IsNull()) + void Equal_Axial_TestInterpolationAndReferenceInterpolation_ReturnsTrue() { - std::cout<<"File " << filename << " is not an image! [FAILED]"<SetSegmentationVolume( m_ManualSlices ); - - std::cout << "OK" << std::endl; - - std::cout << " (II) Testing interpolation result for slice " << std::flush; - - for (unsigned int slice = 1; slice < 98; ++slice) - { - if (slice % 2 == 0) continue; // these were manually drawn, no interpolation possible - - std::cout << slice << " " << std::flush; - mitk::Image::Pointer interpolation = m_Interpolator->Interpolate( 2, slice, 0 ); - - if ( interpolation.IsNull() ) + void Equal_Frontal_TestInterpolationAndReferenceInterpolation_ReturnsTrue() // Coronal { - std::cerr << " (EE) Interpolated image is NULL." << std::endl; - return false; + mitk::SliceNavigationController::ViewDirection viewDirection = mitk::SliceNavigationController::Frontal; + testRoutine(viewDirection); } - if ( !CompareImageSliceTestHelper::CompareSlice( m_InterpolatedSlices, 2, slice, interpolation ) ) + void Equal_Sagittal_TestInterpolationAndReferenceInterpolation_ReturnsTrue() { - std::cerr << " (EE) interpolated image is not identical to reference in slice " << slice << std::endl; - return false; + mitk::SliceNavigationController::ViewDirection viewDirection = mitk::SliceNavigationController::Sagittal; + testRoutine(viewDirection); } - } - - std::cout << std::endl; - std::cout << " (II) Interpolations are the same as the saved references." << std::endl; - - return true; -} - -/// ctest entry point -int mitkSegmentationInterpolationTest(int argc, char* argv[]) -{ -// one big variable to tell if anything went wrong -// std::cout << "Creating CoreObjectFactory" << std::endl; -// itk::ObjectFactoryBase::RegisterFactory(mitk::CoreObjectFactory::New()); - if (argc < 3) - { - std::cerr << " (EE) Missing arguments for testing" << std::endl; - } - mitkSegmentationInterpolationTestClass test; - if ( test.Test(argv[1], argv[2]) ) - { - std::cout << "[PASSED]" << std::endl; - return EXIT_SUCCESS; - } - else - { - std::cout << "[FAILED]" << std::endl; - return EXIT_FAILURE; - } -} +}; +MITK_TEST_SUITE_REGISTRATION(mitkSegmentationInterpolation)