diff --git a/Modules/Filtering/LabelErodeDilate/CMakeLists.txt b/Modules/Filtering/LabelErodeDilate/CMakeLists.txt new file mode 100644 index 000000000000..f949f15f5718 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.16.3) +project(LabelErodeDilate) + +if(NOT ITK_SOURCE_DIR) + find_package(ITK REQUIRED) + list(APPEND CMAKE_MODULE_PATH ${ITK_CMAKE_DIR}) + include(ITKModuleExternal) +else() + itk_module_impl() +endif() diff --git a/Modules/Filtering/LabelErodeDilate/README.md b/Modules/Filtering/LabelErodeDilate/README.md new file mode 100644 index 000000000000..3092dd001f31 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/README.md @@ -0,0 +1,23 @@ +# LabelErodeDilate + +In-tree ITK module providing morphological erosion and dilation +filters for label images. Label collisions are handled consistently +and the operations execute in approximately constant time with +respect to structuring-element size. Only circular, spherical, and +hyperspherical structuring elements are supported. + +The flagship classes are `itk::LabelSetDilateImageFilter` and +`itk::LabelSetErodeImageFilter`, with shared infrastructure in +`itk::LabelSetMorphBaseImageFilter`. + +## Origin + +Ingested from the standalone remote module +[**InsightSoftwareConsortium/ITKLabelErodeDilate**](https://github.com/InsightSoftwareConsortium/ITKLabelErodeDilate) +on 2026-04-29. The upstream repository will be archived read-only +after this PR merges; it remains reachable at the URL above. + +## References + +- Beare R. *A morphological approach to vessel segmentation.* The Insight Journal. 2018. +- Beare R. *Histogram-based thresholding — some missing methods.* The Insight Journal. 2011. diff --git a/Modules/Filtering/LabelErodeDilate/include/itkLabelSetDilateImageFilter.h b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetDilateImageFilter.h new file mode 100644 index 000000000000..503bfc026710 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetDilateImageFilter.h @@ -0,0 +1,94 @@ +/*========================================================================= + * + * 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 itkLabelSetDilateImageFilter_h +#define itkLabelSetDilateImageFilter_h + +#include "itkLabelSetMorphBaseImageFilter.h" +#include "itkNumericTraits.h" + +namespace itk +{ +/** + * \class LabelSetDilateImageFilter + * \brief Class for binary morphological erosion of label images. + * + * This filter is threaded. + * + * \sa itkLabelSetDilateErodeImageFilter + * + * \ingroup LabelErodeDilate + * + * \author Richard Beare, Department of Medicine, Monash University, + * Australia. + **/ +template +class ITK_EXPORT LabelSetDilateImageFilter : public LabelSetMorphBaseImageFilter +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(LabelSetDilateImageFilter); + + /** Standard class type alias. */ + using Self = LabelSetDilateImageFilter; + using Superclass = LabelSetMorphBaseImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkOverrideGetNameOfClassMacro(LabelSetDilateImageFilter); + + /** Pixel Type of the input image */ + using InputImageType = TInputImage; + using OutputImageType = TOutputImage; + using PixelType = typename TInputImage::PixelType; + using RealType = typename NumericTraits::FloatType; + using OutputPixelType = typename TOutputImage::PixelType; + using ScalarRealType = typename NumericTraits::ScalarRealType; + + /** Smart pointer type alias support. */ + using InputImagePointer = typename TInputImage::Pointer; + using InputImageConstPointer = typename TInputImage::ConstPointer; + using InputSizeType = typename TInputImage::SizeType; + using OutputSizeType = typename TOutputImage::SizeType; + + using OutputImageRegionType = typename OutputImageType::RegionType; + + /** Image dimension. */ + static constexpr unsigned int ImageDimension = TInputImage::ImageDimension; + static constexpr unsigned int OutputImageDimension = TOutputImage::ImageDimension; + static constexpr unsigned int InputImageDimension = TInputImage::ImageDimension; + +protected: + LabelSetDilateImageFilter() { this->DynamicMultiThreadingOff(); } + ~LabelSetDilateImageFilter() override = default; + + void + ThreadedGenerateData(const OutputImageRegionType & outputRegionForThread, ThreadIdType threadId) override; + +private: + using DistanceImageType = typename Superclass::DistanceImageType; +}; +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkLabelSetDilateImageFilter.hxx" +#endif + +#endif diff --git a/Modules/Filtering/LabelErodeDilate/include/itkLabelSetDilateImageFilter.hxx b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetDilateImageFilter.hxx new file mode 100644 index 000000000000..92b617d63a29 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetDilateImageFilter.hxx @@ -0,0 +1,147 @@ +/*========================================================================= + * + * 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 itkLabelSetDilateImageFilter_hxx +#define itkLabelSetDilateImageFilter_hxx + +#include "itkImageRegionConstIterator.h" +#include "itkImageRegionIterator.h" + +#include "itkImageLinearIteratorWithIndex.h" +#include "itkImageLinearConstIteratorWithIndex.h" + +#include "itkLabelSetUtils.h" + +namespace itk +{ +template +void +LabelSetDilateImageFilter::ThreadedGenerateData( + const OutputImageRegionType & outputRegionForThread, + ThreadIdType threadId) +{ + // compute the number of rows first, so we can setup a progress reporter + typename std::vector NumberOfRows; + InputSizeType size = outputRegionForThread.GetSize(); + + for (unsigned int i = 0; i < InputImageDimension; i++) + { + NumberOfRows.push_back(1); + for (unsigned int d = 0; d < InputImageDimension; d++) + { + if (d != i) + { + NumberOfRows[i] *= size[d]; + } + } + } + float progressPerDimension = 1.0 / ImageDimension; + + ProgressReporter progress(this, + threadId, + NumberOfRows[this->m_CurrentDimension], + 30, + this->m_CurrentDimension * progressPerDimension, + progressPerDimension); + + // this is where the work happens. We use a distance image with + // floating point pixel to perform the parabolic operations. The + // input image specifies the size of the SE at each location. These + // values need to be squared on the way in to the parabolic + // operations, and the squaring step needs to be integrated with the + // line copy. + // Similarly, the thresholding on output needs to be integrated + // with the last processing stage. + + using InputConstIteratorType = ImageLinearConstIteratorWithIndex; + using OutputIteratorType = ImageLinearIteratorWithIndex; + + using InputDistIteratorType = ImageLinearConstIteratorWithIndex; + using OutputDistIteratorType = ImageLinearIteratorWithIndex; + + // for stages after the first + // using OutputConstIteratorType = ImageLinearConstIteratorWithIndex< TOutputImage > ; + + using RegionType = ImageRegion; + + typename TInputImage::ConstPointer inputImage(this->GetInput()); + typename TOutputImage::Pointer outputImage(this->GetOutput()); + + outputImage->SetBufferedRegion(outputImage->GetRequestedRegion()); + outputImage->Allocate(); + RegionType region = outputRegionForThread; + + InputConstIteratorType inputIterator(inputImage, region); + InputConstIteratorType inputIteratorStage2(outputImage, region); + OutputIteratorType outputIterator(outputImage, region); + // OutputConstIteratorType inputIteratorStage2( outputImage, region ); + + InputDistIteratorType inputDistIterator(this->m_DistanceImage, region); + OutputDistIteratorType outputDistIterator(this->m_DistanceImage, region); + + // setup the progress reporting + // deal with the first dimension - this should be copied to the + // output if the scale is 0 + + // flag to indicate whether the internal distance image has been + // initialized using the special first pass erosion + if (this->m_Scale[this->m_CurrentDimension] > 0) + { + // Perform as normal + // RealType magnitude = 1.0/(2.0 * m_Scale[0]); + unsigned long LineLength = region.GetSize()[this->m_CurrentDimension]; + RealType image_scale = this->GetInput()->GetSpacing()[this->m_CurrentDimension]; + // bool lastpass = (m_CurrentDimension == ImageDimension - 1); + + if (!this->m_FirstPassDone) + { + LabSet:: + doOneDimensionDilateFirstPass( + inputIterator, + outputDistIterator, + outputIterator, + progress, + LineLength, + this->m_CurrentDimension, + this->m_MagnitudeSign, + this->m_UseImageSpacing, + image_scale, + this->m_Scale[this->m_CurrentDimension]); + } + else + { + LabSet::doOneDimensionDilate(inputIteratorStage2, + inputDistIterator, + outputDistIterator, + outputIterator, + progress, + LineLength, + this->m_CurrentDimension, + this->m_MagnitudeSign, + this->m_UseImageSpacing, + this->m_Extreme, + image_scale, + this->m_Scale[this->m_CurrentDimension]); + } + } +} +} // namespace itk +#endif diff --git a/Modules/Filtering/LabelErodeDilate/include/itkLabelSetErodeImageFilter.h b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetErodeImageFilter.h new file mode 100644 index 000000000000..8492409a57b2 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetErodeImageFilter.h @@ -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. + * + *=========================================================================*/ +#ifndef itkLabelSetErodeImageFilter_h +#define itkLabelSetErodeImageFilter_h + +#include "itkLabelSetMorphBaseImageFilter.h" +#include "itkNumericTraits.h" + +namespace itk +{ +/** + * \class LabelSetErodeImageFilter + * \brief Class for binary morphological erosion of label images. + * + * This filter will separate touching labels. If you don't want this + * then use a conventional binary erosion to mask the label image. + * This filter is threaded. + * + * \sa itkLabelSetDilateImageFilter + * + * \ingroup LabelErodeDilate + * + * \author Richard Beare, Department of Medicine, Monash University, + * Australia. + **/ +template +class ITK_EXPORT LabelSetErodeImageFilter : public LabelSetMorphBaseImageFilter +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(LabelSetErodeImageFilter); + + /** Standard class type alias. */ + using Self = LabelSetErodeImageFilter; + using Superclass = LabelSetMorphBaseImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkOverrideGetNameOfClassMacro(LabelSetErodeImageFilter); + + /** Pixel Type of the input image */ + using InputImageType = TInputImage; + using OutputImageType = TOutputImage; + using PixelType = typename TInputImage::PixelType; + using RealType = typename NumericTraits::FloatType; + using OutputPixelType = typename TOutputImage::PixelType; + using ScalarRealType = typename NumericTraits::ScalarRealType; + + /** Smart pointer type alias support. */ + using InputImagePointer = typename TInputImage::Pointer; + using InputImageConstPointer = typename TInputImage::ConstPointer; + using InputSizeType = typename TInputImage::SizeType; + using OutputSizeType = typename TOutputImage::SizeType; + + /** a type to represent the "kernel radius" */ + using RadiusType = typename itk::FixedArray; + /** Image dimension. */ + + using OutputImageRegionType = typename OutputImageType::RegionType; + /** Image dimension. */ + static constexpr unsigned int ImageDimension = TInputImage::ImageDimension; + static constexpr unsigned int OutputImageDimension = TOutputImage::ImageDimension; + static constexpr unsigned int InputImageDimension = TInputImage::ImageDimension; + +protected: + LabelSetErodeImageFilter() { this->DynamicMultiThreadingOff(); } + ~LabelSetErodeImageFilter() override = default; + + void + ThreadedGenerateData(const OutputImageRegionType & outputRegionForThread, ThreadIdType threadId) override; + + // Override since the filter produces the entire dataset. +private: + using DistanceImageType = typename Superclass::DistanceImageType; +}; +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkLabelSetErodeImageFilter.hxx" +#endif + +#endif diff --git a/Modules/Filtering/LabelErodeDilate/include/itkLabelSetErodeImageFilter.hxx b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetErodeImageFilter.hxx new file mode 100644 index 000000000000..c59934807518 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetErodeImageFilter.hxx @@ -0,0 +1,146 @@ +/*========================================================================= + * + * 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 itkLabelSetErodeImageFilter_hxx +#define itkLabelSetErodeImageFilter_hxx + +#include "itkImageRegionConstIterator.h" +#include "itkImageRegionIterator.h" + +#include "itkImageLinearIteratorWithIndex.h" +#include "itkImageLinearConstIteratorWithIndex.h" + +#include "itkLabelSetUtils.h" + +namespace itk +{ +template +void +LabelSetErodeImageFilter::ThreadedGenerateData( + const OutputImageRegionType & outputRegionForThread, + ThreadIdType threadId) +{ + // this is where the work happens. We use a distance image with + // floating point pixel to perform the parabolic operations. The + // input image specifies the size of the SE at each location. These + // values need to be squared on the way in to the parabolic + // operations, and the squaring step needs to be integrated with the + // line copy. + // Similarly, the thresholding on output needs to be integrated + // with the last processing stage. + + typename std::vector NumberOfRows; + InputSizeType size = outputRegionForThread.GetSize(); + + for (unsigned int i = 0; i < InputImageDimension; i++) + { + NumberOfRows.push_back(1); + for (unsigned int d = 0; d < InputImageDimension; d++) + { + if (d != i) + { + NumberOfRows[i] *= size[d]; + } + } + } + float progressPerDimension = 1.0 / ImageDimension; + + ProgressReporter progress(this, + threadId, + NumberOfRows[this->m_CurrentDimension], + 30, + this->m_CurrentDimension * progressPerDimension, + progressPerDimension); + + using InputConstIteratorType = ImageLinearConstIteratorWithIndex; + using OutputIteratorType = ImageLinearIteratorWithIndex; + + using InputDistIteratorType = ImageLinearConstIteratorWithIndex; + using OutputDistIteratorType = ImageLinearIteratorWithIndex; + + using RegionType = ImageRegion; + + typename TInputImage::ConstPointer inputImage(this->GetInput()); + typename TOutputImage::Pointer outputImage(this->GetOutput()); + + outputImage->SetBufferedRegion(outputImage->GetRequestedRegion()); + outputImage->Allocate(); + RegionType region = outputRegionForThread; + + InputConstIteratorType inputIterator(inputImage, region); + OutputIteratorType outputIterator(outputImage, region); + // OutputConstIteratorType inputIteratorStage2( outputImage, region ); + + InputDistIteratorType inputDistIterator(this->m_DistanceImage, region); + OutputDistIteratorType outputDistIterator(this->m_DistanceImage, region); + + // setup the progress reporting + // deal with the first dimension - this should be copied to the + // output if the scale is 0 + + // flag to indicate whether the internal distance image has been + // initialized using the special first pass erosion + if (this->m_Scale[this->m_CurrentDimension] > 0) + { + // Perform as normal + // RealType magnitude = 1.0/(2.0 * m_Scale[0]); + unsigned long LineLength = region.GetSize()[this->m_CurrentDimension]; + RealType image_scale = this->GetInput()->GetSpacing()[this->m_CurrentDimension]; + bool lastpass = (this->m_CurrentDimension == ImageDimension - 1); + + if (!this->m_FirstPassDone) + { + LabSet:: + doOneDimensionErodeFirstPass( + inputIterator, + outputDistIterator, + outputIterator, + progress, + LineLength, + this->m_CurrentDimension, + this->m_MagnitudeSign, + this->m_UseImageSpacing, + image_scale, + this->m_Scale[this->m_CurrentDimension], + lastpass); + } + else + { + // do a standard erosion + LabSet::doOneDimensionErode(inputIterator, + inputDistIterator, + outputDistIterator, + outputIterator, + progress, + LineLength, + this->m_CurrentDimension, + this->m_MagnitudeSign, + this->m_UseImageSpacing, + this->m_Extreme, + image_scale, + this->m_Scale[this->m_CurrentDimension], + this->m_BaseSigma, + lastpass); + } + } +} +} // namespace itk +#endif diff --git a/Modules/Filtering/LabelErodeDilate/include/itkLabelSetMorphBaseImageFilter.h b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetMorphBaseImageFilter.h new file mode 100644 index 000000000000..eb5b5fed5b0b --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetMorphBaseImageFilter.h @@ -0,0 +1,155 @@ +/*========================================================================= + * + * 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 itkLabelSetMorphBaseImageFilter_h +#define itkLabelSetMorphBaseImageFilter_h + +#include "itkNumericTraits.h" +#include "itkImageToImageFilter.h" + +namespace itk +{ +#if ITK_VERSION_MAJOR < 4 +using ThreadIdType = int; +using RegionIndexType = int; +#else +using RegionIndexType = unsigned int; +#endif +/** + * \class LabelSetMorphBaseImageFilter + * \brief Base class for binary morphological erosion of label images. + * + * This filter is threaded. This class handles the threading for subclasses. + * + * \sa itkLabelSetDilateImageFilter itkLabelSetErodeImageFilter + * + * \ingroup LabelErodeDilate + * + * \author Richard Beare, Department of Medicine, Monash University, + * Australia. + **/ +template +class ITK_EXPORT LabelSetMorphBaseImageFilter : public ImageToImageFilter +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(LabelSetMorphBaseImageFilter); + + /** Standard class type alias. */ + using Self = LabelSetMorphBaseImageFilter; + using Superclass = ImageToImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkOverrideGetNameOfClassMacro(LabelSetMorphBaseImageFilter); + + /** Pixel Type of the input image */ + using InputImageType = TInputImage; + using OutputImageType = TOutputImage; + using PixelType = typename TInputImage::PixelType; + using RealType = typename NumericTraits::FloatType; + using OutputPixelType = typename TOutputImage::PixelType; + using ScalarRealType = typename NumericTraits::ScalarRealType; + + using OutputIndexType = typename OutputImageType::IndexType; + using OutputIndexValueType = typename OutputImageType::IndexValueType; + + /** Smart pointer type alias support. */ + using InputImagePointer = typename TInputImage::Pointer; + using InputImageConstPointer = typename TInputImage::ConstPointer; + using InputSizeType = typename TInputImage::SizeType; + using OutputSizeType = typename TOutputImage::SizeType; + + /** a type to represent the "kernel radius" */ + using RadiusType = typename itk::FixedArray; + /** Image dimension. */ + + using OutputImageRegionType = typename OutputImageType::RegionType; + + // set all of the scales the same + void + SetRadius(ScalarRealType scale); + + itkSetMacro(Radius, RadiusType); + itkGetConstReferenceMacro(Radius, RadiusType); + + /** + * Set/Get whether the scale refers to pixels or world units - + * default is false + */ + itkSetMacro(UseImageSpacing, bool); + itkGetConstReferenceMacro(UseImageSpacing, bool); + itkBooleanMacro(UseImageSpacing); + + /** Image dimension. */ + static constexpr unsigned int ImageDimension = TInputImage::ImageDimension; + static constexpr unsigned int OutputImageDimension = TOutputImage::ImageDimension; + static constexpr unsigned int InputImageDimension = TInputImage::ImageDimension; + + /** Define the image type for internal computations + RealType is usually 'double' in NumericTraits. + Here we prefer float in order to save memory. */ + + void + writeDist(std::string fname); + +protected: + LabelSetMorphBaseImageFilter(); + ~LabelSetMorphBaseImageFilter() override = default; + + RegionIndexType + SplitRequestedRegion(RegionIndexType i, RegionIndexType num, OutputImageRegionType & splitRegion) override; + + void + ThreadedGenerateData(const OutputImageRegionType & outputRegionForThread, ThreadIdType threadId) override; + + void + GenerateData() override; + + // Override since the filter produces the entire dataset. + void + EnlargeOutputRequestedRegion(DataObject * output) override; + + bool m_UseImageSpacing; + void + PrintSelf(std::ostream & os, Indent indent) const override; + + RadiusType m_Radius; + RadiusType m_Scale; + using DistanceImageType = typename itk::Image; + RealType m_Extreme; + + typename DistanceImageType::Pointer m_DistanceImage; + + int m_MagnitudeSign; + int m_CurrentDimension; + bool m_FirstPassDone; + + // this is the first non-zero entry in the radius. Needed to + // support elliptical operations + RealType m_BaseSigma; +}; +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkLabelSetMorphBaseImageFilter.hxx" +#endif + +#endif diff --git a/Modules/Filtering/LabelErodeDilate/include/itkLabelSetMorphBaseImageFilter.hxx b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetMorphBaseImageFilter.hxx new file mode 100644 index 000000000000..33a46cb62b5c --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetMorphBaseImageFilter.hxx @@ -0,0 +1,249 @@ +/*========================================================================= + * + * 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 itkLabelSetMorphBaseImageFilter_hxx +#define itkLabelSetMorphBaseImageFilter_hxx + +#include "itkImageRegionConstIterator.h" +#include "itkImageRegionIterator.h" + +#include "itkImageLinearIteratorWithIndex.h" +#include "itkImageLinearConstIteratorWithIndex.h" + +#include "itkLabelSetUtils.h" +#include "itkImageFileWriter.h" + +namespace itk +{ +template +LabelSetMorphBaseImageFilter::LabelSetMorphBaseImageFilter() +{ + this->SetNumberOfRequiredOutputs(1); + this->SetNumberOfRequiredInputs(1); + // needs to be selected according to erosion/dilation + + m_DistanceImage = DistanceImageType::New(); + + if (doDilate) + { + m_Extreme = NumericTraits::NonpositiveMin(); + m_MagnitudeSign = 1; + } + else + { + m_Extreme = NumericTraits::max(); + m_MagnitudeSign = -1; + } + m_UseImageSpacing = false; + + this->m_Radius.Fill(1); + + this->DynamicMultiThreadingOff(); +} + +template +void +LabelSetMorphBaseImageFilter::ThreadedGenerateData(const OutputImageRegionType &, + ThreadIdType) +{} + +template +RegionIndexType +LabelSetMorphBaseImageFilter::SplitRequestedRegion( + RegionIndexType i, + RegionIndexType num, + OutputImageRegionType & splitRegion) +{ + // Get the output pointer + OutputImageType * outputPtr = this->GetOutput(); + + // Initialize the splitRegion to the output requested region + splitRegion = outputPtr->GetRequestedRegion(); + + const OutputSizeType & requestedRegionSize = splitRegion.GetSize(); + + OutputIndexType splitIndex = splitRegion.GetIndex(); + OutputSizeType splitSize = splitRegion.GetSize(); + + // split on the outermost dimension available + // and avoid the current dimension + int splitAxis = static_cast(outputPtr->GetImageDimension()) - 1; + while ((requestedRegionSize[splitAxis] == 1) || (splitAxis == static_cast(m_CurrentDimension))) + { + --splitAxis; + if (splitAxis < 0) + { // cannot split + itkDebugMacro("Cannot Split"); + return 1; + } + } + + // determine the actual number of pieces that will be generated + auto range = static_cast(requestedRegionSize[splitAxis]); + + auto valuesPerThread = static_cast(std::ceil(range / static_cast(num))); + unsigned int maxThreadIdUsed = static_cast(std::ceil(range / static_cast(valuesPerThread))) - 1; + + // Split the region + if (i < maxThreadIdUsed) + { + splitIndex[splitAxis] += i * valuesPerThread; + splitSize[splitAxis] = valuesPerThread; + } + if (i == maxThreadIdUsed) + { + splitIndex[splitAxis] += i * valuesPerThread; + // last thread needs to process the "rest" dimension being split + splitSize[splitAxis] = splitSize[splitAxis] - i * valuesPerThread; + } + + // set the split region ivars + splitRegion.SetIndex(splitIndex); + splitRegion.SetSize(splitSize); + + itkDebugMacro("Split Piece: " << splitRegion); + + return maxThreadIdUsed + 1; +} + +template +void +LabelSetMorphBaseImageFilter::SetRadius(ScalarRealType radius) +{ + RadiusType s; + + s.Fill(radius); + this->SetRadius(s); +} + +template +void +LabelSetMorphBaseImageFilter::EnlargeOutputRequestedRegion(DataObject * output) +{ + auto * out = dynamic_cast(output); + + if (out) + { + out->SetRequestedRegion(out->GetLargestPossibleRegion()); + } +} + +template +void +LabelSetMorphBaseImageFilter::GenerateData() +{ + ThreadIdType nbthreads = this->GetNumberOfWorkUnits(); + + typename TInputImage::ConstPointer inputImage(this->GetInput()); + typename TOutputImage::Pointer outputImage(this->GetOutput()); + + this->AllocateOutputs(); + + m_DistanceImage->SetBufferedRegion(outputImage->GetRequestedRegion()); + m_DistanceImage->Allocate(); + m_DistanceImage->FillBuffer(0); + m_DistanceImage->CopyInformation(inputImage); + + if (this->GetUseImageSpacing()) + { + // radius is in mm + for (unsigned P = 0; P < InputImageType::ImageDimension; P++) + { + m_Scale[P] = 0.5 * m_Radius[P] * m_Radius[P]; + } + } + else + { + // this gives us a little bit of a margin + for (unsigned P = 0; P < InputImageType::ImageDimension; P++) + { + m_Scale[P] = (0.5 * m_Radius[P] * m_Radius[P] + 1); + } + } + + // set up the scaling parameter + // first non zero element of m_Scale sets the value used the first + // active pass over the image. + // Subsequent non zero values are scaled by the first non zero + // value to support elliptical operations. + // The first value needs to be recorded for use by the erosion operation. + unsigned firstval = 0; + for (unsigned P = 0; P < InputImageType::ImageDimension; P++) + { + if (m_Radius[P] != 0) + { + firstval = P; + break; + } + } + m_BaseSigma = m_Scale[firstval]; + for (unsigned P = firstval + 1; P < InputImageType::ImageDimension; P++) + { + m_Scale[P] = m_Scale[P] / m_Scale[firstval]; + } + + m_FirstPassDone = false; + + // Set up the multithreaded processing + typename ImageSource::ThreadStruct str; + str.Filter = this; + MultiThreaderBase * multithreader = this->GetMultiThreader(); + multithreader->SetNumberOfWorkUnits(nbthreads); + multithreader->SetSingleMethod(this->ThreaderCallback, &str); + + // multithread the execution + for (unsigned int d = 0; d < ImageDimension; d++) + { + m_CurrentDimension = d; + multithreader->SingleMethodExecute(); + if (this->m_Scale[m_CurrentDimension] > 0) + { + // needs to be set outside the multithreaded code + // first pass is completed as soon as we hit a structuring + // element dimension that is non zero. + m_FirstPassDone = true; + } + } +} + +template +void +LabelSetMorphBaseImageFilter::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); + if (m_UseImageSpacing) + { + os << "Scale in world units: " << m_Radius << std::endl; + } + else + { + os << "Scale in voxels: " << m_Radius << std::endl; + } +} + +template +void +LabelSetMorphBaseImageFilter::writeDist(std::string fname) +{ + using WriterType = typename itk::ImageFileWriter; + typename WriterType::Pointer writer = WriterType::New(); + writer->SetInput(m_DistanceImage); + writer->SetFileName(fname.c_str()); + writer->Update(); +} +} // namespace itk +#endif diff --git a/Modules/Filtering/LabelErodeDilate/include/itkLabelSetUtils.h b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetUtils.h new file mode 100644 index 000000000000..e214a0fe33f4 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/include/itkLabelSetUtils.h @@ -0,0 +1,681 @@ +/*========================================================================= + * + * 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 itkLabelSetUtils_h +#define itkLabelSetUtils_h + +#include + +#include "itkProgressReporter.h" +#include +namespace itk +{ +namespace LabSet +{ +template +void +DoLineErodeFirstPass(LineBufferType & lineBuf, + RealType leftend, + RealType rightend, + const RealType magnitude, + const RealType sigma) +{ + // This is the first pass algorithm. We can write down the values + // because we know the inputs are binary + + const long lineLength = lineBuf.size(); + + for (long pos = 0; pos < lineLength; pos++) + { + // compute the height of the parabola starting at each end and + // keep the minimum + RealType left, right; + unsigned offset = lineLength - pos; + left = leftend - magnitude * (pos + 1) * (pos + 1); + right = rightend - magnitude * offset * offset; + // note hard coded value here - could be a parameter + // lineBuf[pos] = std::min(std::min(left, right), + // itk::NumericTraits::One); + lineBuf[pos] = std::min(std::min(left, right), sigma); + } +} + +template +void +DoLineDilateFirstPass(LineBufferType & lineBuf, + LineBufferType & tmpLineBuf, + LabLineBufferType & labBuf, + LabLineBufferType & NewLabBuf, + const RealType magnitude) +{ + // need to propagate the labels here + const long lineLength = lineBuf.size(); + long lastcontact = 0; + RealType lastval = lineBuf[0]; + + for (long pos = 0; pos < lineLength; pos++) + { + // left pass + RealType krange = pos - lastcontact; + RealType thisval = lastval - magnitude * krange * krange; + + if (lineBuf[pos] >= lineBuf[lastcontact]) + { + lastcontact = pos; + lastval = lineBuf[pos]; + } + tmpLineBuf[pos] = std::max(lineBuf[pos], thisval); + if (thisval > lineBuf[pos]) + { + NewLabBuf[pos] = labBuf[lastcontact]; + } + else + { + NewLabBuf[pos] = labBuf[pos]; + } + } + + lastcontact = lineLength - 1; + lastval = tmpLineBuf[lastcontact]; + for (long pos = lineLength - 1; pos >= 0; pos--) + { + // right pass + RealType krange = lastcontact - pos; + RealType thisval = lastval - magnitude * krange * krange; + + if (tmpLineBuf[pos] >= tmpLineBuf[lastcontact]) + { + lastcontact = pos; + lastval = tmpLineBuf[pos]; + } + lineBuf[pos] = std::max(tmpLineBuf[pos], thisval); + if (thisval > tmpLineBuf[pos]) + { + NewLabBuf[pos] = labBuf[lastcontact]; + } + // only need to do this bit on the first pass - it doubles as a + // way of initializing NewLabPos + // else + // { + // NewLabBuf[pos] = labBuf[pos]; + // } + } +} + +template +void +DoLine(LineBufferType & lineBuf, LineBufferType & tmpLineBuf, const RealType magnitude, const RealType extreme) +{ + // contact point algorithm + long koffset = 0, newcontact = 0; // how far away the search starts. + + const long lineLength = lineBuf.size(); + + // negative half of the parabola + for (long pos = 0; pos < lineLength; pos++) + { + auto baseVal = (RealType)extreme; // the base value for comparison + for (long krange = koffset; krange <= 0; krange++) + { + // difference needs to be paramaterised + RealType T = lineBuf[pos + krange] - magnitude * krange * krange; + // switch on template parameter - hopefully gets optimized away. + if (doDilate ? (T >= baseVal) : (T <= baseVal)) + { + baseVal = T; + newcontact = krange; + } + } + tmpLineBuf[pos] = baseVal; + koffset = newcontact - 1; + } + // positive half of parabola + koffset = newcontact = 0; + for (long pos = lineLength - 1; pos >= 0; pos--) + { + auto baseVal = (RealType)extreme; // the base value for comparison + for (long krange = koffset; krange >= 0; krange--) + { + RealType T = tmpLineBuf[pos + krange] - magnitude * krange * krange; + if (doDilate ? (T >= baseVal) : (T <= baseVal)) + { + baseVal = T; + newcontact = krange; + } + } + lineBuf[pos] = baseVal; + koffset = newcontact + 1; + } +} + +template +void +DoLineLabelProp(LineBufferType & lineBuf, + LineBufferType & tmpLineBuf, + LabBufferType & labelBuf, + LabBufferType & tmpLabelBuf, + const RealType magnitude, + const RealType extreme) +{ + // contact point algorithm + long koffset = 0, newcontact = 0; // how far away the search starts. + + using LabelType = typename LabBufferType::ValueType; + + const long lineLength = lineBuf.size(); + // negative half of the parabola + for (long pos = 0; pos < lineLength; pos++) + { + auto baseVal = (RealType)extreme; // the base value for comparison + LabelType baseLab = labelBuf[pos]; + for (long krange = koffset; krange <= 0; krange++) + { + // difference needs to be paramaterised + RealType T = lineBuf[pos + krange] - magnitude * krange * krange; + // switch on template parameter - hopefully gets optimized away. + if (doDilate ? (T >= baseVal) : (T <= baseVal)) + { + baseVal = T; + newcontact = krange; + baseLab = labelBuf[pos + krange]; + } + } + tmpLineBuf[pos] = baseVal; + tmpLabelBuf[pos] = baseLab; + koffset = newcontact - 1; + } + // positive half of parabola + koffset = newcontact = 0; +#if 1 + for (long pos = lineLength - 1; pos >= 0; pos--) + { + auto baseVal = (RealType)extreme; // the base value for comparison + // initialize the label to the previously pro + LabelType baseLab = tmpLabelBuf[pos]; + for (long krange = koffset; krange >= 0; krange--) + { + RealType T = tmpLineBuf[pos + krange] - magnitude * krange * krange; + if (doDilate ? (T >= baseVal) : (T <= baseVal)) + { + baseVal = T; + newcontact = krange; + baseLab = tmpLabelBuf[pos + krange]; + } + } + lineBuf[pos] = baseVal; + labelBuf[pos] = baseLab; + koffset = newcontact + 1; + } +#else + for (long pos = lineLength - 1; pos >= 0; pos--) + { + lineBuf[pos] = tmpLineBuf[pos]; + labelBuf[pos] = tmpLabelBuf[pos]; + } + +#endif +} + +template +void +doOneDimensionErodeFirstPass(TInIter & inputIterator, + TOutDistIter & outputIterator, + TOutLabIter & outputLabIterator, + ProgressReporter & progress, + const unsigned lineLength, + const unsigned direction, + const int magnitudeSign, + const bool useImageSpacing, + const RealType imageScale, + const RealType sigma, + const bool lastpass) +{ + // specialised version for binary erosion during first pass. We can + // compute the results directly because the inputs are flat. + using LineBufferType = typename itk::Array; + using LabelBufferType = typename itk::Array; + RealType iscale = 1.0; + if (useImageSpacing) + { + iscale = imageScale; + } + // restructure equation to reduce numerical error + // const RealType magnitude = (magnitudeSign * iscale * iscale)/(2.0 * + // sigma); + const RealType magnitude = (magnitudeSign * iscale * iscale) / (2.0); + LineBufferType lineBuf(lineLength, 0.0); + LabelBufferType labBuf(lineLength, 0.0); + + inputIterator.SetDirection(direction); + outputIterator.SetDirection(direction); + outputLabIterator.SetDirection(direction); + + inputIterator.GoToBegin(); + outputIterator.GoToBegin(); + outputLabIterator.GoToBegin(); + + while (!inputIterator.IsAtEnd() && !outputIterator.IsAtEnd()) + { + // process this direction + // fetch the line into the buffer - this methodology is like + // the gaussian filters + unsigned int i = 0; + + // copy the scanline to a buffer + while (!inputIterator.IsAtEndOfLine()) + { + labBuf[i] = (inputIterator.Get()); + if (labBuf[i]) + { + lineBuf[i] = 1.0; + } + ++i; + ++inputIterator; + } + // runlength encode the line buffer (could be integrated with extraction) + + using EndType = std::vector; + EndType firsts; + EndType lasts; + + for (unsigned idx = 0; idx < lineLength; idx++) + { + RealType val = labBuf[idx]; + if (val != 0) + { + // found a run + firsts.push_back(idx); + unsigned idxend = idx; + for (; idxend < lineLength; idxend++) + { + if (val != labBuf[idxend]) + { + break; + } + } + lasts.push_back(idxend - 1); + idx = idxend - 1; + } + } + + for (unsigned R = 0; R < firsts.size(); R++) + { + unsigned first = firsts[R]; + unsigned last = lasts[R]; + unsigned SLL = last - first + 1; + LineBufferType ShortLineBuf(SLL); + // if one end of the run touches the image edge, then we leave + // the value as 1 + RealType leftend = 0, rightend = 0; + if (first == 0) + { + leftend = sigma; + } + if (last == lineLength - 1) + { + rightend = sigma; + } + + DoLineErodeFirstPass(ShortLineBuf, leftend, rightend, magnitude, sigma); + // copy the segment back into the full line buffer + std::copy(ShortLineBuf.begin(), ShortLineBuf.end(), &(lineBuf[first])); + } + // copy the line buffer back to the image + unsigned j = 0; + while (!outputIterator.IsAtEndOfLine()) + { + outputIterator.Set(static_cast(lineBuf[j++])); + ++outputIterator; + } + + if (lastpass) + { + // copy to the output image - this would be a weird case of only + // using a one dimensional SE + unsigned j2 = 0; + while (!outputLabIterator.IsAtEndOfLine()) + { + typename TInIter::PixelType val = 0; + if (lineBuf[j2] == sigma) + { + val = labBuf[j2]; + } + outputLabIterator.Set(val); + ++outputLabIterator; + ++j2; + } + outputLabIterator.NextLine(); + } + + // now onto the next line + inputIterator.NextLine(); + outputIterator.NextLine(); + progress.CompletedPixel(); + } +} + +template +void +doOneDimensionDilateFirstPass(TInIter & inputIterator, + TOutDistIter & outputIterator, + TOutLabIter & outputLabIterator, + ProgressReporter & progress, + const unsigned lineLength, + const unsigned direction, + const int magnitudeSign, + const bool useImageSpacing, + const RealType imageScale, + const RealType sigma) +{ + // specialised version for binary erosion during first pass. We can + // compute the results directly because the inputs are flat. + using LineBufferType = typename itk::Array; + using LabelBufferType = typename itk::Array; + RealType iscale = 1.0; + if (useImageSpacing) + { + iscale = imageScale; + } + // restructure equation to reduce numerical error + // const RealType magnitude = (magnitudeSign * iscale * iscale)/(2.0 * + // sigma); + const RealType magnitude = (magnitudeSign * iscale * iscale) / (2.0); + const RealType initRealValue = 0.0; + const typename TInIter::PixelType initPixelValue = 0.0; + LineBufferType lineBuf(lineLength, initRealValue); + LabelBufferType labBuf(lineLength, initPixelValue); + LineBufferType tmpLineBuf(lineLength, initRealValue); + LabelBufferType newLabBuf(lineLength, initPixelValue); + + inputIterator.SetDirection(direction); + outputIterator.SetDirection(direction); + outputLabIterator.SetDirection(direction); + + inputIterator.GoToBegin(); + outputIterator.GoToBegin(); + outputLabIterator.GoToBegin(); + + while (!inputIterator.IsAtEnd() && !outputIterator.IsAtEnd()) + { + // process this direction + // fetch the line into the buffer - this methodology is like + // the gaussian filters + unsigned int i = 0; + + // copy the scanline to a buffer + while (!inputIterator.IsAtEndOfLine()) + { + labBuf[i] = (inputIterator.Get()); + if (labBuf[i]) + { + lineBuf[i] = sigma; + } + else + { + lineBuf[i] = 0; + } + ++i; + ++inputIterator; + } + + DoLineDilateFirstPass(lineBuf, tmpLineBuf, labBuf, newLabBuf, magnitude); + // copy the line buffer back to the image + unsigned j = 0; + while (!outputIterator.IsAtEndOfLine()) + { + outputIterator.Set(static_cast(lineBuf[j])); + outputLabIterator.Set(newLabBuf[j]); + ++outputLabIterator; + ++outputIterator; + ++j; + } + + // now onto the next line + inputIterator.NextLine(); + outputIterator.NextLine(); + outputLabIterator.NextLine(); + progress.CompletedPixel(); + } +} + +template +void +doOneDimensionErode(TInIter & inputIterator, + TDistIter & inputDistIterator, + TOutDistIter & outputDistIterator, + TOutLabIter & outputLabIterator, + ProgressReporter & progress, + const unsigned lineLength, + const unsigned direction, + const int magnitudeSign, + const bool useImageSpacing, + const RealType extreme, + const RealType imageScale, + const RealType sigma, + const RealType BaseSigma, + const bool lastpass) +{ + // traditional erosion - can't optimise the same way as the first pass + using LineBufferType = typename itk::Array; + using LabelBufferType = typename itk::Array; + RealType iscale = 1.0; + if (useImageSpacing) + { + iscale = imageScale; + } + const RealType magnitude = (magnitudeSign * iscale * iscale) / (2.0 * sigma); + LineBufferType lineBuf(lineLength, 0.0); + LabelBufferType labBuf(lineLength, 0.0); + + inputIterator.SetDirection(direction); + outputDistIterator.SetDirection(direction); + inputDistIterator.SetDirection(direction); + outputLabIterator.SetDirection(direction); + + inputIterator.GoToBegin(); + outputDistIterator.GoToBegin(); + inputDistIterator.GoToBegin(); + outputLabIterator.GoToBegin(); + + while (!inputIterator.IsAtEnd() && !outputDistIterator.IsAtEnd()) + { + // process this direction + // fetch the line into the buffer - this methodology is like + // the gaussian filters + unsigned int i = 0; + + // copy the scanline to a buffer + while (!inputIterator.IsAtEndOfLine()) + { + lineBuf[i] = static_cast(inputDistIterator.Get()); + labBuf[i] = inputIterator.Get(); + ++i; + ++inputDistIterator; + ++inputIterator; + } + // runlength encode the line buffer (could be integrated with extraction) + using EndType = std::vector; + EndType firsts; + EndType lasts; + for (unsigned idx = 0; idx < lineLength; idx++) + { + RealType val = labBuf[idx]; + if (val != 0) + { + // found a run + firsts.push_back(idx); + unsigned idxend = idx; + for (; idxend < lineLength; idxend++) + { + if (val != labBuf[idxend]) + { + break; + } + } + lasts.push_back(idxend - 1); + idx = idxend - 1; + } + } + + for (unsigned R = 0; R < firsts.size(); R++) + { + unsigned first = firsts[R]; + unsigned last = lasts[R]; + unsigned SLL = last - first + 1; + LineBufferType ShortLineBuf(SLL + 2); + LineBufferType tmpShortLineBuf(SLL + 2); + + // if one end of the run touches the image edge, then we leave + // the value as 1 + RealType leftend = 0, rightend = 0; + if (first == 0) + { + leftend = BaseSigma; + } + if (last == lineLength - 1) + { + rightend = BaseSigma; + } + + ShortLineBuf[0] = leftend; + ShortLineBuf[SLL + 1] = rightend; + + std::copy(&(lineBuf[first]), &(lineBuf[last + 1]), &(ShortLineBuf[1])); + + DoLine(ShortLineBuf, tmpShortLineBuf, magnitude, extreme); + // copy the segment back into the full line buffer + std::copy(&(ShortLineBuf[1]), &(ShortLineBuf[SLL + 1]), &(lineBuf[first])); + } + // copy the line buffer back to the image - don't need to do it on + // the last pass - move when we are sure it is working + unsigned j = 0; + while (!outputDistIterator.IsAtEndOfLine()) + { + outputDistIterator.Set(static_cast(lineBuf[j++])); + ++outputDistIterator; + } + + if (lastpass) + { + unsigned j2 = 0; + while (!outputLabIterator.IsAtEndOfLine()) + { + typename TInIter::PixelType val = 0; + if (lineBuf[j2] == BaseSigma) + { + val = labBuf[j2]; + } + outputLabIterator.Set(val); + ++outputLabIterator; + ++j2; + } + outputLabIterator.NextLine(); + } + // now onto the next line + inputIterator.NextLine(); + inputDistIterator.NextLine(); + outputDistIterator.NextLine(); + progress.CompletedPixel(); + } +} + +template +void +doOneDimensionDilate(TInIter & inputIterator, + TDistIter & inputDistIterator, + TOutDistIter & outputDistIterator, + TOutLabIter & outputLabIterator, + ProgressReporter & progress, + const unsigned lineLength, + const unsigned direction, + const int magnitudeSign, + const bool useImageSpacing, + const RealType extreme, + const RealType imageScale, + const RealType sigma) +{ + // specialised version for binary erosion during first pass. We can + // compute the results directly because the inputs are flat. + using LineBufferType = typename itk::Array; + using LabelBufferType = typename itk::Array; + RealType iscale = 1.0; + if (useImageSpacing) + { + iscale = imageScale; + } + // restructure equation to reduce numerical error + const RealType magnitude = (magnitudeSign * iscale * iscale) / (2.0 * sigma); + // const RealType magnitude = (magnitudeSign * iscale * iscale)/(2.0 ); + const RealType initRealValue = 0.0; + const typename TInIter::PixelType initPixelValue = 0.0; + LineBufferType lineBuf(lineLength, initRealValue); + LabelBufferType labBuf(lineLength, initPixelValue); + LineBufferType tmpLineBuf(lineLength, initRealValue); + LabelBufferType newLabBuf(lineLength, initPixelValue); + LabelBufferType tmpLabBuf(lineLength, initPixelValue); + + inputIterator.SetDirection(direction); + inputDistIterator.SetDirection(direction); + outputDistIterator.SetDirection(direction); + outputLabIterator.SetDirection(direction); + + inputIterator.GoToBegin(); + inputDistIterator.GoToBegin(); + outputDistIterator.GoToBegin(); + outputLabIterator.GoToBegin(); + + while (!inputDistIterator.IsAtEnd() && !outputLabIterator.IsAtEnd()) + { + // process this direction + // fetch the line into the buffer - this methodology is like + // the gaussian filters + unsigned int i = 0; + + // copy the scanline to a buffer + while (!inputDistIterator.IsAtEndOfLine()) + { + lineBuf[i] = inputDistIterator.Get(); + labBuf[i] = inputIterator.Get(); + ++i; + ++inputIterator; + ++inputDistIterator; + } + + DoLineLabelProp( + lineBuf, tmpLineBuf, labBuf, tmpLabBuf, magnitude, extreme); + // copy the line buffer back to the image + unsigned j = 0; + while (!outputDistIterator.IsAtEndOfLine()) + { + outputDistIterator.Set(static_cast(lineBuf[j])); + outputLabIterator.Set(labBuf[j]); + ++outputDistIterator; + ++outputLabIterator; + j++; + } + + // now onto the next line + inputIterator.NextLine(); + outputLabIterator.NextLine(); + inputDistIterator.NextLine(); + outputDistIterator.NextLine(); + progress.CompletedPixel(); + } +} +} // namespace LabSet +} // namespace itk +#endif diff --git a/Modules/Filtering/LabelErodeDilate/include/itkinstance.h b/Modules/Filtering/LabelErodeDilate/include/itkinstance.h new file mode 100644 index 000000000000..105531363784 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/include/itkinstance.h @@ -0,0 +1,35 @@ +/*========================================================================= + * + * 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 itkinstance_h +#define itkinstance_h +// Aidan's trick +#include +namespace itk +{ +template +class Instance : public T::Pointer +{ +public: + Instance() + : SmartPointer(T::New()) + {} +}; +} // namespace itk + +#endif diff --git a/Modules/Filtering/LabelErodeDilate/itk-module.cmake b/Modules/Filtering/LabelErodeDilate/itk-module.cmake new file mode 100644 index 000000000000..618891acbdd2 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/itk-module.cmake @@ -0,0 +1,19 @@ +set( + DOCUMENTATION + "Morphological erosion and dilation for label images. Label collisions are +handled consistently and operations execute in approximately constant time +with respect to structuring-element size. Only circular, spherical, and +hyperspherical structuring elements are supported." +) + +itk_module( + LabelErodeDilate + DEPENDS + ITKIOImageBase + TEST_DEPENDS + ITKImageGrid + ITKTestKernel + ITKSmoothing + EXCLUDE_FROM_DEFAULT + DESCRIPTION "${DOCUMENTATION}" +) diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/axialdilate5.png.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/axialdilate5.png.cid new file mode 100644 index 000000000000..7983fe8b000a --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/axialdilate5.png.cid @@ -0,0 +1 @@ +bafkreignohgnupjs5so6tgy5upo3n2iew3i3xwb5mzfhqp74h2gtdzib2q diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/axialerode3.png.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/axialerode3.png.cid new file mode 100644 index 000000000000..909c7c54fe54 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/axialerode3.png.cid @@ -0,0 +1 @@ +bafkreihutkwlxnie2aapzz3s2ysie74z7rszkhqt3znrw5uidvo56sxqta diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/cortdilate_5.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/cortdilate_5.nii.gz.cid new file mode 100644 index 000000000000..d4ad435b163e --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/cortdilate_5.nii.gz.cid @@ -0,0 +1 @@ +bafkreidx5qgnfstu7teg6zcpcswhltdi3d6oe64i4xojbyevcrkbcqje5a diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/corterode_3.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/corterode_3.nii.gz.cid new file mode 100644 index 000000000000..9de305fe5682 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/corterode_3.nii.gz.cid @@ -0,0 +1 @@ +bafkreib4xillmbq46xyekntmegrixhggvhbh7ko5nvyzbnbrgfpvhqaprq diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/dotdilate_41.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/dotdilate_41.nii.gz.cid new file mode 100644 index 000000000000..a5fd30ec990c --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/dotdilate_41.nii.gz.cid @@ -0,0 +1 @@ +bafkreibstbqwgyaqq564m42fn4lz2d2idanccbgme7y5zu7lm7aekmsxme diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/holeerode_41.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/holeerode_41.nii.gz.cid new file mode 100644 index 000000000000..a728614d8d09 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/holeerode_41.nii.gz.cid @@ -0,0 +1 @@ +bafkreigpzzhdxcwf4h6lqd7g5kbashk5aocrznygd7yh73yf5gbzpnyp7u diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate2d.png.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate2d.png.cid new file mode 100644 index 000000000000..8570dedebfc8 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate2d.png.cid @@ -0,0 +1 @@ +bafkreih4wdtayxyd4eidg4elaitxly52aav77jieynvqxgcqtxvvv7a7ue diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d.nii.gz.cid new file mode 100644 index 000000000000..d4ad435b163e --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d.nii.gz.cid @@ -0,0 +1 @@ +bafkreidx5qgnfstu7teg6zcpcswhltdi3d6oe64i4xojbyevcrkbcqje5a diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d_dez.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d_dez.nii.gz.cid new file mode 100644 index 000000000000..c8b315e29c06 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d_dez.nii.gz.cid @@ -0,0 +1 @@ +bafkreicdjwvwnesgl4debf4az55l53bioupi2opuyqcnxm4rv7u6lwcira diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d_ell.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d_ell.nii.gz.cid new file mode 100644 index 000000000000..bc59737f4d2c --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/labdilate3d_ell.nii.gz.cid @@ -0,0 +1 @@ +bafkreihdcjzlzajfqzxjorxumyv7i4w63xve356jlya5pae3s4a4akgzhy diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode2d.png.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode2d.png.cid new file mode 100644 index 000000000000..710119f001c9 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode2d.png.cid @@ -0,0 +1 @@ +bafkreihc2acgtjlasa5hzs5u4uh3gyzxy64fdhq4vewq3mzx23s656gute diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode3d.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode3d.nii.gz.cid new file mode 100644 index 000000000000..5e84b8e422dc --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode3d.nii.gz.cid @@ -0,0 +1 @@ +bafkreibw5okurput5fxz637uev44dwc6o6jowlo3t6epqlarqjtaotucpm diff --git a/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode3d_ell.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode3d_ell.nii.gz.cid new file mode 100644 index 000000000000..af0e70a5ba50 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Baseline/laberode3d_ell.nii.gz.cid @@ -0,0 +1 @@ +bafkreihz4dy3rid4m52hncbw3ltrb7adhz6hkyeiu6cyvtspondubzznfq diff --git a/Modules/Filtering/LabelErodeDilate/test/CMakeLists.txt b/Modules/Filtering/LabelErodeDilate/test/CMakeLists.txt new file mode 100644 index 000000000000..41dfb34841f7 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/CMakeLists.txt @@ -0,0 +1,87 @@ +itk_module_test() + +set( + LabelErodeDilateTests + itkLabelSetDilateTest.cxx + itkLabelSetErodeTest.cxx +) + +createtestdriver(LabelErodeDilate "${LabelErodeDilate-Test_LIBRARIES}" "${LabelErodeDilateTests}") + +itk_add_test( + NAME itkLabelDilateTest2D_5 + COMMAND + LabelErodeDilateTestDriver + --compare + DATA{Baseline/axialdilate5.png} + ${ITK_TEST_OUTPUT_DIR}/axialdilate5.png + itkLabelSetDilateTest + DATA{Input/axial.png} + 5 + ${ITK_TEST_OUTPUT_DIR}/axialdilate5.png +) + +itk_add_test( + NAME itkLabelDilateTest3D_5 + COMMAND + LabelErodeDilateTestDriver + --compare + DATA{Baseline/cortdilate_5.nii.gz} + ${ITK_TEST_OUTPUT_DIR}/cortdilate_5.nii.gz + itkLabelSetDilateTest + DATA{Input/HarvardOxford-cort-maxprob-thr50-1mm.nii.gz} + 5 + ${ITK_TEST_OUTPUT_DIR}/cortdilate_5.nii.gz +) + +itk_add_test( + NAME itkLabelDilateTest3D_big + COMMAND + LabelErodeDilateTestDriver + --compare + DATA{Baseline/dotdilate_41.nii.gz} + ${ITK_TEST_OUTPUT_DIR}/dotdilate_41.nii.gz + itkLabelSetDilateTest + DATA{Input/dot.nii.gz} + 41 + ${ITK_TEST_OUTPUT_DIR}/dotdilate_41.nii.gz +) + +itk_add_test( + NAME itkLabelErodeTest2D_3 + COMMAND + LabelErodeDilateTestDriver + --compare + DATA{Baseline/axialerode3.png} + ${ITK_TEST_OUTPUT_DIR}/axialerode3.png + itkLabelSetErodeTest + DATA{Input/axial.png} + 3 + ${ITK_TEST_OUTPUT_DIR}/axialerode3.png +) + +itk_add_test( + NAME itkLabelErodeTest3D_3 + COMMAND + LabelErodeDilateTestDriver + --compare + DATA{Baseline/corterode_3.nii.gz} + ${ITK_TEST_OUTPUT_DIR}/corterode_3.nii.gz + itkLabelSetErodeTest + DATA{Input/HarvardOxford-cort-maxprob-thr50-1mm.nii.gz} + 3 + ${ITK_TEST_OUTPUT_DIR}/corterode_3.nii.gz +) + +itk_add_test( + NAME itkLabelErodeTest3D_big + COMMAND + LabelErodeDilateTestDriver + --compare + DATA{Baseline/holeerode_41.nii.gz} + ${ITK_TEST_OUTPUT_DIR}/holeerode_41.nii.gz + itkLabelSetErodeTest + DATA{Input/hole.nii.gz} + 41 + ${ITK_TEST_OUTPUT_DIR}/holeerode_41.nii.gz +) diff --git a/Modules/Filtering/LabelErodeDilate/test/Input/HarvardOxford-cort-maxprob-thr50-1mm.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Input/HarvardOxford-cort-maxprob-thr50-1mm.nii.gz.cid new file mode 100644 index 000000000000..d6f63eb31a2a --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Input/HarvardOxford-cort-maxprob-thr50-1mm.nii.gz.cid @@ -0,0 +1 @@ +bafkreibvmnnjqoxw32pn6grevl43jo5jn45e3pnnovxjt4cygjccj5bm5u diff --git a/Modules/Filtering/LabelErodeDilate/test/Input/axial.png.cid b/Modules/Filtering/LabelErodeDilate/test/Input/axial.png.cid new file mode 100644 index 000000000000..bd0253ed50db --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Input/axial.png.cid @@ -0,0 +1 @@ +bafkreidin7opdyo7vwldry6q4jxpsiglj4a6cukaojvhw5vten3njmp6t4 diff --git a/Modules/Filtering/LabelErodeDilate/test/Input/dot.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Input/dot.nii.gz.cid new file mode 100644 index 000000000000..6263c79899ae --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Input/dot.nii.gz.cid @@ -0,0 +1 @@ +bafkreiexf76zzkideuqez6nryen2ucat3i3yb7a53i7ydtrhyqmxnxx7zi diff --git a/Modules/Filtering/LabelErodeDilate/test/Input/hole.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Input/hole.nii.gz.cid new file mode 100644 index 000000000000..446ed5632f77 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Input/hole.nii.gz.cid @@ -0,0 +1 @@ +bafkreidr4hcmonqqjagd4wqlejhdrb4uzrccbja3wpbph6isxmo6fa47ky diff --git a/Modules/Filtering/LabelErodeDilate/test/Input/point.nii.gz.cid b/Modules/Filtering/LabelErodeDilate/test/Input/point.nii.gz.cid new file mode 100644 index 000000000000..6003e5864788 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/Input/point.nii.gz.cid @@ -0,0 +1 @@ +bafkreie35wxlvvtapcrssnackka5fipw4w6aajermzeiv3dfiyyhbyxe2y diff --git a/Modules/Filtering/LabelErodeDilate/test/itkLabelSetDilateTest.cxx b/Modules/Filtering/LabelErodeDilate/test/itkLabelSetDilateTest.cxx new file mode 100644 index 000000000000..11787cdff6ef --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/itkLabelSetDilateTest.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 "itkImageFileReader.h" +#include "itkImageFileWriter.h" + +#include "itkLabelSetDilateImageFilter.h" +#include "itkTestingMacros.h" +#include "read_info.cxx" + +template +int +doDilate(char * In, char * Out, int radius) +{ + using MaskImType = typename itk::Image; + + // load + using ReaderType = typename itk::ImageFileReader; + typename ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(In); + ITK_TRY_EXPECT_NO_EXCEPTION(reader->Update()); + + // Label dilation + using FilterType = typename itk::LabelSetDilateImageFilter; + typename FilterType::Pointer filter = FilterType::New(); + filter->SetInput(reader->GetOutput()); + filter->SetRadius(radius); + filter->SetUseImageSpacing(true); + using WriterType = typename itk::ImageFileWriter; + typename WriterType::Pointer writer = WriterType::New(); + writer->SetInput(filter->GetOutput()); + writer->SetFileName(Out); + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + return EXIT_SUCCESS; +} + + +int +itkLabelSetDilateTest(int argc, char * argv[]) +{ + + if (argc != 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv); + std::cerr << " inputimage radius outputimage" << std::endl; + return EXIT_FAILURE; + } + + int dim1; + + itk::MultiThreaderBase::SetGlobalMaximumNumberOfThreads(1); + itk::IOComponentEnum ComponentType; + + if (!readImageInfo(argv[1], &ComponentType, &dim1)) + { + std::cerr << "Failed to open " << argv[1] << std::endl; + return EXIT_FAILURE; + } + + int status = EXIT_FAILURE; + switch (dim1) + { + case 2: + status = doDilate(argv[1], argv[3], std::stoi(argv[2])); + break; + case 3: + status = doDilate(argv[1], argv[3], std::stoi(argv[2])); + break; + default: + std::cerr << "Unsupported dimension" << std::endl; + return EXIT_FAILURE; + } + return status; +} diff --git a/Modules/Filtering/LabelErodeDilate/test/itkLabelSetErodeTest.cxx b/Modules/Filtering/LabelErodeDilate/test/itkLabelSetErodeTest.cxx new file mode 100644 index 000000000000..9da220054eb1 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/itkLabelSetErodeTest.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 "itkImageFileReader.h" +#include "itkImageFileWriter.h" + +#include "itkLabelSetErodeImageFilter.h" +#include "itkTestingMacros.h" + +#include "read_info.cxx" + +template +int +doErode(char * In, char * Out, int radius) +{ + using MaskImType = typename itk::Image; + + // load + using ReaderType = typename itk::ImageFileReader; + typename ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(In); + ITK_TRY_EXPECT_NO_EXCEPTION(reader->Update()); + + // Label dilation + using FilterType = typename itk::LabelSetErodeImageFilter; + typename FilterType::Pointer filter = FilterType::New(); + filter->SetInput(reader->GetOutput()); + filter->SetRadius(radius); + filter->SetUseImageSpacing(true); + using WriterType = typename itk::ImageFileWriter; + typename WriterType::Pointer writer = WriterType::New(); + writer->SetInput(filter->GetOutput()); + writer->SetFileName(Out); + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + return EXIT_SUCCESS; +} + + +int +itkLabelSetErodeTest(int argc, char * argv[]) +{ + + if (argc != 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv); + std::cerr << " inputimage radius outputimage" << std::endl; + return EXIT_FAILURE; + } + + int dim1; + + itk::MultiThreaderBase::SetGlobalMaximumNumberOfThreads(1); + itk::IOComponentEnum ComponentType; + + if (!readImageInfo(argv[1], &ComponentType, &dim1)) + { + std::cerr << "Failed to open " << argv[1] << std::endl; + return EXIT_FAILURE; + } + + int status = EXIT_FAILURE; + switch (dim1) + { + case 2: + status = doErode(argv[1], argv[3], std::stoi(argv[2])); + break; + case 3: + status = doErode(argv[1], argv[3], std::stoi(argv[2])); + break; + default: + std::cerr << "Unsupported dimension" << std::endl; + return EXIT_FAILURE; + } + return status; +} diff --git a/Modules/Filtering/LabelErodeDilate/test/read_info.cxx b/Modules/Filtering/LabelErodeDilate/test/read_info.cxx new file mode 100644 index 000000000000..af716d94a2da --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/test/read_info.cxx @@ -0,0 +1,42 @@ +/*========================================================================= + * + * 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 read_info_cxx +#define read_info_cxx + +///////////////////////////////// +static int +readImageInfo(char * filename, itk::IOComponentEnum * ComponentType, int * dim) +{ + itk::ImageIOBase::Pointer imageIO = itk::ImageIOFactory::CreateImageIO(filename, itk::IOFileModeEnum::ReadMode); + + if (imageIO.IsNull()) + { + return 0; + } + + imageIO->SetFileName(filename); + imageIO->ReadImageInformation(); + + *ComponentType = imageIO->GetComponentType(); + *dim = imageIO->GetNumberOfDimensions(); + return (1); +} + +///////////////////////////////// +#endif diff --git a/Modules/Filtering/LabelErodeDilate/wrapping/CMakeLists.txt b/Modules/Filtering/LabelErodeDilate/wrapping/CMakeLists.txt new file mode 100644 index 000000000000..297722e3ceba --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/wrapping/CMakeLists.txt @@ -0,0 +1,11 @@ +itk_wrap_module(LabelErodeDilate) + +set( + WRAPPER_SUBMODULE_ORDER + itkLabelSetDilateImageFilter + itkLabelSetErodeImageFilter + itkLabelSetMorphBaseImageFilter +) + +itk_auto_load_submodules() +itk_end_wrap_module() diff --git a/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetDilateImageFilter.wrap b/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetDilateImageFilter.wrap new file mode 100644 index 000000000000..6c435b28ad2c --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetDilateImageFilter.wrap @@ -0,0 +1,3 @@ +itk_wrap_class("itk::LabelSetDilateImageFilter" POINTER) + itk_wrap_image_filter("${WRAP_ITK_SCALAR}" 2 2+) +itk_end_wrap_class() diff --git a/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetErodeImageFilter.wrap b/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetErodeImageFilter.wrap new file mode 100644 index 000000000000..59ca09789fa5 --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetErodeImageFilter.wrap @@ -0,0 +1,3 @@ +itk_wrap_class("itk::LabelSetErodeImageFilter" POINTER) + itk_wrap_image_filter("${WRAP_ITK_SCALAR}" 2 2+) +itk_end_wrap_class() diff --git a/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetMorphBaseImageFilter.wrap b/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetMorphBaseImageFilter.wrap new file mode 100644 index 000000000000..fd655f85db7d --- /dev/null +++ b/Modules/Filtering/LabelErodeDilate/wrapping/itkLabelSetMorphBaseImageFilter.wrap @@ -0,0 +1,8 @@ +itk_wrap_class("itk::LabelSetMorphBaseImageFilter" POINTER) + foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + itk_wrap_template("${ITKM_I${t}${d}}TRUE${ITKM_I${t}${d}}" "${ITKT_I${t}${d}},true,${ITKT_I${t}${d}}") + itk_wrap_template("${ITKM_I${t}${d}}FALSE${ITKM_I${t}${d}}" "${ITKT_I${t}${d}},false,${ITKT_I${t}${d}}") + endforeach() + endforeach() +itk_end_wrap_class() diff --git a/Modules/Remote/LabelErodeDilate.remote.cmake b/Modules/Remote/LabelErodeDilate.remote.cmake deleted file mode 100644 index 409da5f51ecb..000000000000 --- a/Modules/Remote/LabelErodeDilate.remote.cmake +++ /dev/null @@ -1,56 +0,0 @@ -#-- # Grading Level Criteria Report -#-- EVALUATION DATE: 2020-03-01 -#-- EVALUATORS: [<>,<>] -#-- -#-- ## 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 -#-- - [ ] 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 -#-- - [ ] No external requirements beyond those needed by ITK proper -#-- - [ ] Builds and passes tests on all supported platforms within 1 month of each core tagged release -#-- - [ ] Windows Shared Library Build with Visual Studio -#-- - [ ] Mac with clang compiller -#-- - [ ] Linux with gcc compiler -#-- - [ ] Active developer community dedicated to maintaining code-base -#-- - [ ] 75% code coverage demonstrated for testing suite -#-- - [ ] Continuous integration testing performed -#-- - [ ] 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 2020-02-19, and clang-tidy modernizations completed - -itk_fetch_module( - LabelErodeDilate - "Classes performing morphology efficiently on label images. - Label collisions are consistently handled, and - operations are efficient (approximately constant time wrt - structuring element size). Only circular/spherical/hyperspherical - structuring elements are supported. - https://doi.org/10.54294/j8lsa66 - https://doi.org/10.54294/aq68pt" - MODULE_COMPLIANCE_LEVEL 3 - GIT_REPOSITORY https://github.com/InsightSoftwareConsortium/ITKLabelErodeDilate.git - GIT_TAG 5850a0f75214b72f0fe6e93f2d6101fc888c95c9 - ) diff --git a/pyproject.toml b/pyproject.toml index 13c20a78be19..d75121de752e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ cmd = '''cmake -DModule_Montage:BOOL=ON -DModule_FastBilateral:BOOL=ON -DModule_GenericLabelInterpolator:BOOL=ON + -DModule_LabelErodeDilate:BOOL=ON -DModule_MeshNoise:BOOL=ON -DModule_MGHIO:BOOL=ON -DITK_COMPUTER_MEMORY_SIZE:STRING=11