diff --git a/Modules/AlgorithmsExt/files.cmake b/Modules/AlgorithmsExt/files.cmake index 55da8aeb07..3e8bacd12d 100644 --- a/Modules/AlgorithmsExt/files.cmake +++ b/Modules/AlgorithmsExt/files.cmake @@ -1,36 +1,37 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkAutoCropImageFilter.cpp mitkBoundingObjectCutter.cpp mitkBoundingObjectToSegmentationFilter.cpp mitkGeometryClipImageFilter.cpp mitkGeometryDataSource.cpp mitkHeightFieldSurfaceClipImageFilter.cpp mitkImageToUnstructuredGridFilter.cpp mitkLabeledImageToSurfaceFilter.cpp mitkMaskAndCutRoiImageFilter.cpp mitkMaskImageFilter.cpp mitkMovieGenerator.cpp mitkNonBlockingAlgorithm.cpp mitkPadImageFilter.cpp mitkPlaneFit.cpp mitkPlaneLandmarkProjector.cpp mitkPointLocator.cpp mitkSegmentationSink.cpp mitkSimpleHistogram.cpp mitkSimpleUnstructuredGridHistogram.cpp mitkCovarianceMatrixCalculator.cpp mitkAnisotropicIterativeClosestPointRegistration.cpp mitkWeightedPointTransform.cpp mitkAnisotropicRegistrationCommon.cpp mitkUnstructuredGridClusteringFilter.cpp mitkUnstructuredGridToUnstructuredGridFilter.cpp mitkSurfaceToPointSetFilter.cpp + mitkCropTimestepsImageFilter.cpp ) if(WIN32) list(APPEND CPP_FILES mitkMovieGeneratorWin32.cpp ) endif() diff --git a/Modules/AlgorithmsExt/include/mitkCropTimestepsImageFilter.h b/Modules/AlgorithmsExt/include/mitkCropTimestepsImageFilter.h new file mode 100644 index 0000000000..f2cbe5496b --- /dev/null +++ b/Modules/AlgorithmsExt/include/mitkCropTimestepsImageFilter.h @@ -0,0 +1,84 @@ +/*=================================================================== + +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 mitkCropTimestepsImageFilter_h +#define mitkCropTimestepsImageFilter_h + +#include "MitkAlgorithmsExtExports.h" + +#include + +namespace mitk +{ + /** \brief Crops timesteps at 2D+t and 3D+t images + * \details The filter is able to crop timesteps in front and/or at the end. + * Internally, a new image is created with the remaining volumes. The geometries and properties of + * the input image are transferred to the output image. + */ + class MITKALGORITHMSEXT_EXPORT CropTimestepsImageFilter : public SubImageSelector + { + public: + mitkClassMacro(CropTimestepsImageFilter, SubImageSelector) + itkFactorylessNewMacro(Self) + + /*! + * \brief Sets the input image + * \pre the image has 4 Dimensions + * \pre the image has >1 timestep + * \pre the image is valid (valid geometry and volume) + */ + void SetInput(const InputImageType* image) override; + void SetInput(unsigned int index, const InputImageType* image) override; + + /*! + * \brief The last timestep to retain + * \details Set to the maximum timestep of the input as default + * \note if the last timestep is larger than the maximum timestep of the input, + * it is corrected to the maximum timestep. + * \exception if (LowerBoundaryTimestep > UpperBoundaryTimestep) + */ + itkSetMacro(UpperBoundaryTimestep, unsigned int); + itkGetConstMacro(UpperBoundaryTimestep, unsigned int); + + /*! + * \brief The first timestep to retain + * \details Set to 0 as default + * \exception if (LowerBoundaryTimestep > UpperBoundaryTimestep) + */ + itkSetMacro(LowerBoundaryTimestep, unsigned int); + itkGetConstMacro(LowerBoundaryTimestep, unsigned int); + + private: + using Superclass::SetInput; + + CropTimestepsImageFilter() = default; + ~CropTimestepsImageFilter() override = default; + + void GenerateData() override; + void VerifyInputInformation() override; + void VerifyInputImage(const mitk::Image* inputImage) const; + void GenerateOutputInformation() override; + mitk::SlicedData::RegionType ComputeDesiredRegion() const; + mitk::TimeGeometry::Pointer AdaptTimeGeometry(mitk::TimeGeometry::ConstPointer sourceGeometry, unsigned int startTimestep, unsigned int endTimestep) const; + + unsigned int m_UpperBoundaryTimestep = std::numeric_limits::max(); + unsigned int m_LowerBoundaryTimestep = 0; + + mitk::SlicedData::RegionType m_DesiredRegion; + }; +} + +#endif diff --git a/Modules/AlgorithmsExt/src/mitkCropTimestepsImageFilter.cpp b/Modules/AlgorithmsExt/src/mitkCropTimestepsImageFilter.cpp new file mode 100644 index 0000000000..c68dffe8b7 --- /dev/null +++ b/Modules/AlgorithmsExt/src/mitkCropTimestepsImageFilter.cpp @@ -0,0 +1,151 @@ +/*=================================================================== + +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 "mitkCropTimestepsImageFilter.h" + +#include +#include +#include +#include + + + void mitk::CropTimestepsImageFilter::VerifyInputImage(const mitk::Image* inputImage) const + { + if (!inputImage->IsInitialized()) + mitkThrow() << "Input image is not initialized."; + + if (!inputImage->IsVolumeSet()) + mitkThrow() << "Input image volume is not set."; + + auto geometry = inputImage->GetGeometry(); + + if (nullptr == geometry || !geometry->IsValid()) + mitkThrow() << "Input image has invalid geometry."; + + if (inputImage->GetDimension() != 4) { + mitkThrow() << "CropTimestepsImageFilter only works with 2D+t and 3D+t images."; + } + + if (inputImage->GetTimeSteps() ==1) { + mitkThrow() << "Input image has only one timestep."; + } + + if (!geometry->GetImageGeometry()) + mitkThrow() << "Geometry of input image is not an image geometry."; + } + + void mitk::CropTimestepsImageFilter::GenerateOutputInformation() + { + Image::ConstPointer input = this->GetInput(); + Image::Pointer output = this->GetOutput(); + if (m_LowerBoundaryTimestep > m_UpperBoundaryTimestep) { + mitkThrow() << "lower timestep is larger than upper timestep."; + } + if (m_UpperBoundaryTimestep == std::numeric_limits::max()) { + m_UpperBoundaryTimestep = input->GetTimeSteps(); + } + else if (m_UpperBoundaryTimestep > input->GetTimeSteps()) { + m_UpperBoundaryTimestep = input->GetTimeSteps(); + MITK_WARN << "upper boundary timestep set to " << m_UpperBoundaryTimestep; + } + m_DesiredRegion = ComputeDesiredRegion(); + unsigned int dimension = input->GetDimension(); + auto dimensions = new unsigned int[dimension]; + itk2vtk(m_DesiredRegion.GetSize(), dimensions); + if (dimension > 3) + memcpy(dimensions + 3, input->GetDimensions() + 3, (dimension - 3) * sizeof(unsigned int)); + + dimensions[3] = m_UpperBoundaryTimestep - m_LowerBoundaryTimestep; + + // create basic slicedGeometry that will be initialized below + output->Initialize(mitk::PixelType(input->GetPixelType()), dimension, dimensions); + delete[] dimensions; + auto newTimeGeometry = AdaptTimeGeometry(input->GetTimeGeometry(), m_LowerBoundaryTimestep, m_UpperBoundaryTimestep); + output->SetTimeGeometry(newTimeGeometry); + output->SetPropertyList(input->GetPropertyList()); + } + + mitk::SlicedData::RegionType mitk::CropTimestepsImageFilter::ComputeDesiredRegion() const + { + auto desiredRegion = this->GetInput()->GetLargestPossibleRegion(); + auto index = desiredRegion.GetIndex(); + auto size = desiredRegion.GetSize(); + unsigned int timeDimension = 3; + index[timeDimension] = m_LowerBoundaryTimestep; + size[timeDimension] = m_UpperBoundaryTimestep - m_LowerBoundaryTimestep; + desiredRegion.SetIndex(index); + desiredRegion.SetSize(size); + return desiredRegion; + } + + mitk::TimeGeometry::Pointer mitk::CropTimestepsImageFilter::AdaptTimeGeometry(mitk::TimeGeometry::ConstPointer sourceGeometry, unsigned int startTimestep, unsigned int endTimestep) const + { + auto newTimeGeometry = mitk::ArbitraryTimeGeometry::New(); + newTimeGeometry->ClearAllGeometries(); + for (unsigned int timestep = startTimestep; timestep < endTimestep; timestep++) { + auto geometryForTimePoint = sourceGeometry->GetGeometryForTimeStep(timestep); + newTimeGeometry->AppendNewTimeStep(geometryForTimePoint, + sourceGeometry->GetMinimumTimePoint(timestep), + sourceGeometry->GetMaximumTimePoint(timestep)); + } + return newTimeGeometry.GetPointer(); + } + +void mitk::CropTimestepsImageFilter::GenerateData() +{ + const auto* inputImage = this->GetInput(); + mitk::Image::Pointer output = this->GetOutput(); + + if ((output->IsInitialized() == false)) + return; + + auto timeSelector = mitk::ImageTimeSelector::New(); + + timeSelector->SetInput(inputImage); + + unsigned int timeStart = m_DesiredRegion.GetIndex(3); + unsigned int timeEnd = timeStart + m_DesiredRegion.GetSize(3); + for (unsigned int timestep = timeStart; timestep < timeEnd; ++timestep) + { + timeSelector->SetTimeNr(timestep); + timeSelector->UpdateLargestPossibleRegion(); + mitk::ImageReadAccessor imageAccessorWithOneTimestep(timeSelector->GetOutput()); + output->SetVolume(imageAccessorWithOneTimestep.GetData(), timestep-timeStart); + } +} + +void mitk::CropTimestepsImageFilter::SetInput(const InputImageType* image) +{ + if (this->GetInput() == image) + return; + + Superclass::SetInput(image); +} + +void mitk::CropTimestepsImageFilter::SetInput(unsigned int index, const InputImageType* image) +{ + if (0 != index) + mitkThrow() << "Input index " << index << " is invalid."; + + this->SetInput(image); +} + +void mitk::CropTimestepsImageFilter::VerifyInputInformation() +{ + Superclass::VerifyInputInformation(); + + VerifyInputImage(this->GetInput()); +} diff --git a/Modules/AlgorithmsExt/test/files.cmake b/Modules/AlgorithmsExt/test/files.cmake index 06daaeba52..3701cf8540 100644 --- a/Modules/AlgorithmsExt/test/files.cmake +++ b/Modules/AlgorithmsExt/test/files.cmake @@ -1,15 +1,16 @@ set(MODULE_TESTS mitkAutoCropImageFilterTest.cpp mitkBoundingObjectCutterTest.cpp mitkImageToUnstructuredGridFilterTest.cpp mitkPlaneFitTest.cpp mitkSimpleHistogramTest.cpp mitkCovarianceMatrixCalculatorTest.cpp mitkAnisotropicIterativeClosestPointRegistrationTest.cpp mitkUnstructuredGridClusteringFilterTest.cpp mitkUnstructuredGridToUnstructuredGridFilterTest.cpp + mitkCropTimestepsImageFilterTest.cpp ) set(MODULE_CUSTOM_TESTS mitkLabeledImageToSurfaceFilterTest.cpp ) diff --git a/Modules/AlgorithmsExt/test/mitkCropTimestepsImageFilterTest.cpp b/Modules/AlgorithmsExt/test/mitkCropTimestepsImageFilterTest.cpp new file mode 100644 index 0000000000..e9d6c2e18c --- /dev/null +++ b/Modules/AlgorithmsExt/test/mitkCropTimestepsImageFilterTest.cpp @@ -0,0 +1,181 @@ +/*=================================================================== +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. +===================================================================*/ +// Testing +#include "mitkTestingMacros.h" +#include "mitkTestFixture.h" + +//MITK includes +#include +#include + +class mitkCropTimestepsImageFilterTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkCropTimestepsImageFilterTestSuite); + MITK_TEST(Constructor_Null); + MITK_TEST(Constructor_NoTime); + MITK_TEST(Setter_UpperBoundaryTimestepSmallerThanLowerBoundaryTimestep); + MITK_TEST(Setter_UpperTimestepsGreaterThanMaxImageTimestep); + MITK_TEST(Filter_Default); + MITK_TEST(Filter_OnlyLowerBoundary); + MITK_TEST(Filter_OnlyUpperBoundary); + MITK_TEST(Filter_BothBoundaries); + MITK_TEST(Filter_BothBoundaries2Dt); + CPPUNIT_TEST_SUITE_END(); +private: + std::string m_ImageFilename2D, m_ImageFilename3D, m_ImageFilename2Dt, m_ImageFilename3Dt; + mitk::CropTimestepsImageFilter::Pointer m_cropTimestepsFilter; +public: + void setUp() override + { + m_ImageFilename2D = GetTestDataFilePath("Png2D-bw.png"); + m_ImageFilename3D = GetTestDataFilePath("Pic3D.nrrd"); + m_ImageFilename2Dt = GetTestDataFilePath("Pic2DplusT.nrrd"); + m_ImageFilename3Dt = GetTestDataFilePath("3D+t-ITKIO-TestData/LinearModel_4D_arbitrary_time_geometry.nrrd"); + m_cropTimestepsFilter = mitk::CropTimestepsImageFilter::New(); + } + + void tearDown() override + { + } + + void Constructor_Null() + { + m_cropTimestepsFilter->SetInput(nullptr); + CPPUNIT_ASSERT_THROW(m_cropTimestepsFilter->Update(), itk::ExceptionObject); + } + + void Constructor_NoTime() + { + auto refImage2D = mitk::IOUtil::Load(m_ImageFilename2D); + auto refImage3D = mitk::IOUtil::Load(m_ImageFilename3D); + m_cropTimestepsFilter->SetInput(refImage2D); + CPPUNIT_ASSERT_THROW(m_cropTimestepsFilter->Update(), mitk::Exception); + m_cropTimestepsFilter->SetInput(refImage3D); + CPPUNIT_ASSERT_THROW(m_cropTimestepsFilter->Update(), mitk::Exception); + } + + void Setter_UpperBoundaryTimestepSmallerThanLowerBoundaryTimestep() + { + //check for Exception if UpperBoundaryTimestep < LowerBoundaryTimestep + auto refImage2Dt = mitk::IOUtil::Load(m_ImageFilename2Dt); + m_cropTimestepsFilter->SetInput(refImage2Dt); + unsigned int lowerTimeStep = 5; + unsigned int upperTimeStep = 4; + m_cropTimestepsFilter->SetLowerBoundaryTimestep(lowerTimeStep); + m_cropTimestepsFilter->SetUpperBoundaryTimestep(upperTimeStep); + CPPUNIT_ASSERT_EQUAL(m_cropTimestepsFilter->GetLowerBoundaryTimestep(), lowerTimeStep); + CPPUNIT_ASSERT_EQUAL(m_cropTimestepsFilter->GetUpperBoundaryTimestep(), upperTimeStep); + CPPUNIT_ASSERT_THROW(m_cropTimestepsFilter->Update(), mitk::Exception); + } + + void Setter_UpperTimestepsGreaterThanMaxImageTimestep() + { + //check for correction if UpperBoundaryTimestep > image->GetTimestep() + auto refImage2Dt = mitk::IOUtil::Load(m_ImageFilename2Dt); + m_cropTimestepsFilter->SetInput(refImage2Dt); + unsigned int upperTimeStep = 11; + m_cropTimestepsFilter->SetUpperBoundaryTimestep(upperTimeStep); + CPPUNIT_ASSERT_EQUAL(m_cropTimestepsFilter->GetUpperBoundaryTimestep(), upperTimeStep); + CPPUNIT_ASSERT_NO_THROW(m_cropTimestepsFilter->Update()); + CPPUNIT_ASSERT_EQUAL(m_cropTimestepsFilter->GetUpperBoundaryTimestep(), refImage2Dt->GetTimeSteps()); + } + + void Filter_Default() + { + //Everything default: timesteps should stay the same + auto refImage3Dt = mitk::IOUtil::Load(m_ImageFilename3Dt); + auto timeGeometryBefore = refImage3Dt->GetTimeGeometry()->Clone(); + m_cropTimestepsFilter->SetInput(refImage3Dt); + unsigned int expectedLowerTimestep = 0; + unsigned int expectedUpperTimestep = std::numeric_limits::max(); + CPPUNIT_ASSERT_EQUAL(m_cropTimestepsFilter->GetLowerBoundaryTimestep(), expectedLowerTimestep); + CPPUNIT_ASSERT_EQUAL(m_cropTimestepsFilter->GetUpperBoundaryTimestep(), expectedUpperTimestep); + CPPUNIT_ASSERT_NO_THROW(m_cropTimestepsFilter->Update()); + CPPUNIT_ASSERT_EQUAL(m_cropTimestepsFilter->GetLowerBoundaryTimestep(), expectedLowerTimestep); + CPPUNIT_ASSERT_EQUAL(m_cropTimestepsFilter->GetUpperBoundaryTimestep(), refImage3Dt->GetTimeSteps()); + auto result = m_cropTimestepsFilter->GetOutput(); + auto timeGeometry = result->GetTimeGeometry(); + CPPUNIT_ASSERT_EQUAL(timeGeometryBefore->GetMinimumTimePoint(), timeGeometry->GetMinimumTimePoint()); + CPPUNIT_ASSERT_EQUAL(timeGeometryBefore->GetMaximumTimePoint(), timeGeometry->GetMaximumTimePoint()); + + CPPUNIT_ASSERT_EQUAL(result->GetTimeSteps(), refImage3Dt->GetTimeSteps()); + } + + void Filter_OnlyLowerBoundary() + { + //Crop lower 2 timesteps + auto refImage3Dt = mitk::IOUtil::Load(m_ImageFilename3Dt); + MITK_WARN << "before: " << *refImage3Dt; + auto timeGeometryBefore = refImage3Dt->GetTimeGeometry()->Clone(); + m_cropTimestepsFilter->SetInput(refImage3Dt); + unsigned int lowerTimestep = 2; + m_cropTimestepsFilter->SetLowerBoundaryTimestep(lowerTimestep); + CPPUNIT_ASSERT_NO_THROW(m_cropTimestepsFilter->Update()); + auto result = m_cropTimestepsFilter->GetOutput(); + auto timeGeometry = result->GetTimeGeometry(); + CPPUNIT_ASSERT_EQUAL(timeGeometryBefore->TimeStepToTimePoint(lowerTimestep), timeGeometry->GetMinimumTimePoint()); + CPPUNIT_ASSERT_EQUAL(timeGeometryBefore->GetMaximumTimePoint(), timeGeometry->GetMaximumTimePoint()); + CPPUNIT_ASSERT_EQUAL(refImage3Dt->GetTimeSteps()-lowerTimestep, result->GetTimeSteps()); + } + + void Filter_OnlyUpperBoundary() + { + // Crop upper 3 timesteps + auto refImage3Dt = mitk::IOUtil::Load(m_ImageFilename3Dt); + auto timeGeometryBefore = refImage3Dt->GetTimeGeometry()->Clone(); + m_cropTimestepsFilter->SetInput(refImage3Dt); + unsigned int upperTimestep = 7; + m_cropTimestepsFilter->SetUpperBoundaryTimestep(upperTimestep); + CPPUNIT_ASSERT_NO_THROW(m_cropTimestepsFilter->Update()); + auto result = m_cropTimestepsFilter->GetOutput(); + auto timeGeometry = result->GetTimeGeometry(); + CPPUNIT_ASSERT_EQUAL(timeGeometryBefore->TimeStepToTimePoint(upperTimestep), timeGeometry->GetMaximumTimePoint()); + CPPUNIT_ASSERT_EQUAL(timeGeometryBefore->GetMinimumTimePoint(), timeGeometry->GetMinimumTimePoint()); + CPPUNIT_ASSERT_EQUAL(upperTimestep, result->GetTimeSteps()); + } + + void Filter_BothBoundaries() + { + //Crop lower 2 and upper 3 timesteps + auto refImage3Dt = mitk::IOUtil::Load(m_ImageFilename3Dt); + m_cropTimestepsFilter->SetInput(refImage3Dt); + auto timeGeometryBefore = refImage3Dt->GetTimeGeometry()->Clone(); + unsigned int lowerTimestep = 1; + unsigned int upperTimestep = 7; + m_cropTimestepsFilter->SetLowerBoundaryTimestep(lowerTimestep); + m_cropTimestepsFilter->SetUpperBoundaryTimestep(upperTimestep); + CPPUNIT_ASSERT_NO_THROW(m_cropTimestepsFilter->Update()); + auto result = m_cropTimestepsFilter->GetOutput(); + auto timeGeometry = result->GetTimeGeometry(); + CPPUNIT_ASSERT_EQUAL(timeGeometryBefore->TimeStepToTimePoint(upperTimestep), timeGeometry->GetMaximumTimePoint()); + CPPUNIT_ASSERT_EQUAL(timeGeometryBefore->TimeStepToTimePoint(lowerTimestep), timeGeometry->GetMinimumTimePoint()); + CPPUNIT_ASSERT_EQUAL(upperTimestep-lowerTimestep, result->GetTimeSteps()); + } + + void Filter_BothBoundaries2Dt() + { + //Crop lower 1 and upper 1 timestep, resulting in 1 remaining timestep (2D+t input) + auto refImage2Dt = mitk::IOUtil::Load(m_ImageFilename2Dt); + m_cropTimestepsFilter->SetInput(refImage2Dt); + unsigned int lowerTimestep = 1; + unsigned int upperTimestep = 2; + m_cropTimestepsFilter->SetLowerBoundaryTimestep(lowerTimestep); + m_cropTimestepsFilter->SetUpperBoundaryTimestep(upperTimestep); + CPPUNIT_ASSERT_NO_THROW(m_cropTimestepsFilter->Update()); + auto result = m_cropTimestepsFilter->GetOutput(); + auto timeGeometry = result->GetTimeGeometry(); + CPPUNIT_ASSERT_EQUAL(mitk::TimePointType(upperTimestep), timeGeometry->GetMaximumTimePoint()); + CPPUNIT_ASSERT_EQUAL(mitk::TimePointType(lowerTimestep), timeGeometry->GetMinimumTimePoint()); + CPPUNIT_ASSERT_EQUAL(upperTimestep - lowerTimestep, result->GetTimeSteps()); + } + +}; +MITK_TEST_SUITE_REGISTRATION(mitkCropTimestepsImageFilter)