diff --git a/Documentation/docs/migration_guides/itk_6_migration_guide.md b/Documentation/docs/migration_guides/itk_6_migration_guide.md index 76176926621..dbf988251ab 100644 --- a/Documentation/docs/migration_guides/itk_6_migration_guide.md +++ b/Documentation/docs/migration_guides/itk_6_migration_guide.md @@ -115,3 +115,36 @@ The `CoordRepType` aliases will be removed when `ITK_FUTURE_LEGACY_REMOVE` is enabled. Similarly, `InputCoordinateType`, `OutputCoordinateType`, and `ImagePointCoordinateType` replace `InputCoordRepType`, `OutputCoordRepType`, and `ImagePointCoordRepType`, respectively. + +Remote module integration +------------------------- + +Some remote modules have been integrated into ITK proper. +Consequently, the `Module_XXX` CMake options have been removed. +Where the old module `XXX` was required, a different `ITKYYY` module might be +required. If it is transitively required by another module, listing it +explicitly should not be required. It is better to be on the safe side. + +CMake code with ITK 5.4 and earlier: +```cmake +set(EXAMPLE_ITK_COMPONENTS + ITKImageIO + ITKSmoothing + ... + XXX + ) +find_package(ITK 5.4 COMPONENTS ${EXAMPLE_ITK_COMPONENTS} REQUIRED) +``` +should now become: +```cmake +set(EXAMPLE_ITK_COMPONENTS + ITKImageIO + ITKSmoothing + ... + ITKYYY + ) +find_package(ITK 6.0 COMPONENTS ${EXAMPLE_ITK_COMPONENTS} REQUIRED) +``` +The following list provides old and new module names, as well as classes moved: + * `FastBilateral` is now `ITKImageFeature`. Classes: + * `FastBilateralImageFilter` \ No newline at end of file diff --git a/Modules/Filtering/ImageFeature/include/itkFastBilateralImageFilter.h b/Modules/Filtering/ImageFeature/include/itkFastBilateralImageFilter.h new file mode 100644 index 00000000000..9762dbbd794 --- /dev/null +++ b/Modules/Filtering/ImageFeature/include/itkFastBilateralImageFilter.h @@ -0,0 +1,204 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +#ifndef itkFastBilateralImageFilter_h +#define itkFastBilateralImageFilter_h + +#include "itkImageToImageFilter.h" +#include "itkImage.h" +#include "itkDiscreteGaussianImageFilter.h" +#include "itkImageRegionIterator.h" +#include "itkImageRegionConstIteratorWithIndex.h" +#include "itkLinearInterpolateImageFunction.h" + +namespace itk +{ +/** + * \class FastBilateralImageFilter + * \brief A fast approximation to the bilateral filter + * \ingroup FastBilateral + * + * This filter is a fast approximation to the bilateral filter. + * Blurring is performed on an image based on the distance of pixels in + * both space and intensity. + * + * The algorithm used was originally proposed by Paris and + * Durand [1]. + * + * Instead of calculating a kernel for every pixel in + * an image, this filter places the values of each pixel into a higher + * dimensional image determined by the position and intensity of a pixel. + * How many bins are used is determined by the sigma values provided + * to the filter. Larger sigmas will result in more aggressive downsampling + * and less running time overall. After the data of an image + * has been organized into bins, a DiscreteGaussianImageFilter is applied. + * Finally, the output image is constructed by interpolating the + * values of the output pixels from the blurred higher + * dimensional image. + * + * This filter is great for large spatial sigmas. Numerical differences to + * BilateralImageFilter are negligible for most purposes. + * + * NOTE: This filter is slow for small intensity sigmas and large pixel types + * (e.g. short, int, or float with large intensity range). + * + * + * [1] Sylvain Paris and Frédo Durand, + * A Fast Approximation of the Bilateral Filter using a Signal Processing + * Approach, + * European Conference on Computer Vision (ECCV'06) + * + * \sa BilateralImageFilter + * \sa GaussianOperator + * \sa AnisotropicDiffusionImageFilter + * \sa Image + * \sa Neighborhood + * \sa NeighborhoodOperator + * + * \ingroup ImageEnhancement + * \ingroup ImageFeatureExtraction + * + * \todo Support for color images + * \todo Support for vector images + */ + +template +class ITK_EXPORT FastBilateralImageFilter : public ImageToImageFilter +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(FastBilateralImageFilter); + + /** Standard class typedefs. */ + using Self = FastBilateralImageFilter; + using Superclass = ImageToImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(FastBilateralImageFilter, ImageToImageFilter); + + /** Dimensionality of the input image. Dimensionality of the output image + * is assumed to be the same. */ + static constexpr unsigned int ImageDimension = TInputImage::ImageDimension; + + /** Input image typedefs. */ + using InputImageType = TInputImage; + using InputImagePointer = typename TInputImage::Pointer; + using InputImageConstPointer = typename TInputImage::ConstPointer; + using InputImageSpacingType = typename TInputImage::SpacingType; + using InputImageSizeType = typename TInputImage::SizeType; + using InputImageIndexType = typename TInputImage::IndexType; + + /** Input image iterator type. */ + using InputImageConstIteratorType = ImageRegionConstIteratorWithIndex; + + /** Output image typedefs. */ + using OutputImageType = TOutputImage; + using OutputImagePointer = typename TOutputImage::Pointer; + + /** Output image iterator type. */ + using OutputImageIteratorType = ImageRegionIterator; + + /** Pixel types. */ + using OutputPixelType = typename TOutputImage::PixelType; + using InputPixelType = typename TInputImage::PixelType; + + /** Typedef for an array of doubles that specifies the DomainSigma + * in each spacial dimension. */ + using DomainSigmaArrayType = FixedArray; + + /** Standard get/set macros for filter parameters. + * DomainSigma is specified in the same units as the Image spacing. + * RangeSigma is specified in the units of intensity. */ + itkGetConstMacro(DomainSigma, const DomainSigmaArrayType); + itkSetMacro(DomainSigma, DomainSigmaArrayType); + itkGetConstMacro(RangeSigma, double); + itkSetMacro(RangeSigma, double); + + /** Convenience set method for setting all domain standard deviations to the + * same value. */ + void + SetDomainSigma(const double v) + { + m_DomainSigma.Fill(v); + } + +protected: + /** Default Constructor. Default value for DomainSigma is 4. Default + * value for RangeSigma is 50. These values were chosen match those of the + * BilateralImageFilter */ + FastBilateralImageFilter() + { + m_DomainSigma.Fill(4.0); + m_RangeSigma = 50.0; + } + + virtual ~FastBilateralImageFilter() {} + + /* + * The FastBilateralImageFilter needs a larger input requested + * region than the size of the output requested region. Like + * the BilateralImageFilter, the FastBilateralImageFilter needs + * an amount of padding in each dimension based on the domain sigma. + */ + void + GenerateInputRequestedRegion() override; + + /** Standard pipeline method */ + void + GenerateData() override; + + /** Method to print member variables to an output stream */ + void + PrintSelf(std::ostream & os, Indent indent) const override; + + /** The type of image to use as the higher dimensional grid. + * The blurring is performed on this image type. */ + using GridType = typename itk::Image; + + /** Grid types */ + using GridPixelType = typename GridType::PixelType; + using GridIndexType = typename GridType::IndexType; + using GridSizeType = typename GridType::SizeType; + using GridSizeValueType = typename Size::SizeValueType; + using GridRegionType = typename GridType::RegionType; + + /** Grid image iterator type. */ + using GridImageIteratorType = ImageRegionIterator; + using GridImageConstIteratorType = ImageRegionConstIterator; + + /** The type of blurring to use on the grid. */ + using BlurType = DiscreteGaussianImageFilter; + + /** The type of interpolation done to calculate output pixels. */ + using InterpolatorType = LinearInterpolateImageFunction; + using InterpolatedIndexType = typename InterpolatorType::ContinuousIndexType; + + double m_RangeSigma; + DomainSigmaArrayType m_DomainSigma; +}; + +} // namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkFastBilateralImageFilter.hxx" +#endif + +#endif // itkFastBilateralImageFilter diff --git a/Modules/Filtering/ImageFeature/include/itkFastBilateralImageFilter.hxx b/Modules/Filtering/ImageFeature/include/itkFastBilateralImageFilter.hxx new file mode 100644 index 00000000000..bc2778a2d3e --- /dev/null +++ b/Modules/Filtering/ImageFeature/include/itkFastBilateralImageFilter.hxx @@ -0,0 +1,311 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +#ifndef itkFastBilateralImageFilter_hxx +#define itkFastBilateralImageFilter_hxx + +#include "itkMinimumMaximumImageCalculator.h" +#include "itkImageDuplicator.h" +#include + +namespace itk +{ + +template +void +FastBilateralImageFilter::GenerateInputRequestedRegion() +{ + // call the superclass' implementation of this method. this should + // copy the output requested region to the input requested region + Superclass::GenerateInputRequestedRegion(); + + // get pointers to the input and output + typename Superclass::InputImagePointer inputPtr = const_cast(this->GetInput()); + + if (!inputPtr) + { + return; + } + + // Pad the image by 2*sigma (pixel units) + // this is done to ensure that nearby pixels are still + // included in calculations + // When the filter does the downsampling pixels are placed into + // bins based on their position/sigma. Therefore, assuming + // that the input region includes a pixel that would be placed into a new bin + // on its own, that bin and the bin beside it would need to be populated. + // Each bin can contain at most ceil(sigma) pixels + InputImageSizeType radius; + for (size_t i = 0; i < ImageDimension; ++i) + { + radius[i] = 2 * std::ceil(m_DomainSigma[i] / (this->GetInput()->GetSpacing()[i])); + } + + // get a copy of the input requested region (should equal the output + // requested region) + typename TInputImage::RegionType inputRequestedRegion; + inputRequestedRegion = inputPtr->GetRequestedRegion(); + + // pad the input requested region by the operator radius + inputRequestedRegion.PadByRadius(radius); + + // crop the input requested region at the input's largest possible region + if (inputRequestedRegion.Crop(inputPtr->GetLargestPossibleRegion())) + { + inputPtr->SetRequestedRegion(inputRequestedRegion); + return; + } + else + { + // Couldn't crop the region (requested region is outside the largest + // possible region). Throw an exception. + + // store what we tried to request (prior to trying to crop) + inputPtr->SetRequestedRegion(inputRequestedRegion); + + // build an exception + InvalidRequestedRegionError e(__FILE__, __LINE__); + e.SetLocation(ITK_LOCATION); + e.SetDescription("Requested region is outside the largest possible region."); + e.SetDataObject(inputPtr); + throw e; + } +} + +template +void +FastBilateralImageFilter::GenerateData() +{ + this->AllocateOutputs(); + InputImageConstPointer input = this->GetInput(); + OutputImagePointer output = this->GetOutput(); + + // Array to store domain sigmas, used during down-sampling and reconstruction + DomainSigmaArrayType domainSigmaInPixels; + + // Minimum intensity of the input image, + // used during down-sampling and reconstruction + InputPixelType intensityMin; + + // Define the GridType + // These are pointers to the source and destination of the blurring filter + // Data from the input will be sorted into the first two images while the + // second two images will provide the output data. + // The parameters are determined by the size of the input image and the + // values of m_DomainSigma and m_RangeSigma + typename GridType::Pointer gridImage = GridType::New(); + typename GridType::Pointer gridWeight; + typename GridType::Pointer gridImageOut; + typename GridType::Pointer gridWeightOut; + + // The amount of padding around the grid images, required so that + // interpolation is not done outside of the grid during reconstruction + int padding = 2; + + // Setup the higher dimensional grids (gridImage and gridWeight). + { + GridIndexType gridStartPos; + GridSizeType gridSize; + + // Convert domain sigmas from spacing units to pixel units + // for the blurring in the grid. + // The set method for m_DomainSigma uses spacing units to be + // consistent with the itkBilateralImageFilter implementation. + // When the data is placed into bins in the grid the + // itkDiscreteGaussianImageFilter will be run on the grid using + // imageSpacingOff. + InputImageSizeType inputSize = input->GetRequestedRegion().GetSize(); + // InputImageSizeType fullSize = input->GetLargestPossibleRegion().GetSize(); + + const InputImageSpacingType & spacing = input->GetSpacing(); + for (size_t i = 0; i < ImageDimension; ++i) + { + domainSigmaInPixels[i] = m_DomainSigma[i] / spacing[i]; + gridSize[i] = std::floor((inputSize[i] - 1) / domainSigmaInPixels[i]) + 1 + 2 * padding; + } + + // Determine min/max intensities to calculate grid size in the intensity axis + using MinMaxCalculatorType = MinimumMaximumImageCalculator; + typename MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); + + calculator->SetImage(input); + calculator->Compute(); + intensityMin = calculator->GetMinimum(); + InputPixelType intensityMax = calculator->GetMaximum(); + InputPixelType intensityDelta = static_cast(intensityMax - intensityMin); + gridSize[ImageDimension] = static_cast((intensityDelta / m_RangeSigma) + 1 + 2 * padding); + + for (size_t i = 0; i < ImageDimension + 1; ++i) + { + gridStartPos[i] = 0; + } + + GridRegionType region; + region.SetSize(gridSize); + region.SetIndex(gridStartPos); + gridImage->SetRegions(region); + } + + // Allocate the memory for pixel data + // We now have an empty grid. Two will be needed total, + // one will be a container for the down-sampled data and one + // will remember how many pixels were placed into each bin + gridImage->Allocate(); + // Init values of image to 0 + gridImage->FillBuffer(0.0); + + // Duplicate our grid image for the weight image + { + using DuplicatorType = ImageDuplicator; + typename DuplicatorType::Pointer duplicator = DuplicatorType::New(); + duplicator->SetInputImage(gridImage); + duplicator->Update(); + gridWeight = duplicator->GetOutput(); + } + + // Sort the input image in gridImage and keep track of weights in gridWeight + { + InputImageConstIteratorType iterInputImage(input, input->GetRequestedRegion()); + GridIndexType gridIndices; + size_t i; + InputPixelType current; + InputPixelType intensityDelta; + InputImageIndexType index; + + // For every pixel in the input image, place it into a bin in the grid + // This is a scatter type operation and will be inefficient, as far as I + // know, there is no way to place the pixels into the grid using iterators + for (iterInputImage.GoToBegin(); !iterInputImage.IsAtEnd(); ++iterInputImage) + { + index = iterInputImage.GetIndex(); + current = iterInputImage.Get(); + // Determine the position in the grid to place the pixel + for (i = 0; i < ImageDimension; ++i) + { + gridIndices[i] = static_cast(index[i] / domainSigmaInPixels[i] + 0.5 + padding); + } + intensityDelta = current - intensityMin; + gridIndices[ImageDimension] = static_cast(intensityDelta / m_RangeSigma + 0.5 + padding); + + // Update the bin and the weight + (gridImage->GetPixel(gridIndices)) += current; + (gridWeight->GetPixel(gridIndices)) += 1.0; + } + } + + // Perform blurring on gridImage and gridWeight + // outputs are pointed to by gridImageOut and gridWeightOut + { + + // This variance approximately corresponds to a 1D filter of [1 2 1] which is + // used in Paris and Durands C++ implementation to blur their down-sampled + // data. With this variance a kernel width larger than 5 is not necessary. + double variance = 1.59577; + int maxWidth = 5; + + // Setup the Gaussian filter + typename BlurType::Pointer gridImageBlurFilter = BlurType::New(); + typename BlurType::Pointer gridWeightBlurFilter = BlurType::New(); + + gridImageBlurFilter->SetVariance(variance); + gridWeightBlurFilter->SetVariance(variance); + + gridImageBlurFilter->SetUseImageSpacingOff(); + gridWeightBlurFilter->SetUseImageSpacingOff(); + + gridImageBlurFilter->SetMaximumKernelWidth(maxWidth); + gridWeightBlurFilter->SetMaximumKernelWidth(maxWidth); + + gridImageBlurFilter->SetInput(gridImage); + gridWeightBlurFilter->SetInput(gridWeight); + + gridImageOut = gridImageBlurFilter->GetOutput(); + gridWeightOut = gridWeightBlurFilter->GetOutput(); + + gridImageBlurFilter->Update(); + gridWeightBlurFilter->Update(); + } + + // Early division; in Paris and Durand's implementation early division can + // be done on the grid image, or interpolation on both the bin and the + // weights can be done then the division. Interpolation is an expensive + // operation so I've opted for the early division approach. + { + GridImageIteratorType iterGridImage(gridImageOut, gridImageOut->GetLargestPossibleRegion()); + GridImageConstIteratorType iterGridWeight(gridWeightOut, gridWeightOut->GetLargestPossibleRegion()); + + GridPixelType weight; + + for (iterGridImage.GoToBegin(), iterGridWeight.GoToBegin(); !iterGridImage.IsAtEnd(); + ++iterGridImage, ++iterGridWeight) + { + if ((weight = iterGridWeight.Get()) != 0.0) + { + iterGridImage.Value() /= weight; + } + } + } + + // Perform interpolation in order to construct the output. + // For every pixel in the input image, determine where in the grid the pixel + // was placed and interpolate for the output pixel's value. + { + + OutputImageIteratorType iterOutputImage(output, output->GetRequestedRegion()); + InputImageConstIteratorType iterInputImage(input, output->GetRequestedRegion()); + + InterpolatedIndexType gridIndices; + size_t i; + InputPixelType current; + InputPixelType intensityDelta; + InputImageIndexType index; + typename InterpolatorType::Pointer interpolator = InterpolatorType::New(); + interpolator->SetInputImage(gridImageOut); + + for (iterOutputImage.GoToBegin(), iterInputImage.GoToBegin(); !iterOutputImage.IsAtEnd(); + ++iterOutputImage, ++iterInputImage) + { + index = iterInputImage.GetIndex(); + current = iterInputImage.Get(); + // Determine the position in the grid to get the data from + for (i = 0; i < ImageDimension; ++i) + { + gridIndices[i] = index[i] / domainSigmaInPixels[i] + padding; + } + + intensityDelta = current - intensityMin; + + gridIndices[ImageDimension] = intensityDelta / m_RangeSigma + padding; + + iterOutputImage.Set(static_cast(interpolator->EvaluateAtContinuousIndex(gridIndices))); + } + } +} + +template +void +FastBilateralImageFilter::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + os << indent << "DomainSigma: " << m_DomainSigma << std::endl; + os << indent << "RangeSigma: " << m_RangeSigma << std::endl; +} + +} // namespace itk + +#endif diff --git a/Modules/Filtering/ImageFeature/itk-module.cmake b/Modules/Filtering/ImageFeature/itk-module.cmake index 78a6c70aaf3..5efb3f98609 100644 --- a/Modules/Filtering/ImageFeature/itk-module.cmake +++ b/Modules/Filtering/ImageFeature/itk-module.cmake @@ -18,6 +18,8 @@ itk_module( TEST_DEPENDS ITKTestKernel ITKThresholding + ITKIOMeta + ITKIOPNG DESCRIPTION "${DOCUMENTATION}") # Extra test dependency on ITKThresholding is introduced by itkHoughTransform2DCirclesImageTest. diff --git a/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTest2Output.png.cid b/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTest2Output.png.cid new file mode 100644 index 00000000000..4644bf77b5a --- /dev/null +++ b/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTest2Output.png.cid @@ -0,0 +1 @@ +bafkreiadhbzhtn4fsqlgw4lscdcwzkqbrs5gntgirpbobrdbwv6hskaozy diff --git a/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTest3Output.png.cid b/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTest3Output.png.cid new file mode 100644 index 00000000000..ad6fbbd4cf4 --- /dev/null +++ b/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTest3Output.png.cid @@ -0,0 +1 @@ +bafkreibryts52hwb7mtu5lab6lvf36omn5jlpk6hmedue2itn7f3s5zrvm diff --git a/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTestOutput.mha.cid b/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTestOutput.mha.cid new file mode 100644 index 00000000000..5f6fabb3007 --- /dev/null +++ b/Modules/Filtering/ImageFeature/test/Baseline/itkFastBilateralImageFilterTestOutput.mha.cid @@ -0,0 +1 @@ +bafkreickuip2r2uejp3bzrizyvis7vs77edil3kkuh45rnye5tvfrjof6i diff --git a/Modules/Filtering/ImageFeature/test/CMakeLists.txt b/Modules/Filtering/ImageFeature/test/CMakeLists.txt index 5469a0f9c08..bd580265901 100644 --- a/Modules/Filtering/ImageFeature/test/CMakeLists.txt +++ b/Modules/Filtering/ImageFeature/test/CMakeLists.txt @@ -13,6 +13,9 @@ set(ITKImageFeatureTests itkBilateralImageFilterTest.cxx itkBilateralImageFilterTest2.cxx itkBilateralImageFilterTest3.cxx + itkFastBilateralImageFilterTest.cxx + itkFastBilateralImageFilterTest2.cxx + itkFastBilateralImageFilterTest3.cxx itkGradientVectorFlowImageFilterTest.cxx itkSimpleContourExtractorImageFilterTest.cxx itkZeroCrossingImageFilterTest.cxx @@ -25,7 +28,8 @@ set(ITKImageFeatureTests itkUnsharpMaskImageFilterTest.cxx itkDiscreteGaussianDerivativeImageFilterScaleSpaceTest.cxx itkDiscreteGaussianDerivativeImageFilterTest.cxx - itkMultiScaleHessianBasedMeasureImageFilterTest.cxx) + itkMultiScaleHessianBasedMeasureImageFilterTest.cxx +) createtestdriver(ITKImageFeature "${ITKImageFeature-Test_LIBRARIES}" "${ITKImageFeatureTests}") @@ -237,6 +241,36 @@ itk_add_test( itkBilateralImageFilterTest3 DATA{${ITK_DATA_ROOT}/Input/cake_easy.png} ${ITK_TEST_OUTPUT_DIR}/BilateralImageFilterTest3.png) + +itk_add_test(NAME itkFastBilateralImageFilterTest + COMMAND ITKImageFeatureTestDriver + --compare + DATA{Baseline/itkFastBilateralImageFilterTestOutput.mha} + ${ITK_TEST_OUTPUT_DIR}/itkFastBilateralImageFilterTestOutput.mha + itkFastBilateralImageFilterTest + ${ITK_TEST_OUTPUT_DIR}/itkFastBilateralImageFilterTestOutput.mha + ) + +itk_add_test(NAME itkFastBilateralImageFilterTest2 + COMMAND ITKImageFeatureTestDriver + --compare + DATA{Baseline/itkFastBilateralImageFilterTest2Output.png} + ${ITK_TEST_OUTPUT_DIR}/itkFastBilateralImageFilterTest2Output.png + itkFastBilateralImageFilterTest2 + DATA{${ITK_DATA_ROOT}/Input/cake_easy.png} + ${ITK_TEST_OUTPUT_DIR}/itkFastBilateralImageFilterTest2Output.png + ) + +itk_add_test(NAME itkFastBilateralImageFilterTest3 + COMMAND ITKImageFeatureTestDriver + --compare + DATA{Baseline/itkFastBilateralImageFilterTest3Output.png} + ${ITK_TEST_OUTPUT_DIR}/itkFastBilateralImageFilterTest3Output.png + itkFastBilateralImageFilterTest3 + DATA{${ITK_DATA_ROOT}/Input/cake_easy.png} + ${ITK_TEST_OUTPUT_DIR}/itkFastBilateralImageFilterTest3Output.png + ) + itk_add_test( NAME itkGradientVectorFlowImageFilterTest diff --git a/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest.cxx b/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest.cxx new file mode 100644 index 00000000000..7303fe61670 --- /dev/null +++ b/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest.cxx @@ -0,0 +1,100 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkFastBilateralImageFilter.h" + +#include "itkCommand.h" +#include "itkImageFileWriter.h" +#include "itkTestingMacros.h" + +namespace +{ +class ShowProgress : public itk::Command +{ +public: + itkNewMacro(ShowProgress); + + void + Execute(itk::Object * caller, const itk::EventObject & event) override + { + Execute((const itk::Object *)caller, event); + } + + void + Execute(const itk::Object * caller, const itk::EventObject & event) override + { + if (!itk::ProgressEvent().CheckEvent(&event)) + { + return; + } + const auto * processObject = dynamic_cast(caller); + if (!processObject) + { + return; + } + std::cout << " " << processObject->GetProgress(); + } +}; +} // namespace + +int +itkFastBilateralImageFilterTest(int argc, char * argv[]) +{ + if (argc < 2) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv); + std::cerr << " outputImage"; + std::cerr << std::endl; + return EXIT_FAILURE; + } + const char * outputImageFileName = argv[1]; + + constexpr unsigned int Dimension = 2; + using PixelType = float; + using ImageType = itk::Image; + + using FilterType = itk::FastBilateralImageFilter; + FilterType::Pointer filter = FilterType::New(); + + ITK_EXERCISE_BASIC_OBJECT_METHODS(filter, FastBilateralImageFilter, ImageToImageFilter); + + // Create input image to avoid test dependencies. + ImageType::SizeType size; + size.Fill(128); + ImageType::Pointer image = ImageType::New(); + image->SetRegions(size); + image->Allocate(); + image->FillBuffer(1.1f); + + ShowProgress::Pointer showProgress = ShowProgress::New(); + filter->AddObserver(itk::ProgressEvent(), showProgress); + filter->SetInput(image); + + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(outputImageFileName); + writer->SetInput(filter->GetOutput()); + writer->SetUseCompression(true); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest2.cxx b/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest2.cxx new file mode 100644 index 00000000000..5d4241cddd2 --- /dev/null +++ b/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest2.cxx @@ -0,0 +1,93 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +#include +#include "itkFastBilateralImageFilter.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkImageRegionIterator.h" +#include "itkTestingMacros.h" + +/** + * This test was originally taken from the tests for the itkBilateralImageFilter + * and modified for the itkFastBilateralImageFilter. + */ +int +itkFastBilateralImageFilterTest2(int ac, char * av[]) +{ + if (ac < 3) + { + std::cerr << "Usage: " << av[0] << " InputImage OutputImage\n"; + return EXIT_FAILURE; + } + + using PixelType = unsigned char; + const unsigned int dimension = 2; + using ImageType = itk::Image; + auto input = itk::ImageFileReader::New(); + input->SetFileName(av[1]); + + // Create a filter + using FilterType = itk::FastBilateralImageFilter; + + auto filter = FilterType::New(); + + filter->SetInput(input->GetOutput()); + + // these settings reduce the amount of noise by a factor of 10 + // when the original signal to noise level is 5 + filter->SetDomainSigma(4.0); + filter->SetRangeSigma(50.0); + + + // Test itkSetVectorMacro + double domainSigma[dimension]; + for (unsigned int i = 0; i < dimension; i++) + { + domainSigma[i] = 4.0; + } + filter->SetDomainSigma(domainSigma); + + // Test itkGetMacro + std::cout << "filter->GetDomainSigma(): " << filter->GetDomainSigma() << std::endl; + std::cout << "filter->GetRangeSigma(): " << filter->GetRangeSigma() << std::endl; + + try + { + input->Update(); + filter->Update(); + } + catch (itk::ExceptionObject & e) + { + std::cerr << "Exception detected: " << e.GetDescription(); + return -1; + } + catch (...) + { + std::cerr << "Some other exception occurred" << std::endl; + return -2; + } + + // Generate test image + itk::ImageFileWriter::Pointer writer; + writer = itk::ImageFileWriter::New(); + writer->SetInput(filter->GetOutput()); + writer->SetFileName(av[2]); + writer->Update(); + + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest3.cxx b/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest3.cxx new file mode 100644 index 00000000000..fc1c48edf54 --- /dev/null +++ b/Modules/Filtering/ImageFeature/test/itkFastBilateralImageFilterTest3.cxx @@ -0,0 +1,92 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include +#include "itkFastBilateralImageFilter.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkImageRegionIterator.h" +#include "itkTestingMacros.h" + + +/** + * This test was originally taken from the tests for the itkBilateralImageFilter + * and modified for the itkFastBilateralImageFilter. + */ +int +itkFastBilateralImageFilterTest3(int ac, char * av[]) +{ + if (ac < 3) + { + std::cerr << "Usage: " << av[0] << " InputImage BaselineImage\n"; + return -1; + } + + using PixelType = unsigned char; + using ImageType = itk::Image; + auto input = itk::ImageFileReader::New(); + input->SetFileName(av[1]); + + // Create a filter + using FilterType = itk::FastBilateralImageFilter; + + FilterType::Pointer filter1 = FilterType::New(); + filter1->SetInput(input->GetOutput()); + FilterType::Pointer filter2 = FilterType::New(); + filter2->SetInput(filter1->GetOutput()); + FilterType::Pointer filter3 = FilterType::New(); + filter3->SetInput(filter2->GetOutput()); + + // Instead of using a single agressive smoothing filter, use 3 + // less aggressive filters. + // + // These settings match the "wedding" cake image (cake_easy.png) where + // the signal to noise ratio is 5 (step heights near 100 units, + // noise sigma near 20 units). A single filter stage with these + // settings cuts the noise level in half. These three stages should + // reduce the amount of noise by a factor of 8. This is comparable to + // the noise reduction in using a single stage with parameters + // (4.0, 50.0). The difference is that with 3 less aggressive stages + // the edges are preserved better. + filter1->SetDomainSigma(4.0); + filter1->SetRangeSigma(20.0); + filter2->SetDomainSigma(4.0); + filter2->SetRangeSigma(20.0); + filter3->SetDomainSigma(4.0); + filter3->SetRangeSigma(20.0); + + try + { + input->Update(); + filter3->Update(); + } + catch (itk::ExceptionObject & e) + { + std::cerr << "Exception detected: " << e.GetDescription(); + return -1; + } + + // Generate test image + itk::ImageFileWriter::Pointer writer; + writer = itk::ImageFileWriter::New(); + writer->SetInput(filter3->GetOutput()); + writer->SetFileName(av[2]); + writer->Update(); + + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/ImageFeature/wrapping/itkFastBilateralImageFilter.wrap b/Modules/Filtering/ImageFeature/wrapping/itkFastBilateralImageFilter.wrap new file mode 100644 index 00000000000..93531e004da --- /dev/null +++ b/Modules/Filtering/ImageFeature/wrapping/itkFastBilateralImageFilter.wrap @@ -0,0 +1,3 @@ +itk_wrap_class("itk::FastBilateralImageFilter" POINTER) + itk_wrap_image_filter("${WRAP_ITK_SCALAR}" 2) +itk_end_wrap_class() diff --git a/Modules/Filtering/ImageFeature/wrapping/test/CMakeLists.txt b/Modules/Filtering/ImageFeature/wrapping/test/CMakeLists.txt index 6602c096c81..f88a063b8cd 100644 --- a/Modules/Filtering/ImageFeature/wrapping/test/CMakeLists.txt +++ b/Modules/Filtering/ImageFeature/wrapping/test/CMakeLists.txt @@ -56,4 +56,7 @@ if(ITK_WRAP_PYTHON) DATA{Baseline/itkHoughTransform2DLinesImageFilterTest.png}) endif() + + itk_python_expression_add_test(NAME itkFastBilateralImageFilterPythonTest + EXPRESSION "instance = itk.FastBilateralImageFilter.New()") endif() diff --git a/Modules/Remote/FastBilateral.remote.cmake b/Modules/Remote/FastBilateral.remote.cmake index 1516b0f79af..0f424b90265 100644 --- a/Modules/Remote/FastBilateral.remote.cmake +++ b/Modules/Remote/FastBilateral.remote.cmake @@ -1,54 +1,3 @@ -#-- # Grading Level Criteria Report -#-- EVALUATION DATE: 2024-03-19 -#-- EVALUATORS: [Dženan Zukić] -#-- -#-- ## Compliance level 5 star (AKA ITK main modules, or remote modules that could become core modules) -#-- - [ ] Widespread community dependance -#-- - [ ] Above 90% code coverage -#-- - [ ] CI dashboards and testing monitored rigorously -#-- - [x] Key API features are exposed in wrapping interface -#-- - [ ] All requirements of Levels 4,3,2,1 -#-- -#-- ## Compliance Level 4 star (Very high-quality code, perhaps small community dependance) -#-- - [ ] Meets all ITK code style standards -#-- - [X] No external requirements beyond those needed by ITK proper -#-- - [X] Builds and passes tests on all supported platforms within 1 month of each core tagged release -#-- - [X] Windows Shared Library Build with Visual Studio -#-- - [X] Mac with clang compiller -#-- - [X] Linux with gcc compiler -#-- - [ ] Active developer community dedicated to maintaining code-base -#-- - [ ] 75% code coverage demonstrated for testing suite -#-- - [X] Continuous integration testing performed -#-- - [X] All requirements of Levels 3,2,1 -#-- -#-- ## Compliance Level 3 star (Quality beta code) -#-- - [X] API executable interface is considered mostly stable and feature complete -#-- - [X] 10% C0-code coverage demonstrated for testing suite -#-- - [X] Some tests exist and pass on at least some platform -#-- - [X] All requirements of Levels 2,1 -#-- -#-- ## Compliance Level 2 star (Alpha code feature API development or niche community/execution environment dependance ) -#-- - [X] Compiles for at least 1 niche set of execution envirionments, and perhaps others -#-- (may depend on specific external tools like a java environment, or specific external libraries to work ) -#-- - [X] All requirements of Levels 1 -#-- -#-- ## Compliance Level 1 star (Pre-alpha features under development and code of unknown quality) -#-- - [X] Code complies on at least 1 platform -#-- -#-- ## Compliance Level 0 star ( Code/Feature of known poor-quality or deprecated status ) -#-- - [ ] Code reviewed and explicitly identified as not recommended for use -#-- -#-- ### Please document here any justification for the criteria above -# Code style enforced by clang-format on 2024-03-19 - -# Contact: Dženan Zukić -itk_fetch_module( - FastBilateral - "A Fast Approximation to the Bilateral Filter for ITK - -Insight Journal article: -https://doi.org/10.54294/noo5vc" - MODULE_COMPLIANCE_LEVEL 3 - GIT_REPOSITORY https://github.com/InsightSoftwareConsortium/ITKFastBilateral.git - GIT_TAG e37be230b50c6934b0c260c2ffec8092bf54f498 - ) +set(old_module_name "FastBilateral") +set(new_module_name "ITKImageFeature") +message(FATAL_ERROR "Remote module ${old_module_name} has been integrated into ITK proper. It is now part of module ${new_module_name}. Take a look at the migration guide for more information. Migration guide:\nhttps://github.com/InsightSoftwareConsortium/ITK/blob/master/Documentation/docs/migration_guides/itk_6_migration_guide.md#remote-module-integration")