diff --git a/Modules/Filtering/TextureFeatures/CMakeLists.txt b/Modules/Filtering/TextureFeatures/CMakeLists.txt new file mode 100644 index 00000000000..fcebd3f7af1 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/CMakeLists.txt @@ -0,0 +1,3 @@ +project(TextureFeatures) + +itk_module_impl() diff --git a/Modules/Filtering/TextureFeatures/README.md b/Modules/Filtering/TextureFeatures/README.md new file mode 100644 index 00000000000..9157aa2392d --- /dev/null +++ b/Modules/Filtering/TextureFeatures/README.md @@ -0,0 +1,31 @@ +# TextureFeatures + +In-tree ITK module providing N-dimensional textural feature image +filters. The flagship classes compute first-order, run-length, and +co-occurrence (GLCM) texture features over a sliding window for each +voxel of an input image, producing per-feature output maps suitable +for radiomics and computer-vision pipelines. + +The flagship filters are +`itk::CoocurrenceTextureFeaturesImageFilter`, +`itk::FirstOrderTextureFeaturesImageFilter`, and +`itk::RunLengthTextureFeaturesImageFilter`, with supporting +`itk::DigitizerFunctor` and `itk::FirstOrderTextureHistogram` building +blocks. + +## Origin + +Ingested from the standalone remote module +[**InsightSoftwareConsortium/ITKTextureFeatures**](https://github.com/InsightSoftwareConsortium/ITKTextureFeatures) +on 2026-05-08, following the v4 ingestion guidelines defined in +InsightSoftwareConsortium/ITK#6204. The upstream repository will be +archived read-only after this PR merges; it remains reachable at the +URL above for historical reference (notably the `doc/` and `example/` +directories, which were intentionally left in the upstream archive). + +## References + +- Vimort J., McCormick M., Budin F., Paniagua B. + *Computing Textural Feature Maps for N-Dimensional images.* + The Insight Journal. January-December. 2017. + diff --git a/Modules/Filtering/TextureFeatures/include/itkCoocurrenceTextureFeaturesImageFilter.h b/Modules/Filtering/TextureFeatures/include/itkCoocurrenceTextureFeaturesImageFilter.h new file mode 100644 index 00000000000..d57b371ab20 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/include/itkCoocurrenceTextureFeaturesImageFilter.h @@ -0,0 +1,263 @@ +/*========================================================================= + * + * 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 itkCoocurrenceTextureFeaturesImageFilter_h +#define itkCoocurrenceTextureFeaturesImageFilter_h + +#include "itkImageToImageFilter.h" +#include "itkConstNeighborhoodIterator.h" +#include "itkVectorContainer.h" + +namespace itk +{ +namespace Statistics +{ +/** \class CoocurrenceTextureFeaturesImageFilter + * \brief This class computes texture descriptions for each voxel of + * a given image and a mask image if provided. The output image can then be + * displayed by using colormaps. + * + * This filter computes a N-D image where each voxel will contain + * a vector of up to 8 scalars representing the texture features + * (of the specified neighborhood) from a N-D scalar image. + * The texture features are computed for each spatial + * direction and averaged afterward. + * + * This filter use grey-level co-occurence matrix in order to compute description a la Haralick. (See + * Haralick, R.M., K. Shanmugam and I. Dinstein. 1973. Textural Features for + * Image Classification. IEEE Transactions on Systems, Man and Cybernetics. + * SMC-3(6):610-620. See also Haralick, R.M. 1979. Statistical and Structural + * Approaches to Texture. Proceedings of the IEEE, 67:786-804.) + * + * Template Parameters: + * -# The input image type: a N dimensional image where the pixel type MUST be integer. + * -# The output image type: a N dimensional image where the pixel type MUST be a vector of floating points or a + *VectorImage. + * + * Inputs and parameters: + * -# An image + * -# A mask defining the region over which texture features will be + * calculated. (Optional) + * -# The pixel value that defines the "inside" of the mask. (Optional, defaults + * to 1 if a mask is set.) + * -# The number of intensity bins. (Optional, defaults to 256.) + * -# The set of directions (offsets) to average across. (Optional, defaults to + * {(-1, 0), (-1, -1), (0, -1), (1, -1)} for 2D images and scales analogously + * for ND images.) + * -# The pixel intensity range over which the features will be calculated. + * (Optional, defaults to the full dynamic range of the pixel type.) + * -# The size of the neighborhood radius. (Optional, defaults to 2.) + * + * Recommendations: + * -# Input image: To improve the computation time, the useful data should take as much + * space as possible in the input image. If the useful data is concentrated in one part of + * the image a crop step should be considered prior to the usage of this filter. + * -# Mask: Even if optional, the usage of a mask will greatly improve the computation time. + * -# Number of intensity bins: The number of bins should be adapted to the type of results expected + * and the intensity range. In addition a high number of bins will increase the + * computation time. + * -# Pixel intensity range: For better results the Pixel intensity should be adapted to the input image + * intensity range. For example they could be the minimum and maximum intensity of the image, or 0 and + * the maximum intensity (if the negative values are considered as noise). + * + * WARNING: This probably won't work for pixels of double or long-double type + * unless you set the histogram min and max manually. This is because the largest + * histogram bin by default has max value of the largest possible pixel value + * plus 1. For double and long-double types, whose "RealType" as defined by the + * NumericTraits class is the same, and thus cannot hold any larger values, + * this would cause a float overflow. + * + * \sa HistogramToTextureFeaturesFilter + * \sa ScalarImageToCooccurrenceMatrixFilte + * \sa ScalarImageToTextureFeaturesFilter + * + * \author: Jean-Baptiste Vimort + * \ingroup TextureFeatures + **/ + +template > +class ITK_TEMPLATE_EXPORT CoocurrenceTextureFeaturesImageFilter : public ImageToImageFilter +{ +public: + /** Standard type alias */ + using Self = CoocurrenceTextureFeaturesImageFilter; + using Superclass = ImageToImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(CoocurrenceTextureFeaturesImageFilter); + + /** standard New() method support */ + itkNewMacro(Self); + + using InputImageType = TInputImage; + using OutputImageType = TOutputImage; + using MaskImageType = TMaskImage; + + using PixelType = typename InputImageType::PixelType; + using MaskPixelType = typename MaskImageType::PixelType; + using IndexType = typename InputImageType::IndexType; + using PointType = typename InputImageType::PointType; + + using OffsetType = typename InputImageType::OffsetType; + using OffsetVector = VectorContainer; + using OffsetVectorPointer = typename OffsetVector::Pointer; + using OffsetVectorConstPointer = typename OffsetVector::ConstPointer; + + using InputRegionType = typename InputImageType::RegionType; + using OutputRegionType = typename OutputImageType::RegionType; + + using NeighborhoodRadiusType = typename itk::ConstNeighborhoodIterator::RadiusType; + + using MeasurementType = typename NumericTraits::RealType; + using RealType = typename NumericTraits::RealType; + + /** Method to set/get the Neighborhood radius */ + itkSetMacro(NeighborhoodRadius, NeighborhoodRadiusType); + itkGetConstMacro(NeighborhoodRadius, NeighborhoodRadiusType); + + /** Method to set the mask image */ + itkSetInputMacro(MaskImage, MaskImageType); + + /** Method to get the mask image */ + itkGetInputMacro(MaskImage, MaskImageType); + + + /** Specify the default number of bins per axis */ + static constexpr unsigned int DefaultBinsPerAxis = 256; + + /** + * Set the offsets over which the intensities pairs will be computed. + * Invoking this function clears the previous offsets. + * Note: for each individual offset in the OffsetVector, the rightmost non-zero + * offset element must be positive. For example, in the offset list of a 2D image, + * (1, 0) means the offset along x-axis. (1, 0) has to be set instead + * of (-1, 0). This is required from the iterating order of pixel iterator. + * + */ + itkSetObjectMacro(Offsets, OffsetVector); + + /** + * Set offset over which the intensities pairs will be computed. + * Invoking this function clears the previous offset(s). + * Note: for each individual offset, the rightmost non-zero + * offset element must be positive. For example, in the offset list of a 2D image, + * (1, 0) means the offset along x-axis. (1, 0) has to be set instead + * of (-1, 0). This is required from the iterating order of pixel iterator. + * + */ + void + SetOffset(const OffsetType offset); + + /** + * Get the current offset(s). + */ + itkGetModifiableObjectMacro(Offsets, OffsetVector); + + /** Set number of histogram bins along each axis */ + itkSetMacro(NumberOfBinsPerAxis, unsigned int); + + /** Get number of histogram bins along each axis */ + itkGetConstMacro(NumberOfBinsPerAxis, unsigned int); + + /** Get the max pixel value defining one dimension of the joint histogram. */ + itkGetConstMacro(HistogramMaximum, PixelType); + itkSetMacro(HistogramMaximum, PixelType); + + /** Get the min pixel value defining one dimension of the joint histogram. */ + itkGetConstMacro(HistogramMinimum, PixelType); + itkSetMacro(HistogramMinimum, PixelType); + + /** + * Set the pixel value of the mask that should be considered "inside" the + * object. Defaults to 1. + */ + itkSetMacro(InsidePixelValue, MaskPixelType); + itkGetConstMacro(InsidePixelValue, MaskPixelType); + + /** Set the calculator to normalize the histogram (divide all bins by the + total frequency). Normalization is off by default. */ + itkSetMacro(Normalize, bool); + itkGetConstMacro(Normalize, bool); + itkBooleanMacro(Normalize); + + using OutputPixelType = typename OutputImageType::PixelType; + using OutputRealType = typename NumericTraits::ScalarRealType; + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro(OutputPixelTypeCheck, (Concept::IsFloatingPoint)); + // End concept checking +#endif + +protected: + using HistogramIndexType = int; + using DigitizedImageType = itk::Image; + using NeighborhoodIteratorType = typename itk::ConstNeighborhoodIterator; + using NeighborIndexType = typename NeighborhoodIteratorType::NeighborIndexType; + + CoocurrenceTextureFeaturesImageFilter(); + ~CoocurrenceTextureFeaturesImageFilter() override = default; + + bool + IsInsideNeighborhood(const OffsetType & iteratedOffset); + void + ComputeFeatures(const vnl_matrix & hist, + const unsigned int totalNumberOfFreq, + typename TOutputImage::PixelType & outputPixel); + void + ComputeMeansAndVariances(const vnl_matrix & hist, + const unsigned int totalNumberOfFreq, + double & pixelMean, + double & marginalMean, + double & marginalDevSquared, + double & pixelVariance); + void + PrintSelf(std::ostream & os, Indent indent) const override; + + /** This method causes the filter to generate its output. */ + void + BeforeThreadedGenerateData() override; + void + AfterThreadedGenerateData() override; + void + DynamicThreadedGenerateData(const OutputRegionType & outputRegionForThread) override; + void + GenerateOutputInformation() override; + +private: + typename DigitizedImageType::Pointer m_DigitizedInputImage; + + NeighborhoodRadiusType m_NeighborhoodRadius; + OffsetVectorPointer m_Offsets; + unsigned int m_NumberOfBinsPerAxis; + PixelType m_HistogramMinimum; + PixelType m_HistogramMaximum; + MaskPixelType m_InsidePixelValue; + bool m_Normalize; +}; +} // end of namespace Statistics +} // end of namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkCoocurrenceTextureFeaturesImageFilter.hxx" +#endif + +#endif diff --git a/Modules/Filtering/TextureFeatures/include/itkCoocurrenceTextureFeaturesImageFilter.hxx b/Modules/Filtering/TextureFeatures/include/itkCoocurrenceTextureFeaturesImageFilter.hxx new file mode 100644 index 00000000000..5d38ffb713c --- /dev/null +++ b/Modules/Filtering/TextureFeatures/include/itkCoocurrenceTextureFeaturesImageFilter.hxx @@ -0,0 +1,450 @@ +/*========================================================================= + * + * 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 itkCoocurrenceTextureFeaturesImageFilter_hxx +#define itkCoocurrenceTextureFeaturesImageFilter_hxx + +#include "itkCoocurrenceTextureFeaturesImageFilter.h" +#include "itkRegionOfInterestImageFilter.h" +#include "itkNeighborhoodAlgorithm.h" +#include "itkBinaryFunctorImageFilter.h" +#include "itkDigitizerFunctor.h" + +#include + +namespace itk +{ +namespace Statistics +{ +template +CoocurrenceTextureFeaturesImageFilter::CoocurrenceTextureFeaturesImageFilter() + : m_NumberOfBinsPerAxis(Self::DefaultBinsPerAxis) + , m_HistogramMinimum(NumericTraits::NonpositiveMin()) + , m_HistogramMaximum(NumericTraits::max()) + , m_InsidePixelValue(NumericTraits::OneValue()) +{ + this->SetNumberOfRequiredInputs(1); + this->SetNumberOfRequiredOutputs(1); + + // Mark the "MaskImage" as an optional named input. First it has to + // be added to the list of named inputs then removed from the + // required list. + Self::AddRequiredInputName("MaskImage"); + Self::RemoveRequiredInputName("MaskImage"); + + // Set the offset directions to their defaults: half of all the possible + // directions 1 pixel away. (The other half is included by symmetry.) + // We use a neighborhood iterator to calculate the appropriate offsets. + using NeighborhoodType = Neighborhood; + NeighborhoodType hood; + hood.SetRadius(1); + + // Select all "previous" neighbors that are face+edge+vertex + // connected to the iterated pixel. Do not include the currentInNeighborhood pixel. + unsigned int centerIndex = hood.GetCenterNeighborhoodIndex(); + OffsetVectorPointer offsets = OffsetVector::New(); + for (unsigned int d = 0; d < centerIndex; ++d) + { + OffsetType offset = hood.GetOffset(d); + offsets->push_back(offset); + } + this->SetOffsets(offsets); + NeighborhoodType nhood; + nhood.SetRadius(2); + this->m_NeighborhoodRadius = nhood.GetRadius(); + + this->m_Normalize = false; + this->DynamicMultiThreadingOn(); +} + +template +void +CoocurrenceTextureFeaturesImageFilter::SetOffset(const OffsetType offset) +{ + OffsetVectorPointer offsetVector = OffsetVector::New(); + offsetVector->push_back(offset); + this->SetOffsets(offsetVector); +} + +template +void +CoocurrenceTextureFeaturesImageFilter::BeforeThreadedGenerateData() +{ + + typename TInputImage::Pointer input = InputImageType::New(); + input->Graft(const_cast(this->GetInput())); + + using DigitizerFunctorType = Digitizer; + + DigitizerFunctorType digitalizer(m_NumberOfBinsPerAxis, m_InsidePixelValue, m_HistogramMinimum, m_HistogramMaximum); + + using FilterType = BinaryFunctorImageFilter; + typename FilterType::Pointer filter = FilterType::New(); + if (this->GetMaskImage() != nullptr) + { + typename TMaskImage::Pointer mask = MaskImageType::New(); + mask->Graft(const_cast(this->GetMaskImage())); + filter->SetInput1(mask); + } + else + { + filter->SetConstant1(m_InsidePixelValue); + } + filter->SetInput2(input); + filter->SetFunctor(digitalizer); + filter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + filter->Update(); + m_DigitizedInputImage = filter->GetOutput(); +} + +template +void +CoocurrenceTextureFeaturesImageFilter::AfterThreadedGenerateData() +{ + // Free internal image + this->m_DigitizedInputImage = nullptr; +} + + +template +void +CoocurrenceTextureFeaturesImageFilter::DynamicThreadedGenerateData( + const OutputRegionType & outputRegionForThread) +{ + // Recuperation of the different inputs/outputs + OutputImageType * outputPtr = this->GetOutput(); + + // Creation of the output pixel type + typename TOutputImage::PixelType outputPixel; + NumericTraits::SetLength(outputPixel, outputPtr->GetNumberOfComponentsPerPixel()); + + // Separation of the non-boundary region that will be processed in a different way + NeighborhoodAlgorithm::ImageBoundaryFacesCalculator boundaryFacesCalculator; + typename NeighborhoodAlgorithm::ImageBoundaryFacesCalculator::FaceListType faceList = + boundaryFacesCalculator(this->m_DigitizedInputImage, outputRegionForThread, m_NeighborhoodRadius); + auto fit = faceList.begin(); + + // Declaration of the variables useful to iterate over the all image region + bool isInImage; + typename OffsetVector::ConstIterator offsets; + + // Declaration of the variables useful to iterate over the all the offsets + OffsetType offset; + unsigned int totalNumberOfFreq; + + + vnl_matrix hist(m_NumberOfBinsPerAxis, m_NumberOfBinsPerAxis); + + // Declaration of the variables useful to iterate over the all neighborhood region + HistogramIndexType currentInNeighborhoodPixelIntensity; + + // Declaration of the variables useful to iterate over the run + HistogramIndexType pixelIntensity(NumericTraits::ZeroValue()); + OffsetType tempOffset; + + /// ***** Non-boundary Region ***** + for (; fit != faceList.end(); ++fit) + { + NeighborhoodIteratorType inputNIt(m_NeighborhoodRadius, this->m_DigitizedInputImage, *fit); + using IteratorType = itk::ImageRegionIterator; + IteratorType outputIt(outputPtr, *fit); + + // Iteration over the all image region + while (!inputNIt.IsAtEnd()) + { + // If the voxel is outside of the mask, don't treat it + if (inputNIt.GetCenterPixel() < (-5)) // the pixel is outside of the mask + { + outputPixel.Fill(0); + outputIt.Set(outputPixel); + ++inputNIt; + ++outputIt; + continue; + } + // Initialisation of the histogram + hist.fill(0); + + totalNumberOfFreq = 0; + // Iteration over all the offsets + for (offsets = m_Offsets->Begin(); offsets != m_Offsets->End(); ++offsets) + { + offset = offsets.Value(); + // Iteration over the all neighborhood region + for (NeighborIndexType nb = 0; nb < inputNIt.Size(); ++nb) + { + // Test if the current voxel is in the mask and is the range of the image intensity specified + currentInNeighborhoodPixelIntensity = inputNIt.GetPixel(nb); + if (currentInNeighborhoodPixelIntensity < 0) + { + continue; + } + + // Test if the current offset is still pointing to a voxel inside th neighborhood + tempOffset = inputNIt.GetOffset(nb) + offset; + if (!(this->IsInsideNeighborhood(tempOffset))) + { + continue; + } + + // Test if the part of the neighborhood pointed by the offset is still part of the image + if (fit == faceList.begin()) + { + inputNIt.GetPixel(tempOffset, isInImage); + if (!isInImage) + { + break; + } + } + + // Test if the pointed voxel is in the mask and is the range of the image intensity specified + pixelIntensity = inputNIt.GetPixel(tempOffset); + if (pixelIntensity < 0) + { + continue; + } + + // Increase the corresponding bin in the histogram + ++totalNumberOfFreq; + ++hist[currentInNeighborhoodPixelIntensity][pixelIntensity]; + } + } + + // No coocurrences means we are computing the texture of a single pixel, which is undefined + if (totalNumberOfFreq != 0) + { + // Compute the run length features + this->ComputeFeatures(hist, totalNumberOfFreq, outputPixel); + outputIt.Set(outputPixel); + } + else + { + outputPixel.Fill(0); + outputIt.Set(outputPixel); + } + + ++inputNIt; + ++outputIt; + } + } +} + +template +void +CoocurrenceTextureFeaturesImageFilter::GenerateOutputInformation() +{ + // Call superclass's version + Superclass::GenerateOutputInformation(); + + OutputImageType * output = this->GetOutput(); + // If the output image type is a VectorImage the number of + // components will be properly sized if before allocation, if the + // output is a fixed width vector and the wrong number of + // components, then an exception will be thrown. + if (output->GetNumberOfComponentsPerPixel() != 8) + { + output->SetNumberOfComponentsPerPixel(8); + } +} + +template +bool +CoocurrenceTextureFeaturesImageFilter::IsInsideNeighborhood( + const OffsetType & iteratedOffset) +{ + bool insideNeighborhood = true; + for (unsigned int i = 0; i < this->m_NeighborhoodRadius.Dimension; ++i) + { + int boundDistance = m_NeighborhoodRadius[i] - Math::abs(iteratedOffset[i]); + if (boundDistance < 0) + { + insideNeighborhood = false; + break; + } + } + return insideNeighborhood; +} + +template +void +CoocurrenceTextureFeaturesImageFilter::ComputeFeatures( + const vnl_matrix & hist, + const unsigned int totalNumberOfFreq, + typename TOutputImage::PixelType & outputPixel) +{ + // Now get the various means and variances. This is takes two passes + // through the histogram. + double pixelMean; + double marginalMean; + double marginalDevSquared; + double pixelVariance; + + this->ComputeMeansAndVariances(hist, totalNumberOfFreq, pixelMean, marginalMean, marginalDevSquared, pixelVariance); + + // Finally compute the texture features. Another pass. + MeasurementType energy = NumericTraits::ZeroValue(); + MeasurementType entropy = NumericTraits::ZeroValue(); + MeasurementType correlation = NumericTraits::ZeroValue(); + + MeasurementType inverseDifferenceMoment = NumericTraits::ZeroValue(); + + MeasurementType inertia = NumericTraits::ZeroValue(); + MeasurementType clusterShade = NumericTraits::ZeroValue(); + MeasurementType clusterProminence = NumericTraits::ZeroValue(); + MeasurementType haralickCorrelation = NumericTraits::ZeroValue(); + + double pixelVarianceSquared = pixelVariance * pixelVariance; + // Variance is only used in correlation. If variance is 0, then + // (index[0] - pixelMean) * (index[1] - pixelMean) + // should be zero as well. In this case, set the variance to 1. in + // order to avoid NaN correlation. + if (Math::FloatAlmostEqual(pixelVarianceSquared, 0.0, 4, 2 * NumericTraits::epsilon())) + { + pixelVarianceSquared = 1.; + } + const double log2 = std::log(2.0); + + for (unsigned int a = 0; a < m_NumberOfBinsPerAxis; ++a) + { + for (unsigned int b = 0; b < m_NumberOfBinsPerAxis; ++b) + { + float frequency = hist[a][b] / (float)totalNumberOfFreq; + if (Math::AlmostEquals(frequency, NumericTraits::ZeroValue())) + { + continue; // no use doing these calculations if we're just multiplying by + // zero. + } + + energy += frequency * frequency; + entropy -= (frequency > 0.0001) ? frequency * std::log(frequency) / log2 : 0; + correlation += ((a - pixelMean) * (b - pixelMean) * frequency) / pixelVarianceSquared; + inverseDifferenceMoment += frequency / (1.0 + (a - b) * (a - b)); + inertia += (a - b) * (a - b) * frequency; + clusterShade += std::pow((a - pixelMean) + (b - pixelMean), 3) * frequency; + clusterProminence += std::pow((a - pixelMean) + (b - pixelMean), 4) * frequency; + haralickCorrelation += a * b * frequency; + } + } + + haralickCorrelation = (haralickCorrelation - marginalMean * marginalMean) / marginalDevSquared; + + outputPixel[0] = energy; + outputPixel[1] = entropy; + outputPixel[2] = correlation; + outputPixel[3] = inverseDifferenceMoment; + outputPixel[4] = inertia; + outputPixel[5] = clusterShade; + outputPixel[6] = clusterProminence; + outputPixel[7] = haralickCorrelation; +} + +template +void +CoocurrenceTextureFeaturesImageFilter::ComputeMeansAndVariances( + const vnl_matrix & hist, + const unsigned int totalNumberOfFreq, + double & pixelMean, + double & marginalMean, + double & marginalDevSquared, + double & pixelVariance) +{ + // This function takes two passes through the histogram and two passes through + // an array of the same length as a histogram axis. This could probably be + // cleverly compressed to one pass, but it's not clear that that's necessary. + + // Initialize everything + std::vector marginalSums(m_NumberOfBinsPerAxis, 0.0); + + pixelMean = 0; + + // Ok, now do the first pass through the histogram to get the marginal sums + // and compute the pixel mean + for (unsigned int a = 0; a < m_NumberOfBinsPerAxis; a++) + { + for (unsigned int b = 0; b < m_NumberOfBinsPerAxis; b++) + { + float frequency = hist[a][b] / (float)totalNumberOfFreq; + pixelMean += a * frequency; + marginalSums[a] += frequency; + } + } + + /* Now get the mean and deviaton of the marginal sums. + Compute incremental mean and SD, a la Knuth, "The Art of Computer + Programming, Volume 2: Seminumerical Algorithms", section 4.2.2. + Compute mean and standard deviation using the recurrence relation: + M(1) = x(1), M(k) = M(k-1) + (x(k) - M(k-1) ) / k + S(1) = 0, S(k) = S(k-1) + (x(k) - M(k-1)) * (x(k) - M(k)) + for 2 <= k <= n, then + sigma = std::sqrt(S(n) / n) (or divide by n-1 for sample SD instead of + population SD). + */ + marginalMean = marginalSums[0]; + marginalDevSquared = 0; + for (unsigned int arrayIndex = 1; arrayIndex < m_NumberOfBinsPerAxis; arrayIndex++) + { + int k = arrayIndex + 1; + double M_k_minus_1 = marginalMean; + double S_k_minus_1 = marginalDevSquared; + double x_k = marginalSums[arrayIndex]; + + double M_k = M_k_minus_1 + (x_k - M_k_minus_1) / k; + double S_k = S_k_minus_1 + (x_k - M_k_minus_1) * (x_k - M_k); + + marginalMean = M_k; + marginalDevSquared = S_k; + } + marginalDevSquared = marginalDevSquared / m_NumberOfBinsPerAxis; + + // OK, now compute the pixel variances. + pixelVariance = 0; + for (unsigned int a = 0; a < m_NumberOfBinsPerAxis; a++) + { + for (unsigned int b = 0; b < m_NumberOfBinsPerAxis; b++) + { + float frequency = hist[a][b] / (float)totalNumberOfFreq; + pixelVariance += (a - pixelMean) * (a - pixelMean) * (frequency); + } + } +} + +template +void +CoocurrenceTextureFeaturesImageFilter::PrintSelf(std::ostream & os, + Indent indent) const +{ + + Superclass::PrintSelf(os, indent); + + itkPrintSelfObjectMacro(DigitizedInputImage); + + os << indent << "NeighborhoodRadius: " + << static_cast::PrintType>(m_NeighborhoodRadius) << std::endl; + + itkPrintSelfObjectMacro(Offsets); + + os << indent << "NumberOfBinsPerAxis: " << m_NumberOfBinsPerAxis << std::endl; + os << indent << "Min: " << static_cast::PrintType>(m_HistogramMinimum) << std::endl; + os << indent << "Max: " << static_cast::PrintType>(m_HistogramMaximum) << std::endl; + os << indent << "InsidePixelValue: " << static_cast::PrintType>(m_InsidePixelValue) + << std::endl; + os << indent << "Normalize: " << m_Normalize << std::endl; +} +} // end of namespace Statistics +} // end of namespace itk + +#endif diff --git a/Modules/Filtering/TextureFeatures/include/itkDigitizerFunctor.h b/Modules/Filtering/TextureFeatures/include/itkDigitizerFunctor.h new file mode 100644 index 00000000000..7967cc6e399 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/include/itkDigitizerFunctor.h @@ -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. + * + *=========================================================================*/ +#ifndef itkDigitizerFunctor_h +#define itkDigitizerFunctor_h + +#include "itkNumericTraits.h" +#include "itkMath.h" + +namespace itk +{ +namespace Statistics +{ + +template +class Digitizer +{ +public: + using PixelType = TInput2; + using MaskPixelType = TInput1; + + Digitizer() + : m_MaskValue(1) + , m_Min(NumericTraits::min()) + , m_Max(NumericTraits::max()) + {} + + Digitizer(unsigned int numberOfBinsPerAxis, MaskPixelType maskValue, PixelType min, PixelType max) + : m_NumberOfBinsPerAxis(numberOfBinsPerAxis) + , m_MaskValue(maskValue) + , m_Min(min) + , m_Max(max) + {} + + ~Digitizer() = default; + + bool + operator!=(const Digitizer & other) const + { + return (m_NumberOfBinsPerAxis != other.m_NumberOfBinsPerAxis) || (m_MaskValue != other.m_MaskValue) || + (m_Min != other.m_Min) || (m_Max != other.m_Max); + } + + bool + operator==(const Digitizer & other) const + { + return !(*this != other); + } + + inline TOutput + operator()(const MaskPixelType & maskPixel, const PixelType & inputPixel) const + { + + if (maskPixel != m_MaskValue) + { + return -10; + } + else if (inputPixel < this->m_Min || inputPixel >= m_Max) + { + return -1; + } + else + { + return Math::Floor((inputPixel - m_Min) / ((m_Max - m_Min) / (float)m_NumberOfBinsPerAxis)); + } + } + + unsigned int m_NumberOfBinsPerAxis{ 256 }; + + MaskPixelType m_MaskValue; + + typename NumericTraits::RealType m_Min; + typename NumericTraits::RealType m_Max; +}; + +} // namespace Statistics +} // namespace itk + +#endif diff --git a/Modules/Filtering/TextureFeatures/include/itkFirstOrderTextureFeaturesImageFilter.h b/Modules/Filtering/TextureFeatures/include/itkFirstOrderTextureFeaturesImageFilter.h new file mode 100644 index 00000000000..1ccac084521 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/include/itkFirstOrderTextureFeaturesImageFilter.h @@ -0,0 +1,125 @@ +/*========================================================================= + * + * 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 itkFirstOrderTextureFeaturesImageFilter_h +#define itkFirstOrderTextureFeaturesImageFilter_h + +#include "itkMovingHistogramImageFilter.h" +#include "itkFirstOrderTextureHistogram.h" + +namespace itk +{ +/** + * \class FirstOrderTextureFeaturesImageFilter + * \brief Compute first order statistics in a neighborhood for each + * pixel. + * + * The output of this filter is a multi-component image where each + * pixel is: + * -# mean + * -# minimum + * -# maximum + * -# variance + * -# standard deviation (sigma) + * -# skewness + * -# kurtosis + * -# entropy. + * These first order statistics are computed based on a define + * neighborhood or kernel such as FlatStructuringElement::Box. + * + * The boundary is handle by only considering the pixel in the image, + * so that the boundary pixel have lets data to compute the + * statistics. + * + * \ingroup TextureFeatures + */ + +template +class ITK_TEMPLATE_EXPORT FirstOrderTextureFeaturesImageFilter + : public MovingHistogramImageFilter< + TInputImage, + TOutputImage, + TKernel, + typename Function::FirstOrderTextureHistogram> +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(FirstOrderTextureFeaturesImageFilter); + + /** Standard class type alias. */ + using Self = FirstOrderTextureFeaturesImageFilter; + using Superclass = MovingHistogramImageFilter< + TInputImage, + TOutputImage, + TKernel, + typename Function::FirstOrderTextureHistogram>; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Standard New method. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkOverrideGetNameOfClassMacro(FirstOrderTextureFeaturesImageFilter); + + /** Image related type alias. */ + using InputImageType = TInputImage; + using OutputImageType = TOutputImage; + using RegionType = typename TInputImage::RegionType; + using SizeType = typename TInputImage::SizeType; + using IndexType = typename TInputImage::IndexType; + using PixelType = typename TInputImage::PixelType; + using OffsetType = typename TInputImage::OffsetType; + using OutputImageRegionType = typename Superclass::OutputImageRegionType; + using OutputPixelType = typename TOutputImage::PixelType; + + /** Image related type alias. */ + static constexpr unsigned int ImageDimension = TInputImage::ImageDimension; + +protected: + unsigned int + GetNumberOfOutputComponents() + { + return 8; + } + + FirstOrderTextureFeaturesImageFilter() = default; + + void + GenerateOutputInformation() override + { + // this methods is overloaded so that if the output image is a + // VectorImage then the correct number of components are set. + + Superclass::GenerateOutputInformation(); + OutputImageType * output = this->GetOutput(); + + if (!output) + { + return; + } + if (output->GetNumberOfComponentsPerPixel() != this->GetNumberOfOutputComponents()) + { + output->SetNumberOfComponentsPerPixel(this->GetNumberOfOutputComponents()); + } + } + + + ~FirstOrderTextureFeaturesImageFilter() override = default; +}; +} // end namespace itk + +#endif diff --git a/Modules/Filtering/TextureFeatures/include/itkFirstOrderTextureHistogram.h b/Modules/Filtering/TextureFeatures/include/itkFirstOrderTextureHistogram.h new file mode 100644 index 00000000000..7451ee045b1 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/include/itkFirstOrderTextureHistogram.h @@ -0,0 +1,145 @@ +/*========================================================================= + * + * 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 itkFirstOrderTextureHistogram_h +#define itkFirstOrderTextureHistogram_h + +#include "itkNumericTraits.h" +#include "itkMath.h" +#include + +namespace itk +{ +namespace Function +{ + +/* \class FirstOrderTextureHistogram + * + * An implementation of the "MovingHistogram" interface for the + * MovingHistogramImageFilter class. This implementation maintains a + * std::map based "histogram" during iteration and computes first + * order statistics from the histogram. + * + * \ingroup TextureFeatures + */ +template +class ITK_TEMPLATE_EXPORT FirstOrderTextureHistogram +{ +public: + FirstOrderTextureHistogram() { m_Count = 0; } + + void + AddPixel(const TInputPixel & p) + { + m_Map[p]++; + ++m_Count; + } + + void + RemovePixel(const TInputPixel & p) + { + + // insert new item if one doesn't exist + auto it = m_Map.find(p); + + assert(it != m_Map.end()); + + if (--(it->second) == 0) + { + m_Map.erase(it); + } + --m_Count; + } + + TOutputPixel + GetValue(const TInputPixel &) + { + TOutputPixel out; + NumericTraits::SetLength(out, 8); + + double sum = 0.0; + double sum2 = 0.0; + double sum3 = 0.0; + double sum4 = 0.0; + const size_t count = m_Count; + + double entropy = 0.0; + size_t curCount = 0; + + for (auto i = m_Map.begin(); i != m_Map.end(); ++i) + { + double t = double(i->first) * double(i->second); + sum += t; + sum2 += (t *= double(i->first)); + sum3 += (t *= double(i->first)); + sum4 += (t *= double(i->first)); + + curCount += i->second; + + const double p_x = double(i->second) / count; + entropy += -p_x * std::log(p_x) / itk::Math::ln2; + } + + const double mean = sum / count; + + // unbiased estimate (requires at least 2 samples) + const double variance = (count > 1) ? (sum2 - (sum * sum) / count) / (count - 1) : 0.0; + const double sigma = std::sqrt(variance); + double skewness = 0.0; + double kurtosis = 0.0; + if (std::abs(variance * sigma) > itk::NumericTraits::min()) + { + + skewness = ((sum3 - 3.0 * mean * sum2) / count + 2.0 * mean * mean * mean) / (variance * sigma); + } + if (std::abs(variance) > itk::NumericTraits::min()) + { + kurtosis = (sum4 / count + mean * (-4.0 * sum3 / count + mean * (6.0 * sum2 / count - 3.0 * mean * mean))) / + (variance * variance) - + 3.0; + } + + unsigned int i = 0; + out[i++] = mean; + out[i++] = m_Map.begin()->first; + out[i++] = m_Map.rbegin()->first; + out[i++] = variance; + out[i++] = sigma; + out[i++] = skewness; + out[i++] = kurtosis; + out[i++] = entropy; + return out; + } + + void + AddBoundary() + {} + + void + RemoveBoundary() + {} + +private: + using MapType = typename std::map; + + MapType m_Map; + size_t m_Count; +}; + +} // end namespace Function +} // end namespace itk +#endif diff --git a/Modules/Filtering/TextureFeatures/include/itkRunLengthTextureFeaturesImageFilter.h b/Modules/Filtering/TextureFeatures/include/itkRunLengthTextureFeaturesImageFilter.h new file mode 100644 index 00000000000..6c9253fd6be --- /dev/null +++ b/Modules/Filtering/TextureFeatures/include/itkRunLengthTextureFeaturesImageFilter.h @@ -0,0 +1,289 @@ +/*========================================================================= + * + * 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 itkRunLengthTextureFeaturesImageFilter_h +#define itkRunLengthTextureFeaturesImageFilter_h + +#include "itkImageToImageFilter.h" +#include "itkScalarImageToRunLengthMatrixFilter.h" +#include "itkConstNeighborhoodIterator.h" + +namespace itk +{ +namespace Statistics +{ +/** \class RunLengthTextureFeaturesImageFilter + * \brief This class computes run length features for each voxel of + * a given image and a mask image if provided. The output image can then be + * displayed by using colormaps. + * + * This filter computes a N-D image where each voxel will contain + * a vector of up to 10 scalars representing the run length features + * (of the specified neighborhood) from a N-D scalar image. + * The run length features are computed from joint histograms of + * pixel intensities and distance (run length) per spatial direction + * then averaged afterward. + * + * The result obtained is a possible texture description. See the following references. + * M. M. Galloway. Texture analysis using gray level run lengths. Computer + * Graphics and Image Processing, 4:172-179, 1975. + * + * A. Chu, C. M. Sehgal, and J. F. Greenleaf. Use of gray value distribution of + * run lengths for texture analysis. Pattern Recognition Letters, 11:415-420, + * 1990. + * + * B. R. Dasarathy and E. B. Holder. Image characterizations based on joint + * gray-level run-length distributions. Pattern Recognition Letters, 12:490-502, + * 1991. + * + * Template Parameters: + * -# The input image type: a N dimensional image where the pixel type MUST be integer. + * -# The output image type: a N dimensional image where the pixel type MUST be a vector of floating points or a + * VectorImage. + * + * Inputs and parameters: + * -# An image + * -# A mask defining the region over which texture features will be + * calculated. (Optional) + * -# The pixel value that defines the "inside" of the mask. (Optional, defaults + * to 1 if a mask is set.) + * -# The number of intensity bins. (Optional, defaults to 256.) + * -# The set of directions (offsets) to average across. (Optional, defaults to + * {(-1, 0), (-1, -1), (0, -1), (1, -1)} for 2D images and scales analogously + * for ND images.) + * -# The pixel intensity range for the joint histogram over which the + * features will be calculated. (Optional, defaults to the full + * dynamic range of the pixel type.) + * -# The distance range for the joint histogram over which the + * features will be calculated. (Optional, defaults to the full + * dynamic range of double type.) + * -# The size of the neighborhood radius. (Optional, defaults to 2.) + * + * Recommendations: + * -# Input image: To improve the computation time, the useful data should take as much + * space as possible in the input image. If the useful data is concentrated in one part of + * the image a crop step should be considered prior to the usage of this filter. + * -# Mask: Even if optional, the usage of a mask will greatly improve the computation time. + * -# Number of intensity bins: The number of bins should be adapted to the type of results expected + * and the intensity and distances ranges. In addition a high number of bins will increase the + * computation time. + * -# Pixel intensity range: For better results the Pixel intensity should be adapted to the input image + * intensity range. For example they could be the minimum and maximum intensity of the image, or 0 and + * the maximum intensity (if the negative values are considered as noise). + * -# Distance range: For better results the distance range should be adapted to the spacing of the input image + * and the size of the neighborhood. + * + * WARNING: This probably won't work for pixels of double or long-double type + * unless you set the histogram min and max manually. This is because the largest + * histogram bin by default has max value of the largest possible pixel value + * plus 1. For double and long-double types, whose "RealType" as defined by the + * NumericTraits class is the same, and thus cannot hold any larger values, + * this would cause a float overflow. + * + * \sa ScalarImageToRunLengthFeaturesFilter + * \sa ScalarImageToRunLengthMatrixFilter + * \sa HistogramToRunLengthFeaturesFilter + * + * \author: Jean-Baptiste Vimort + * \ingroup TextureFeatures + */ + +template > +class ITK_TEMPLATE_EXPORT RunLengthTextureFeaturesImageFilter : public ImageToImageFilter +{ +public: + /** Standard type alias */ + using Self = RunLengthTextureFeaturesImageFilter; + using Superclass = ImageToImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(RunLengthTextureFeaturesImageFilter); + + /** standard New() method support */ + itkNewMacro(Self); + + using InputImageType = TInputImage; + using OutputImageType = TOutputImage; + using MaskImageType = TMaskImage; + + using PixelType = typename InputImageType::PixelType; + using MaskPixelType = typename MaskImageType::PixelType; + using IndexType = typename InputImageType::IndexType; + using PointType = typename InputImageType::PointType; + + using OffsetType = typename InputImageType::OffsetType; + using OffsetVector = VectorContainer; + using OffsetVectorPointer = typename OffsetVector::Pointer; + using OffsetVectorConstPointer = typename OffsetVector::ConstPointer; + + using InputRegionType = typename InputImageType::RegionType; + using OutputRegionType = typename OutputImageType::RegionType; + + using NeighborhoodRadiusType = typename itk::ConstNeighborhoodIterator::RadiusType; + + using MeasurementType = typename NumericTraits::RealType; + using RealType = typename NumericTraits::RealType; + + /** Method to set/get the Neighborhood radius */ + itkSetMacro(NeighborhoodRadius, NeighborhoodRadiusType); + itkGetConstMacro(NeighborhoodRadius, NeighborhoodRadiusType); + + /** Method to set the mask image */ + itkSetInputMacro(MaskImage, MaskImageType); + + /** Method to get the mask image */ + itkGetInputMacro(MaskImage, MaskImageType); + + + /** Specify the default number of bins per axis */ + static constexpr unsigned int DefaultBinsPerAxis = 256; + + /** + * Set the offsets over which the intensity/distance pairs will be computed. + * Invoking this function clears the previous offsets. + * Note: for each individual offset in the OffsetVector, the rightmost non-zero + * offset element must be positive. For example, in the offset list of a 2D image, + * (1, 0) means the offset along x-axis. (1, 0) has to be set instead + * of (-1, 0). This is required from the iterating order of pixel iterator. + * + */ + itkSetObjectMacro(Offsets, OffsetVector); + + /** + * Set offset over which the intensity/distance pairs will be computed. + * Invoking this function clears the previous offset(s). + * Note: for each individual offset, the rightmost non-zero + * offset element must be positive. For example, in the offset list of a 2D image, + * (1, 0) means the offset along x-axis. (1, 0) has to be set instead + * of (-1, 0). This is required from the iterating order of pixel iterator. + * + */ + void + SetOffset(const OffsetType offset); + + /** + * Get the current offset(s). + */ + itkGetModifiableObjectMacro(Offsets, OffsetVector); + + /** Set number of histogram bins along each axis */ + itkSetMacro(NumberOfBinsPerAxis, unsigned int); + + /** Get number of histogram bins along each axis */ + itkGetConstMacro(NumberOfBinsPerAxis, unsigned int); + + /** Set/Get the minimum (inclusive) pixel value defining one dimension of the joint + * value distance histogram. */ + itkGetConstMacro(HistogramValueMinimum, PixelType); + itkSetMacro(HistogramValueMinimum, PixelType); + + /** Set/Get the maximum pixel value defining one dimension of the joint + * value distance histogram. */ + itkGetConstMacro(HistogramValueMaximum, PixelType); + itkSetMacro(HistogramValueMaximum, PixelType); + + + /** + * Set/Get the minimum (inclusive) run length distance in physical + * space that will be used in generating the joint value distance histogram. + */ + itkGetConstMacro(HistogramDistanceMinimum, RealType); + itkSetMacro(HistogramDistanceMinimum, RealType); + + /** + * Set/Get the maximum run length distance in physical + * space that will be used in generating the joint value distance histogram. + */ + itkGetConstMacro(HistogramDistanceMaximum, RealType); + itkSetMacro(HistogramDistanceMaximum, RealType); + + /** + * Set the pixel value of the mask that should be considered "inside" the + * object. Defaults to 1. + */ + itkSetMacro(InsidePixelValue, MaskPixelType); + itkGetConstMacro(InsidePixelValue, MaskPixelType); + + using OutputPixelType = typename OutputImageType::PixelType; + using OutputRealType = typename NumericTraits::ScalarRealType; + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro(OutputPixelTypeCheck, (Concept::IsFloatingPoint)); + // End concept checking +#endif + +protected: + using HistogramIndexType = int; + using DigitizedImageType = itk::Image; + using NeighborhoodIteratorType = typename itk::ConstNeighborhoodIterator; + using NeighborIndexType = typename NeighborhoodIteratorType::NeighborIndexType; + + RunLengthTextureFeaturesImageFilter(); + ~RunLengthTextureFeaturesImageFilter() override = default; + + void + NormalizeOffsetDirection(OffsetType & offset); + bool + IsInsideNeighborhood(const OffsetType & iteratedOffset); + void + IncreaseHistogram(vnl_matrix & hist, + unsigned int & totalNumberOfRuns, + const HistogramIndexType & currentInNeighborhoodPixelIntensity, + const OffsetType & offset, + const unsigned int & pixelDistance); + void + ComputeFeatures(vnl_matrix & hist, + const unsigned int & totalNumberOfRuns, + typename TOutputImage::PixelType & outputPixel); + void + PrintSelf(std::ostream & os, Indent indent) const override; + + /** This method causes the filter to generate its output. */ + void + BeforeThreadedGenerateData() override; + void + AfterThreadedGenerateData() override; + void + DynamicThreadedGenerateData(const OutputRegionType & outputRegionForThread) override; + void + GenerateOutputInformation() override; + +private: + typename DigitizedImageType::Pointer m_DigitizedInputImage; + NeighborhoodRadiusType m_NeighborhoodRadius; + OffsetVectorPointer m_Offsets; + unsigned int m_NumberOfBinsPerAxis; + PixelType m_HistogramValueMinimum; + PixelType m_HistogramValueMaximum; + RealType m_HistogramDistanceMinimum; + RealType m_HistogramDistanceMaximum; + MaskPixelType m_InsidePixelValue; + typename TInputImage::SpacingType m_Spacing; +}; +} // end of namespace Statistics +} // end of namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkRunLengthTextureFeaturesImageFilter.hxx" +#endif + +#endif diff --git a/Modules/Filtering/TextureFeatures/include/itkRunLengthTextureFeaturesImageFilter.hxx b/Modules/Filtering/TextureFeatures/include/itkRunLengthTextureFeaturesImageFilter.hxx new file mode 100644 index 00000000000..d366f0406af --- /dev/null +++ b/Modules/Filtering/TextureFeatures/include/itkRunLengthTextureFeaturesImageFilter.hxx @@ -0,0 +1,481 @@ +/*========================================================================= + * + * 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 itkRunLengthTextureFeaturesImageFilter_hxx +#define itkRunLengthTextureFeaturesImageFilter_hxx + +#include "itkRunLengthTextureFeaturesImageFilter.h" +#include "itkRegionOfInterestImageFilter.h" +#include "itkNeighborhoodAlgorithm.h" +#include "itkBinaryFunctorImageFilter.h" +#include "itkDigitizerFunctor.h" + +namespace itk +{ +namespace Statistics +{ +template +RunLengthTextureFeaturesImageFilter::RunLengthTextureFeaturesImageFilter() + : m_NumberOfBinsPerAxis(Self::DefaultBinsPerAxis) + , m_HistogramValueMinimum(NumericTraits::NonpositiveMin()) + , m_HistogramValueMaximum(NumericTraits::max()) + , m_HistogramDistanceMinimum(NumericTraits::ZeroValue()) + , m_HistogramDistanceMaximum(NumericTraits::max()) + , m_InsidePixelValue(NumericTraits::OneValue()) + , m_Spacing(1.0) +{ + this->SetNumberOfRequiredInputs(1); + this->SetNumberOfRequiredOutputs(1); + + // Mark the "MaskImage" as an optional named input. First it has to + // be added to the list of named inputs then removed from the + // required list. + Self::AddRequiredInputName("MaskImage"); + Self::RemoveRequiredInputName("MaskImage"); + + // Set the offset directions to their defaults: half of all the possible + // directions 1 pixel away. (The other half is included by symmetry.) + // We use a neighborhood iterator to calculate the appropriate offsets. + using NeighborhoodType = Neighborhood; + NeighborhoodType hood; + hood.SetRadius(1); + + // Select all "previous" neighbors that are face+edge+vertex + // connected to the iterated pixel. Do not include the currentInNeighborhood pixel. + unsigned int centerIndex = hood.GetCenterNeighborhoodIndex(); + OffsetVectorPointer offsets = OffsetVector::New(); + for (unsigned int d = 0; d < centerIndex; ++d) + { + OffsetType offset = hood.GetOffset(d); + offsets->push_back(offset); + } + this->SetOffsets(offsets); + NeighborhoodType nhood; + nhood.SetRadius(2); + this->m_NeighborhoodRadius = nhood.GetRadius(); + this->DynamicMultiThreadingOn(); +} + +template +void +RunLengthTextureFeaturesImageFilter::SetOffset(const OffsetType offset) +{ + OffsetVectorPointer offsetVector = OffsetVector::New(); + offsetVector->push_back(offset); + this->SetOffsets(offsetVector); +} + +template +void +RunLengthTextureFeaturesImageFilter::BeforeThreadedGenerateData() +{ + + typename TInputImage::Pointer input = InputImageType::New(); + input->Graft(const_cast(this->GetInput())); + + using DigitizerFunctorType = Digitizer; + + DigitizerFunctorType digitalizer( + m_NumberOfBinsPerAxis, m_InsidePixelValue, m_HistogramValueMinimum, m_HistogramValueMaximum); + + using FilterType = BinaryFunctorImageFilter; + typename FilterType::Pointer filter = FilterType::New(); + if (this->GetMaskImage() != nullptr) + { + typename TMaskImage::Pointer mask = MaskImageType::New(); + mask->Graft(const_cast(this->GetMaskImage())); + filter->SetInput1(mask); + } + else + { + filter->SetConstant1(m_InsidePixelValue); + } + filter->SetInput2(input); + filter->SetFunctor(digitalizer); + filter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + filter->Update(); + m_DigitizedInputImage = filter->GetOutput(); + + m_Spacing = this->GetInput()->GetSpacing(); +} + + +template +void +RunLengthTextureFeaturesImageFilter::AfterThreadedGenerateData() +{ + // free internal image + this->m_DigitizedInputImage = nullptr; +} + + +template +void +RunLengthTextureFeaturesImageFilter::DynamicThreadedGenerateData( + const OutputRegionType & outputRegionForThread) +{ + // Get the inputs/outputs + TOutputImage * outputPtr = this->GetOutput(); + + // Creation of the output pixel type + typename TOutputImage::PixelType outputPixel; + NumericTraits::SetLength(outputPixel, outputPtr->GetNumberOfComponentsPerPixel()); + + // Creation of a region with the same size as the neighborhood. This region + // will be used to check if each voxel has already been visited. + InputRegionType boolRegion; + typename InputRegionType::IndexType boolStart; + typename InputRegionType::SizeType boolSize; + IndexType boolCurentInNeighborhoodIndex; + using BoolImageType = Image; + typename BoolImageType::Pointer alreadyVisitedImage = BoolImageType::New(); + for (unsigned int i = 0; i < this->m_NeighborhoodRadius.Dimension; ++i) + { + boolSize[i] = this->m_NeighborhoodRadius[i] * 2 + 1; + boolStart[i] = 0; + boolCurentInNeighborhoodIndex[i] = m_NeighborhoodRadius[i]; + } + boolRegion.SetIndex(boolStart); + boolRegion.SetSize(boolSize); + alreadyVisitedImage->CopyInformation(this->m_DigitizedInputImage); + alreadyVisitedImage->SetRegions(boolRegion); + alreadyVisitedImage->Allocate(); + + // Separation of the non-boundary region that will be processed in a different way + NeighborhoodAlgorithm::ImageBoundaryFacesCalculator boundaryFacesCalculator; + typename NeighborhoodAlgorithm::ImageBoundaryFacesCalculator::FaceListType faceList = + boundaryFacesCalculator(this->m_DigitizedInputImage, outputRegionForThread, m_NeighborhoodRadius); + auto fit = faceList.begin(); + + // Declaration of the variables useful to iterate over the all image region + bool isInImage; + typename OffsetVector::ConstIterator offsets; + + // Declaration of the variables useful to iterate over the all the offsets + OffsetType offset; + unsigned int totalNumberOfRuns; + + vnl_matrix histogram(m_NumberOfBinsPerAxis, m_NumberOfBinsPerAxis); + + + // Declaration of the variables useful to iterate over the all neighborhood region + HistogramIndexType currentInNeighborhoodPixelIntensity; + + // Declaration of the variables useful to iterate over the run + HistogramIndexType pixelIntensity(NumericTraits::ZeroValue()); + OffsetType iteratedOffset; + OffsetType tempOffset; + unsigned int pixelDistance; + bool insideNeighborhood; + + /// ***** Non-boundary Region ***** + for (; fit != faceList.end(); ++fit) + { + NeighborhoodIteratorType inputNIt(m_NeighborhoodRadius, this->m_DigitizedInputImage, *fit); + using IteratorType = itk::ImageRegionIterator; + IteratorType outputIt(outputPtr, *fit); + + // Iteration over the all image region + while (!inputNIt.IsAtEnd()) + { + // If the voxel is outside of the mask, don't treat it + if (inputNIt.GetCenterPixel() < (-5)) // the pixel is outside of the mask + { + outputPixel.Fill(0); + outputIt.Set(outputPixel); + ++inputNIt; + ++outputIt; + continue; + } + // Initialisation of the histogram + for (unsigned int a = 0; a < m_NumberOfBinsPerAxis; ++a) + { + for (unsigned int b = 0; b < m_NumberOfBinsPerAxis; ++b) + { + histogram[a][b] = 0; + } + } + totalNumberOfRuns = 0; + // Iteration over all the offsets + for (offsets = m_Offsets->Begin(); offsets != m_Offsets->End(); ++offsets) + { + alreadyVisitedImage->FillBuffer(false); + offset = offsets.Value(); + this->NormalizeOffsetDirection(offset); + // Iteration over the all neighborhood region + for (NeighborIndexType nb = 0; nb < inputNIt.Size(); ++nb) + { + currentInNeighborhoodPixelIntensity = inputNIt.GetPixel(nb); + tempOffset = inputNIt.GetOffset(nb); + // Checking if the value is out-of-bounds or is outside the mask. + if (currentInNeighborhoodPixelIntensity < 0 || // The pixel is outside of the mask or outside of bounds + alreadyVisitedImage->GetPixel(boolCurentInNeighborhoodIndex + tempOffset)) + { + continue; + } + // Initialisation of the variables useful to iterate over the run + iteratedOffset = tempOffset + offset; + pixelDistance = 0; + insideNeighborhood = this->IsInsideNeighborhood(iteratedOffset); + // Scan from the iterated pixel at index, following the direction of + // offset. Run length is computed as the length of continuous pixel + // whose pixel values are in the same bin. + while (insideNeighborhood) + { + // If the voxel reached is outside of the image, stop the iterations + if (fit == faceList.begin()) + { + inputNIt.GetPixel(iteratedOffset, isInImage); + if (!isInImage) + { + break; + } + } + pixelIntensity = inputNIt.GetPixel(iteratedOffset); + // Special attention paid to boundaries of bins. + // For the last bin, it is left close and right close (following the previous + // gerrit patch). For all other bins, the bin is left close and right open. + if (pixelIntensity == currentInNeighborhoodPixelIntensity) + { + alreadyVisitedImage->SetPixel(boolCurentInNeighborhoodIndex + iteratedOffset, true); + ++pixelDistance; + iteratedOffset += offset; + insideNeighborhood = this->IsInsideNeighborhood(iteratedOffset); + } + else + { + break; + } + } + // Increase the corresponding bin in the histogram + + this->IncreaseHistogram( + histogram, totalNumberOfRuns, currentInNeighborhoodPixelIntensity, offset, pixelDistance); + } + } + // Compute the run length features + this->ComputeFeatures(histogram, totalNumberOfRuns, outputPixel); + outputIt.Set(outputPixel); + + ++inputNIt; + ++outputIt; + } + } +} + +template +void +RunLengthTextureFeaturesImageFilter::GenerateOutputInformation() +{ + Superclass::GenerateOutputInformation(); + + OutputImageType * output = this->GetOutput(); + // If the output image type is a VectorImage the number of + // components will be properly sized if before allocation, if the + // output is a fixed width vector and the wrong number of + // components, then an exception will be thrown. + if (output->GetNumberOfComponentsPerPixel() != 10) + { + output->SetNumberOfComponentsPerPixel(10); + } +} + +template +void +RunLengthTextureFeaturesImageFilter::NormalizeOffsetDirection( + OffsetType & offset) +{ + itkDebugMacro("old offset = " << offset << std::endl); + int sign = 1; + bool metLastNonZero = false; + for (int i = offset.GetOffsetDimension() - 1; i >= 0; i--) + { + if (metLastNonZero) + { + offset[i] *= sign; + } + else if (offset[i] != 0) + { + sign = (offset[i] > 0) ? 1 : -1; + metLastNonZero = true; + offset[i] *= sign; + } + } + itkDebugMacro("new offset = " << offset << std::endl); +} + +template +bool +RunLengthTextureFeaturesImageFilter::IsInsideNeighborhood( + const OffsetType & iteratedOffset) +{ + bool insideNeighborhood = true; + for (unsigned int i = 0; i < this->m_NeighborhoodRadius.Dimension; ++i) + { + int boundDistance = m_NeighborhoodRadius[i] - Math::abs(iteratedOffset[i]); + if (boundDistance < 0) + { + insideNeighborhood = false; + break; + } + } + return insideNeighborhood; +} + +template +void +RunLengthTextureFeaturesImageFilter::IncreaseHistogram( + vnl_matrix & histogram, + unsigned int & totalNumberOfRuns, + const HistogramIndexType & currentInNeighborhoodPixelIntensity, + const OffsetType & offset, + const unsigned int & pixelDistance) +{ + float offsetDistance = 0; + for (unsigned int i = 0; i < offset.GetOffsetDimension(); ++i) + { + offsetDistance += (offset[i] * m_Spacing[i]) * (offset[i] * m_Spacing[i]); + } + offsetDistance = std::sqrt(offsetDistance); + auto offsetDistanceBin = + static_cast((offsetDistance * pixelDistance - m_HistogramDistanceMinimum) / + ((m_HistogramDistanceMaximum - m_HistogramDistanceMinimum) / (float)m_NumberOfBinsPerAxis)); + if (offsetDistanceBin < static_cast(m_NumberOfBinsPerAxis) && offsetDistanceBin >= 0) + { + ++totalNumberOfRuns; + ++histogram[currentInNeighborhoodPixelIntensity][offsetDistanceBin]; + } +} + +template +void +RunLengthTextureFeaturesImageFilter::ComputeFeatures( + vnl_matrix & histogram, + const unsigned int & totalNumberOfRuns, + typename TOutputImage::PixelType & outputPixel) +{ + OutputRealType shortRunEmphasis = NumericTraits::ZeroValue(); + OutputRealType longRunEmphasis = NumericTraits::ZeroValue(); + OutputRealType greyLevelNonuniformity = NumericTraits::ZeroValue(); + OutputRealType runLengthNonuniformity = NumericTraits::ZeroValue(); + OutputRealType lowGreyLevelRunEmphasis = NumericTraits::ZeroValue(); + OutputRealType highGreyLevelRunEmphasis = NumericTraits::ZeroValue(); + OutputRealType shortRunLowGreyLevelEmphasis = NumericTraits::ZeroValue(); + OutputRealType shortRunHighGreyLevelEmphasis = NumericTraits::ZeroValue(); + OutputRealType longRunLowGreyLevelEmphasis = NumericTraits::ZeroValue(); + OutputRealType longRunHighGreyLevelEmphasis = NumericTraits::ZeroValue(); + + vnl_vector greyLevelNonuniformityVector(m_NumberOfBinsPerAxis, 0.0); + vnl_vector runLengthNonuniformityVector(m_NumberOfBinsPerAxis, 0.0); + + for (unsigned int a = 0; a < m_NumberOfBinsPerAxis; ++a) + { + for (unsigned int b = 0; b < m_NumberOfBinsPerAxis; ++b) + { + OutputRealType frequency = histogram[a][b]; + if (Math::ExactlyEquals(frequency, NumericTraits::ZeroValue())) + { + continue; + } + + auto i2 = static_cast((a + 1) * (a + 1)); + auto j2 = static_cast((b + 1) * (b + 1)); + + // Traditional measures + shortRunEmphasis += (frequency / j2); + longRunEmphasis += (frequency * j2); + + greyLevelNonuniformityVector[a] += frequency; + runLengthNonuniformityVector[b] += frequency; + + // Measures from Chu et al. + lowGreyLevelRunEmphasis += (frequency / i2); + highGreyLevelRunEmphasis += (frequency * i2); + + // Measures from Dasarathy and Holder + shortRunLowGreyLevelEmphasis += (frequency / (i2 * j2)); + shortRunHighGreyLevelEmphasis += (frequency * i2 / j2); + longRunLowGreyLevelEmphasis += (frequency * j2 / i2); + longRunHighGreyLevelEmphasis += (frequency * i2 * j2); + } + } + greyLevelNonuniformity = greyLevelNonuniformityVector.squared_magnitude(); + runLengthNonuniformity = runLengthNonuniformityVector.squared_magnitude(); + + // Normalize all measures by the total number of runs + + shortRunEmphasis /= static_cast(totalNumberOfRuns); + longRunEmphasis /= static_cast(totalNumberOfRuns); + greyLevelNonuniformity /= static_cast(totalNumberOfRuns); + runLengthNonuniformity /= static_cast(totalNumberOfRuns); + + lowGreyLevelRunEmphasis /= static_cast(totalNumberOfRuns); + highGreyLevelRunEmphasis /= static_cast(totalNumberOfRuns); + + shortRunLowGreyLevelEmphasis /= static_cast(totalNumberOfRuns); + shortRunHighGreyLevelEmphasis /= static_cast(totalNumberOfRuns); + longRunLowGreyLevelEmphasis /= static_cast(totalNumberOfRuns); + longRunHighGreyLevelEmphasis /= static_cast(totalNumberOfRuns); + + outputPixel[0] = shortRunEmphasis; + outputPixel[1] = longRunEmphasis; + outputPixel[2] = greyLevelNonuniformity; + outputPixel[3] = runLengthNonuniformity; + outputPixel[4] = lowGreyLevelRunEmphasis; + outputPixel[5] = highGreyLevelRunEmphasis; + outputPixel[6] = shortRunLowGreyLevelEmphasis; + outputPixel[7] = shortRunHighGreyLevelEmphasis; + outputPixel[8] = longRunLowGreyLevelEmphasis; + outputPixel[9] = longRunHighGreyLevelEmphasis; +} + +template +void +RunLengthTextureFeaturesImageFilter::PrintSelf(std::ostream & os, + Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + itkPrintSelfObjectMacro(DigitizedInputImage); + + os << indent << "NeighborhoodRadius: " + << static_cast::PrintType>(m_NeighborhoodRadius) << std::endl; + + itkPrintSelfObjectMacro(Offsets); + + os << indent << "NumberOfBinsPerAxis: " << m_NumberOfBinsPerAxis << std::endl; + os << indent << "Min: " << static_cast::PrintType>(m_HistogramValueMinimum) + << std::endl; + os << indent << "Max: " << static_cast::PrintType>(m_HistogramValueMaximum) + << std::endl; + os << indent + << "MinDistance: " << static_cast::PrintType>(m_HistogramDistanceMinimum) + << std::endl; + os << indent + << "MaxDistance: " << static_cast::PrintType>(m_HistogramDistanceMaximum) + << std::endl; + os << indent << "InsidePixelValue: " << static_cast::PrintType>(m_InsidePixelValue) + << std::endl; + os << indent + << "Spacing: " << static_cast::PrintType>(m_Spacing) + << std::endl; +} +} // end of namespace Statistics +} // end of namespace itk + +#endif diff --git a/Modules/Filtering/TextureFeatures/itk-module.cmake b/Modules/Filtering/TextureFeatures/itk-module.cmake new file mode 100644 index 00000000000..cec84ba8bc4 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/itk-module.cmake @@ -0,0 +1,17 @@ +itk_module( + TextureFeatures + DEPENDS + ITKCommon + ITKStatistics + ITKImageGrid + ITKMathematicalMorphology + TEST_DEPENDS + ITKTestKernel + ITKMetaIO + ITKImageIntensity + ITKImageNoise + ITKGoogleTest + DESCRIPTION + "N-dimensional textural feature image filters: first-order, run-length, and co-occurrence (GLCM) features computed over a sliding window for radiomics and computer-vision pipelines." + EXCLUDE_FROM_DEFAULT +) diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage1.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage1.nrrd.cid new file mode 100644 index 00000000000..ea4fb3bb5e3 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage1.nrrd.cid @@ -0,0 +1 @@ +bafkreieqoy6zuybqpukawjye3d2gpsqupoiaocysx67yh3cutazoyx33be diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage2.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage2.nrrd.cid new file mode 100644 index 00000000000..d92f49112d7 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage2.nrrd.cid @@ -0,0 +1 @@ +bafkreigidvq27ucb3n66nm6bylexqcjjkco3urjq76cqxgtspde3iotk6m diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage3.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage3.nrrd.cid new file mode 100644 index 00000000000..d5128394753 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage3.nrrd.cid @@ -0,0 +1 @@ +bafkreidjnd3kqczulc2lpe4g2m63bjd2angxghrwzjycukypombolvywei diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage4.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage4.nrrd.cid new file mode 100644 index 00000000000..30fe3ec2057 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultPartialImage4.nrrd.cid @@ -0,0 +1 @@ +bafkreih6vvesdbheduxukuo6bof5ecnvjubyya2325mkbdemvdzitggd7m diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_0.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_0.nrrd.cid new file mode 100644 index 00000000000..ef63bfdbbe3 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_0.nrrd.cid @@ -0,0 +1 @@ +bafkreigiu6nyc6uuxily7w6epxsse24zjmq4wd24lub5igaweycxa3pqrq diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_1.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_1.nrrd.cid new file mode 100644 index 00000000000..3257fecbbe7 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_1.nrrd.cid @@ -0,0 +1 @@ +bafkreihi57ay4th7ge6vgs2nu33mnn7g5ys3asg6zyodfv52k7vax5qp3m diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_11.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_11.nrrd.cid new file mode 100644 index 00000000000..9ba7e46c05c --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_11.nrrd.cid @@ -0,0 +1 @@ +bafkreibwfa2yxnhgzetzd5fao7b5ptofgc7givdsqgiwm5s6ksrw5adiie diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_12.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_12.nrrd.cid new file mode 100644 index 00000000000..81ec47a8816 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_12.nrrd.cid @@ -0,0 +1 @@ +bafkreieaz4f4pw3xfzmauzp2uveaaku6v4rjpcpszffmubt4dwdxb7b4yy diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_13.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_13.nrrd.cid new file mode 100644 index 00000000000..40f7c26bcca --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_13.nrrd.cid @@ -0,0 +1 @@ +bafkreia4lyk5q6t734mdwe55ydevyvhod4p7l4ukkdmcvfakxkcufod6qm diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_14.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_14.nrrd.cid new file mode 100644 index 00000000000..27b50db2c45 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_14.nrrd.cid @@ -0,0 +1 @@ +bafkreifju6wt7gckka7kfwcot5nqal65jt5lisoajhwjdxiym77hz75vpe diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_15.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_15.nrrd.cid new file mode 100644 index 00000000000..9626a125d42 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_15.nrrd.cid @@ -0,0 +1 @@ +bafkreig3q4efde4i2hbxsg5tz224kgmp37tyi2o6qxmnvlmqkw265qmhmu diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_16.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_16.nrrd.cid new file mode 100644 index 00000000000..ccd3602610c --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_16.nrrd.cid @@ -0,0 +1 @@ +bafkreih5yz7h5lodawb7bsyvipb3f3yv77gclqikiov4xrdc3p3xbmq7b4 diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_17.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_17.nrrd.cid new file mode 100644 index 00000000000..380c4d9e40c --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_17.nrrd.cid @@ -0,0 +1 @@ +bafkreiclviqbaoqapce5fesanfccwdsxq6ky2cuvjzfp2antw74upktcki diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_18.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_18.nrrd.cid new file mode 100644 index 00000000000..15b72984319 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_18.nrrd.cid @@ -0,0 +1 @@ +bafkreibkydefzskqsjyxgnuf5l6zwfbrr36dfy7jwd5zgh2mrfknfzcx44 diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_2.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_2.nrrd.cid new file mode 100644 index 00000000000..07c3a64ae68 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_2.nrrd.cid @@ -0,0 +1 @@ +bafkreibcxzft3pj57wv7jonbkuikte7mhvhzs3xrerogarigoujqmzof5e diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_3.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_3.nrrd.cid new file mode 100644 index 00000000000..6ad4920342a --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_3.nrrd.cid @@ -0,0 +1 @@ +bafkreihz4frhrl2l5lj2kanzijvmhpic325qpznj5tvfik3fadwjugbydi diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_4.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_4.nrrd.cid new file mode 100644 index 00000000000..62f24831b27 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_4.nrrd.cid @@ -0,0 +1 @@ +bafkreicir2u4nyg4iqhq4riwpz4y36vuyti7gsboi72kphf5rlk4gtfpoi diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_5.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_5.nrrd.cid new file mode 100644 index 00000000000..f08572721de --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_5.nrrd.cid @@ -0,0 +1 @@ +bafkreiaxnk5a5i6iujizpkludyyp7jepyublr7mnc4tmzh4it55xmg7p5e diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_6.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_6.nrrd.cid new file mode 100644 index 00000000000..3e1a815b329 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_6.nrrd.cid @@ -0,0 +1 @@ +bafkreihnlgnqswqytbhokkkwg2au3byusqrlxr4z2agqjyn5wfjumbt5du diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_7.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_7.nrrd.cid new file mode 100644 index 00000000000..b5c0800014b --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_7.nrrd.cid @@ -0,0 +1 @@ +bafkreibid3omq626ubiphod2qc2v3k6higegt64xfb3d7iognybclx7mjm diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_8.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_8.nrrd.cid new file mode 100644 index 00000000000..459d618a439 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_8.nrrd.cid @@ -0,0 +1 @@ +bafkreib5nvhzsu4uuk4scjqc5x6a3ux36d6lmctihoyskthsmqr4evix3a diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_9.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_9.nrrd.cid new file mode 100644 index 00000000000..59bb35760a3 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultSeparateFeatures_9.nrrd.cid @@ -0,0 +1 @@ +bafkreigdnxtoiwvqqbdrwh3i3lkvk7th5ieck5rcdf6caszeuo4ic3posm diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask1.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask1.nrrd.cid new file mode 100644 index 00000000000..3e44784a4e4 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask1.nrrd.cid @@ -0,0 +1 @@ +bafkreidgeywjxwcvcy7lsjezicpqjnqous7mnwnc4or6axok4im422fjfy diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask2.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask2.nrrd.cid new file mode 100644 index 00000000000..d1812a394e4 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask2.nrrd.cid @@ -0,0 +1 @@ +bafkreiexanvvgdzwax535bq7ug2gs3t4xbvummid4hlx7aj6qhlghkwea4 diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask3.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask3.nrrd.cid new file mode 100644 index 00000000000..1ade7983152 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask3.nrrd.cid @@ -0,0 +1 @@ +bafkreia67vny4sjg4qtejp2yaefkyqetivjor7rheaxgdyqnxdgdolno5e diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask4.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask4.nrrd.cid new file mode 100644 index 00000000000..c5116229649 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask4.nrrd.cid @@ -0,0 +1 @@ +bafkreig7fct5rdfv7f37hocqpowwltt7fhy2qnq66u5wlfnn4fk2dm7t44 diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask5.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask5.nrrd.cid new file mode 100644 index 00000000000..a4ab68bf01d --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask5.nrrd.cid @@ -0,0 +1 @@ +bafkreicyneybl2xbnoft7i5ithlx3belf3azztjjk5s652kzy63aqx7y24 diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask6.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask6.nrrd.cid new file mode 100644 index 00000000000..0f15f133092 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultTestWithoutMask6.nrrd.cid @@ -0,0 +1 @@ +bafkreicbuqzfy2nqcpfvoqf4gucysfl52mzmcvab2ufevmwkpc6idjgufi diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultWholeImage.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultWholeImage.nrrd.cid new file mode 100644 index 00000000000..2945fe7e761 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultWholeImage.nrrd.cid @@ -0,0 +1 @@ +bafkreihfxf2ofq2jlcuq3s2ploditkh7sx4q56e7euy33p6yaowjh3tfp4 diff --git a/Modules/Filtering/TextureFeatures/test/Baseline/resultWholeImage2.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Baseline/resultWholeImage2.nrrd.cid new file mode 100644 index 00000000000..da315109369 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Baseline/resultWholeImage2.nrrd.cid @@ -0,0 +1 @@ +bafkreidmhhresse5jrsz3t2ep4fplmwdtez3bw2t4n7du7kxeb7mnu62j4 diff --git a/Modules/Filtering/TextureFeatures/test/CMakeLists.txt b/Modules/Filtering/TextureFeatures/test/CMakeLists.txt new file mode 100644 index 00000000000..31d95c9f469 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/CMakeLists.txt @@ -0,0 +1,504 @@ +itk_module_test() + +set( + TextureFeaturesTests + RunLengthTextureFeaturesImageFilterInstantiationTest.cxx + RunLengthTextureFeaturesImageFilterTest.cxx + RunLengthTextureFeaturesImageFilterTestSeparateFeatures.cxx + RunLengthTextureFeaturesImageFilterTestWithoutMask.cxx + RunLengthTextureFeaturesImageFilterTestWithVectorImage.cxx + RunLengthTextureFeaturesImageFilterTestVectorImageSeparateFeatures.cxx + CoocurrenceTextureFeaturesImageFilterInstantiationTest.cxx + CoocurrenceTextureFeaturesImageFilterTest.cxx + CoocurrenceTextureFeaturesImageFilterTestSeparateFeatures.cxx + CoocurrenceTextureFeaturesImageFilterTestWithoutMask.cxx + CoocurrenceTextureFeaturesImageFilterTestWithVectorImage.cxx + CoocurrenceTextureFeaturesImageFilterTestVectorImageSeparateFeatures.cxx + itkFirstOrderTextureFeaturesImageFilterTest.cxx +) + +createtestdriver(TextureFeatures "${TextureFeatures-Test_LIBRARIES}" "${TextureFeaturesTests}") + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterInstantiationTest + COMMAND + TextureFeaturesTestDriver + RunLengthTextureFeaturesImageFilterInstantiationTest + DATA{Input/Scan_CBCT_13R.nrrd} + DATA{Input/SegmC_CBCT_13R.nrrd} +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterTestWithoutMask1 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultTestWithoutMask1.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask1.nrrd + RunLengthTextureFeaturesImageFilterTestWithoutMask + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask1.nrrd + 10 + 0 + 4200 + 0 + 0.7 + 2 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterTestWithoutMask2 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultTestWithoutMask2.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask2.nrrd + RunLengthTextureFeaturesImageFilterTestWithoutMask + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask2.nrrd + 10 + 0 + 4200 + 0 + 1.25 + 4 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterTestWithoutMask3 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultTestWithoutMask3.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask3.nrrd + RunLengthTextureFeaturesImageFilterTestWithoutMask + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask3.nrrd + 10 + 0 + 4200 + 0 + 1.8 + 6 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterVectorlImage1 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultPartialImage1.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImage1.nrrd + RunLengthTextureFeaturesImageFilterTestWithVectorImage + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + DATA{Input/SegmC_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImage1.nrrd + 10 + 0 + 4200 + 0 + 0.7 + 2 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterVectorImage2 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultPartialImage2.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImage2.nrrd + RunLengthTextureFeaturesImageFilterTestWithVectorImage + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + DATA{Input/SegmC_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImage2.nrrd + 10 + 0 + 4200 + 0 + 1.25 + 4 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterPartialImage1 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultPartialImage1.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultPartialImage1.nrrd + RunLengthTextureFeaturesImageFilterTest + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + DATA{Input/SegmC_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultPartialImage1.nrrd + 10 + 0 + 4200 + 0 + 0.7 + 2 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterPartialImage2 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultPartialImage2.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultPartialImage2.nrrd + RunLengthTextureFeaturesImageFilterTest + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + DATA{Input/SegmC_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultPartialImage2.nrrd + 10 + 0 + 4200 + 0 + 1.25 + 4 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterTestWholeImage + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultWholeImage.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultWholeImage.nrrd + RunLengthTextureFeaturesImageFilterTest + DATA{Input/Scan_CBCT_13R.nrrd} + DATA{Input/SegmC_CBCT_13R.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultWholeImage.nrrd + 10 + 0 + 4200 + 0 + 0.7 + 2 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterTestSeparateFeatures + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultSeparateFeatures_1.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_1.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_2.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_2.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_3.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_3.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_4.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_4.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_5.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_5.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_6.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_6.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_7.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_7.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_8.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_8.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_9.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_9.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_0.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_0.nrrd + RunLengthTextureFeaturesImageFilterTestSeparateFeatures + DATA{Input/Scan_CBCT_13R.nrrd} + DATA{Input/SegmC_CBCT_13R.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures + 10 + 0 + 4200 + 0 + 1.25 + 4 +) + +itk_add_test( + NAME RunLengthTextureFeaturesImageFilterTestVectorImageSeparateFeatures + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultSeparateFeatures_1.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_1.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_2.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_2.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_3.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_3.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_4.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_4.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_5.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_5.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_6.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_6.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_7.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_7.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_8.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_8.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_9.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_9.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_0.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_0.nrrd + RunLengthTextureFeaturesImageFilterTestVectorImageSeparateFeatures + DATA{Input/Scan_CBCT_13R.nrrd} + DATA{Input/SegmC_CBCT_13R.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures + 10 + 0 + 4200 + 0 + 1.25 + 4 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterInstantiationTest + COMMAND + TextureFeaturesTestDriver + CoocurrenceTextureFeaturesImageFilterInstantiationTest + DATA{Input/Scan_CBCT_13R.nrrd} + DATA{Input/SegmC_CBCT_13R.nrrd} +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterTestWithoutMask1 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultTestWithoutMask4.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask4.nrrd + CoocurrenceTextureFeaturesImageFilterTestWithoutMask + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask4.nrrd + 10 + 0 + 4200 + 2 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterTestWithoutMask2 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultTestWithoutMask5.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask5.nrrd + CoocurrenceTextureFeaturesImageFilterTestWithoutMask + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask5.nrrd + 10 + 0 + 4200 + 4 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterTestWithoutMask3 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultTestWithoutMask6.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask6.nrrd + CoocurrenceTextureFeaturesImageFilterTestWithoutMask + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultTestWithoutMask6.nrrd + 10 + 0 + 4200 + 6 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterVectorlImage1 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultPartialImage3.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImage3.nrrd + CoocurrenceTextureFeaturesImageFilterTestWithVectorImage + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + DATA{Input/SegmC_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImage3.nrrd + 10 + 0 + 4200 + 2 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterVectorImage2 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultPartialImage4.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImage4.nrrd + CoocurrenceTextureFeaturesImageFilterTestWithVectorImage + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + DATA{Input/SegmC_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImage4.nrrd + 10 + 0 + 4200 + 4 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterPartialImage1 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultPartialImage3.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultPartialImage3.nrrd + CoocurrenceTextureFeaturesImageFilterTest + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + DATA{Input/SegmC_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultPartialImage3.nrrd + 10 + 0 + 4200 + 2 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterPartialImage2 + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultPartialImage4.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultPartialImage4.nrrd + CoocurrenceTextureFeaturesImageFilterTest + DATA{Input/Scan_CBCT_13R_D1_crop.nrrd} + DATA{Input/SegmC_CBCT_13R_D1_crop.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultPartialImage4.nrrd + 10 + 0 + 4200 + 4 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterTestWholeImage + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultWholeImage2.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultWholeImage2.nrrd + CoocurrenceTextureFeaturesImageFilterTest + DATA{Input/Scan_CBCT_13R.nrrd} + DATA{Input/SegmC_CBCT_13R.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultWholeImage2.nrrd + 10 + 0 + 4200 + 2 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterTestSeparateFeatures + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultSeparateFeatures_11.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_11.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_12.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_12.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_13.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_13.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_14.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_14.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_15.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_15.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_16.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_16.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_17.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_17.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_18.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures_18.nrrd + CoocurrenceTextureFeaturesImageFilterTestSeparateFeatures + DATA{Input/Scan_CBCT_13R.nrrd} + DATA{Input/SegmC_CBCT_13R.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultSeparateFeatures + 10 + 0 + 4200 + 4 +) + +itk_add_test( + NAME CoocurrenceTextureFeaturesImageFilterTestVectorImageSeparateFeatures + COMMAND + TextureFeaturesTestDriver + --compare + DATA{Baseline/resultSeparateFeatures_11.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_11.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_12.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_12.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_13.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_13.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_14.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_14.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_15.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_15.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_16.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_16.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_17.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_17.nrrd + --compare + DATA{Baseline/resultSeparateFeatures_18.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures_18.nrrd + CoocurrenceTextureFeaturesImageFilterTestVectorImageSeparateFeatures + DATA{Input/Scan_CBCT_13R.nrrd} + DATA{Input/SegmC_CBCT_13R.nrrd} + ${ITK_TEST_OUTPUT_DIR}/resultVectorImageSeparateFeatures + 10 + 0 + 4200 + 4 +) + +itk_add_test( + NAME itkFirstOrderTextureFeaturesImageFilterTest1 + COMMAND + TextureFeaturesTestDriver + itkFirstOrderTextureFeaturesImageFilterTest + DATA{Input/cthead1.png} + ${ITK_TEST_OUTPUT_DIR}/itkTextureFeatureImageFilterTest1.mha + 5 +) + +if(NOT "${ITK_VERSION_MAJOR}.${ITK_VERSION_MINOR}" VERSION_LESS "4.13") + set(TextureFeaturesGTests itkFirstOrderTextureFeaturesImageFilterGTest.cxx) + + creategoogletestdriver(TextureFeatures "${TextureFeatures-Test_LIBRARIES}" "${TextureFeaturesGTests}") +endif() diff --git a/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterInstantiationTest.cxx b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterInstantiationTest.cxx new file mode 100644 index 00000000000..6a008241bad --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterInstantiationTest.cxx @@ -0,0 +1,111 @@ +/*========================================================================= + * + * 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 "itkCoocurrenceTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVector.h" +#include "itkImageFileReader.h" +#include "itkTestingMacros.h" + +int +CoocurrenceTextureFeaturesImageFilterInstantiationTest(int argc, char * argv[]) +{ + if (argc < 3) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 8; + + // Declare types + using InputPixelType = int; + using MaskPixelType = unsigned char; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using MaskImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using MaskReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + std::string inputFilename = argv[1]; + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + MaskReaderType::Pointer maskReader = MaskReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = itk::Statistics::CoocurrenceTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + ITK_EXERCISE_BASIC_OBJECT_METHODS(filter, CoocurrenceTextureFeaturesImageFilter, ImageToImageFilter); + + + filter->SetInput(reader->GetOutput()); + + filter->SetMaskImage(maskReader->GetOutput()); + ITK_TEST_SET_GET_VALUE(maskReader->GetOutput(), filter->GetMaskImage()); + + unsigned int numberOfBinsPerAxis = 15; + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + ITK_TEST_SET_GET_VALUE(numberOfBinsPerAxis, filter->GetNumberOfBinsPerAxis()); + + + FilterType::PixelType min = -62; + FilterType::PixelType max = 2456; + filter->SetHistogramMinimum(min); + filter->SetHistogramMaximum(max); + ITK_TEST_SET_GET_VALUE(min, filter->GetHistogramMinimum()); + ITK_TEST_SET_GET_VALUE(max, filter->GetHistogramMaximum()); + + NeighborhoodType::SizeValueType neighborhoodRadius = 3; + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + ITK_TEST_SET_GET_VALUE(hood.GetRadius(), filter->GetNeighborhoodRadius()); + + FilterType::MaskPixelType insidePixelValue = 0; + filter->SetInsidePixelValue(insidePixelValue); + ITK_TEST_SET_GET_VALUE(insidePixelValue, filter->GetInsidePixelValue()); + + FilterType::OffsetType offset = { { -1, 0, 1 } }; + FilterType::OffsetVector::Pointer offsetVector = FilterType::OffsetVector::New(); + offsetVector->push_back(offset); + filter->SetOffsets(offsetVector); + ITK_TEST_SET_GET_VALUE(offsetVector, filter->GetOffsets()); + + filter->SetOffsets(offsetVector); + ITK_TEST_SET_GET_VALUE(offsetVector, filter->GetOffsets()); + + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTest.cxx b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTest.cxx new file mode 100644 index 00000000000..12da59f5bab --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTest.cxx @@ -0,0 +1,96 @@ +/*========================================================================= + * + * 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 "itkCoocurrenceTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVector.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkTestingMacros.h" + +int +CoocurrenceTextureFeaturesImageFilterTest(int argc, char * argv[]) +{ + if (argc < 3) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 8; + + // Declare types + using InputPixelType = float; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + ReaderType::Pointer maskReader = ReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = + itk::Statistics::CoocurrenceTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + filter->SetInput(reader->GetOutput()); + filter->SetMaskImage(maskReader->GetOutput()); + + if (argc >= 5) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[4]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[5]); + FilterType::PixelType pixelValueMax = std::stod(argv[6]); + filter->SetHistogramMinimum(pixelValueMin); + filter->SetHistogramMaximum(pixelValueMax); + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[7]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(argv[3]); + writer->SetInput(filter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestSeparateFeatures.cxx b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestSeparateFeatures.cxx new file mode 100644 index 00000000000..a1c8031692a --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestSeparateFeatures.cxx @@ -0,0 +1,119 @@ +/*========================================================================= + * + * 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 "itkCoocurrenceTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVector.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkVectorIndexSelectionCastImageFilter.h" +#include "itkTestingMacros.h" + +int +CoocurrenceTextureFeaturesImageFilterTestSeparateFeatures(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 8; + + // Declare types + using InputPixelType = int; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + ReaderType::Pointer maskReader = ReaderType::New(); + maskReader->SetFileName(argv[2]); + + + // Create the filter + using FilterType = + itk::Statistics::CoocurrenceTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + filter->SetInput(reader->GetOutput()); + filter->SetMaskImage(maskReader->GetOutput()); + + if (argc >= 5) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[4]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[5]); + FilterType::PixelType pixelValueMax = std::stod(argv[6]); + filter->SetHistogramMinimum(pixelValueMin); + filter->SetHistogramMaximum(pixelValueMax); + + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[7]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + + using FeatureImageType = itk::Image; + using IndexSelectionType = itk::VectorIndexSelectionCastImageFilter; + IndexSelectionType::Pointer indexSelectionFilter = IndexSelectionType::New(); + indexSelectionFilter->SetInput(filter->GetOutput()); + + for (unsigned int i = 0; i < VectorComponentDimension; i++) + { + indexSelectionFilter->SetIndex(i); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + std::string outputFilename = argv[3]; + std::ostringstream ss; + ss << i + 1; + std::string s = ss.str(); + writer->SetFileName(outputFilename + "_1" + s + ".nrrd"); + writer->SetInput(indexSelectionFilter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + } + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestVectorImageSeparateFeatures.cxx b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestVectorImageSeparateFeatures.cxx new file mode 100644 index 00000000000..bd324338369 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestVectorImageSeparateFeatures.cxx @@ -0,0 +1,118 @@ +/*========================================================================= + * + * 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 "itkCoocurrenceTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkImageAlgorithm.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkVectorIndexSelectionCastImageFilter.h" +#include "itkVectorImageToImageAdaptor.h" +#include "itkNthElementImageAdaptor.h" +#include "itkTestingMacros.h" + +int +CoocurrenceTextureFeaturesImageFilterTestVectorImageSeparateFeatures(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 8; + + // Declare types + using InputPixelType = float; + using OutputPixelType = float; + + using InputImageType = itk::Image; + using OutputImageType = itk::VectorImage; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + ReaderType::Pointer maskReader = ReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = + itk::Statistics::CoocurrenceTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + filter->SetInput(reader->GetOutput()); + filter->SetMaskImage(maskReader->GetOutput()); + + if (argc >= 5) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[4]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[5]); + FilterType::PixelType pixelValueMax = std::stod(argv[6]); + filter->SetHistogramMinimum(pixelValueMin); + filter->SetHistogramMaximum(pixelValueMax); + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[7]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + + using FeatureImageType = itk::Image; + using IndexSelectionType = itk::VectorIndexSelectionCastImageFilter; + IndexSelectionType::Pointer indexSelectionFilter = IndexSelectionType::New(); + indexSelectionFilter->SetInput(filter->GetOutput()); + + for (unsigned int i = 0; i < VectorComponentDimension; i++) + { + indexSelectionFilter->SetIndex(i); + + // Create and setup a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + std::string outputFilename = argv[3]; + std::ostringstream ss; + ss << i + 1; + std::string s = ss.str(); + writer->SetFileName(outputFilename + "_1" + s + ".nrrd"); + writer->SetInput(indexSelectionFilter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + } + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestWithVectorImage.cxx b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestWithVectorImage.cxx new file mode 100644 index 00000000000..74f1a1239d2 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestWithVectorImage.cxx @@ -0,0 +1,102 @@ +/*========================================================================= + * + * 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 "itkCoocurrenceTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVectorImage.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkTestingMacros.h" + +int +CoocurrenceTextureFeaturesImageFilterTestWithVectorImage(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + + // Declare types + using InputPixelType = int; + using OutputPixelType = float; + + using InputImageType = itk::Image; + using OutputImageType = itk::VectorImage; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + ReaderType::Pointer maskReader = ReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = + itk::Statistics::CoocurrenceTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + ITK_EXERCISE_BASIC_OBJECT_METHODS(filter, CoocurrenceTextureFeaturesImageFilter, ImageToImageFilter); + + + filter->SetInput(reader->GetOutput()); + filter->SetMaskImage(maskReader->GetOutput()); + + if (argc >= 5) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[4]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[5]); + FilterType::PixelType pixelValueMax = std::stod(argv[6]); + filter->SetHistogramMinimum(pixelValueMin); + filter->SetHistogramMaximum(pixelValueMax); + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[7]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(argv[3]); + writer->SetInput(filter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestWithoutMask.cxx b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestWithoutMask.cxx new file mode 100644 index 00000000000..cca95730159 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/CoocurrenceTextureFeaturesImageFilterTestWithoutMask.cxx @@ -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. + * + *=========================================================================*/ +#include "itkCoocurrenceTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVector.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkTestingMacros.h" + +int +CoocurrenceTextureFeaturesImageFilterTestWithoutMask(int argc, char * argv[]) +{ + if (argc < 3) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 8; + + // Declare types + using InputPixelType = float; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create the filter + using FilterType = itk::Statistics::CoocurrenceTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + filter->SetInput(reader->GetOutput()); + + if (argc >= 4) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[3]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[4]); + FilterType::PixelType pixelValueMax = std::stod(argv[5]); + filter->SetHistogramMinimum(pixelValueMin); + filter->SetHistogramMaximum(pixelValueMax); + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[6]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(argv[2]); + writer->SetInput(filter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/Input/Scan_CBCT_13R.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Input/Scan_CBCT_13R.nrrd.cid new file mode 100644 index 00000000000..5b9dae5e334 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Input/Scan_CBCT_13R.nrrd.cid @@ -0,0 +1 @@ +bafkreid4rlyt3wo75vmo2gtvjlafeurrosreelglyuzao4y4iogfosfm5i diff --git a/Modules/Filtering/TextureFeatures/test/Input/Scan_CBCT_13R_D1_crop.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Input/Scan_CBCT_13R_D1_crop.nrrd.cid new file mode 100644 index 00000000000..10f944714e9 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Input/Scan_CBCT_13R_D1_crop.nrrd.cid @@ -0,0 +1 @@ +bafkreidfez4stfcamwt7xwm4imamlxamdp5guyzbqglctmwahcnqfik2xu diff --git a/Modules/Filtering/TextureFeatures/test/Input/SegmC_CBCT_13R.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Input/SegmC_CBCT_13R.nrrd.cid new file mode 100644 index 00000000000..b9ab2b62d24 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Input/SegmC_CBCT_13R.nrrd.cid @@ -0,0 +1 @@ +bafkreict23shcetpoj22obkpg3ke3ltlqyymp7m2wzgndkmdoh6liq3yne diff --git a/Modules/Filtering/TextureFeatures/test/Input/SegmC_CBCT_13R_D1_crop.nrrd.cid b/Modules/Filtering/TextureFeatures/test/Input/SegmC_CBCT_13R_D1_crop.nrrd.cid new file mode 100644 index 00000000000..e4488d33abe --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Input/SegmC_CBCT_13R_D1_crop.nrrd.cid @@ -0,0 +1 @@ +bafkreifwpvyouggt2shr3vajozfp33h2eglcanvntk7ofw2fj4drrnlulq diff --git a/Modules/Filtering/TextureFeatures/test/Input/cthead1.png.cid b/Modules/Filtering/TextureFeatures/test/Input/cthead1.png.cid new file mode 100644 index 00000000000..fed459e534a --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/Input/cthead1.png.cid @@ -0,0 +1 @@ +bafkreieilrgrns7xcna5yle2yw3q2wxkl3wn7lp22k4d4pwjhlhwc3ed4u diff --git a/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterInstantiationTest.cxx b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterInstantiationTest.cxx new file mode 100644 index 00000000000..24abcd21020 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterInstantiationTest.cxx @@ -0,0 +1,117 @@ +/*========================================================================= + * + * 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 "itkRunLengthTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVector.h" +#include "itkImageFileReader.h" +#include "itkTestingMacros.h" + +int +RunLengthTextureFeaturesImageFilterInstantiationTest(int argc, char * argv[]) +{ + if (argc < 3) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 10; + + // Declare types + using InputPixelType = int; + using MaskPixelType = unsigned char; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using MaskImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using MaskReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + MaskReaderType::Pointer maskReader = MaskReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = itk::Statistics::RunLengthTextureFeaturesImageFilter; + + FilterType::Pointer filter = FilterType::New(); + + ITK_EXERCISE_BASIC_OBJECT_METHODS(filter, RunLengthTextureFeaturesImageFilter, ImageToImageFilter); + + + filter->SetInput(reader->GetOutput()); + + filter->SetMaskImage(maskReader->GetOutput()); + ITK_TEST_SET_GET_VALUE(maskReader->GetOutput(), filter->GetMaskImage()); + + unsigned int numberOfBinsPerAxis = 15; + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + ITK_TEST_SET_GET_VALUE(numberOfBinsPerAxis, filter->GetNumberOfBinsPerAxis()); + + FilterType::PixelType pixelValueMin = -62; + FilterType::PixelType pixelValueMax = 2456; + filter->SetHistogramValueMinimum(pixelValueMin); + filter->SetHistogramValueMaximum(pixelValueMax); + ITK_TEST_SET_GET_VALUE(pixelValueMin, filter->GetHistogramValueMinimum()); + ITK_TEST_SET_GET_VALUE(pixelValueMax, filter->GetHistogramValueMaximum()); + + FilterType::RealType minDistance = 0.15; + FilterType::RealType maxDistance = 1.5; + filter->SetHistogramDistanceMinimum(minDistance); + filter->SetHistogramDistanceMaximum(maxDistance); + ITK_TEST_SET_GET_VALUE(minDistance, filter->GetHistogramDistanceMinimum()); + ITK_TEST_SET_GET_VALUE(maxDistance, filter->GetHistogramDistanceMaximum()); + + NeighborhoodType::SizeValueType neighborhoodRadius = 3; + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + ITK_TEST_SET_GET_VALUE(hood.GetRadius(), filter->GetNeighborhoodRadius()); + + FilterType::MaskPixelType insidePixelValue = 0; + filter->SetInsidePixelValue(insidePixelValue); + ITK_TEST_SET_GET_VALUE(insidePixelValue, filter->GetInsidePixelValue()); + + FilterType::OffsetType offset = { { -1, 0, 1 } }; + FilterType::OffsetVector::Pointer offsetVector = FilterType::OffsetVector::New(); + offsetVector->push_back(offset); + filter->SetOffsets(offsetVector); + ITK_TEST_SET_GET_VALUE(offsetVector, filter->GetOffsets()); + + filter->SetOffsets(offsetVector); + ITK_TEST_SET_GET_VALUE(offsetVector, filter->GetOffsets()); + + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTest.cxx b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTest.cxx new file mode 100644 index 00000000000..2c6eb197a35 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTest.cxx @@ -0,0 +1,119 @@ +/*========================================================================= + * + * 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 "itkRunLengthTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVector.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkTestingMacros.h" + +int +RunLengthTextureFeaturesImageFilterTest(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [minDistance]" + << " [maxDistance]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 10; + + // Declare types + using InputPixelType = float; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + ReaderType::Pointer maskReader = ReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = + itk::Statistics::RunLengthTextureFeaturesImageFilter; + + FilterType::Pointer filter = FilterType::New(); + + ITK_EXERCISE_BASIC_OBJECT_METHODS(filter, RunLengthTextureFeaturesImageFilter, ImageToImageFilter); + + + filter->SetInput(reader->GetOutput()); + filter->SetMaskImage(maskReader->GetOutput()); + ITK_TEST_SET_GET_VALUE(maskReader->GetOutput(), filter->GetMaskImage()); + + if (argc >= 5) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[4]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + ITK_TEST_SET_GET_VALUE(numberOfBinsPerAxis, filter->GetNumberOfBinsPerAxis()); + + FilterType::PixelType pixelValueMin = std::stod(argv[5]); + FilterType::PixelType pixelValueMax = std::stod(argv[6]); + filter->SetHistogramValueMinimum(pixelValueMin); + filter->SetHistogramValueMaximum(pixelValueMax); + ITK_TEST_SET_GET_VALUE(pixelValueMin, filter->GetHistogramValueMinimum()); + ITK_TEST_SET_GET_VALUE(pixelValueMax, filter->GetHistogramValueMaximum()); + + FilterType::RealType minDistance = std::stod(argv[7]); + FilterType::RealType maxDistance = std::stod(argv[8]); + filter->SetHistogramDistanceMinimum(minDistance); + filter->SetHistogramDistanceMaximum(maxDistance); + ITK_TEST_SET_GET_VALUE(minDistance, filter->GetHistogramDistanceMinimum()); + ITK_TEST_SET_GET_VALUE(maxDistance, filter->GetHistogramDistanceMaximum()); + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[9]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + ITK_TEST_SET_GET_VALUE(hood.GetRadius(), filter->GetNeighborhoodRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(argv[3]); + writer->SetInput(filter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestSeparateFeatures.cxx b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestSeparateFeatures.cxx new file mode 100644 index 00000000000..6b0026e2507 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestSeparateFeatures.cxx @@ -0,0 +1,126 @@ +/*========================================================================= + * + * 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 "itkRunLengthTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVector.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkVectorIndexSelectionCastImageFilter.h" +#include "itkTestingMacros.h" + +int +RunLengthTextureFeaturesImageFilterTestSeparateFeatures(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [minDistance]" + << " [maxDistance]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 10; + + // Declare types + using InputPixelType = float; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + ReaderType::Pointer maskReader = ReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = + itk::Statistics::RunLengthTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + filter->SetInput(reader->GetOutput()); + filter->SetMaskImage(maskReader->GetOutput()); + + if (argc >= 5) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[4]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[5]); + FilterType::PixelType pixelValueMax = std::stod(argv[6]); + filter->SetHistogramValueMinimum(pixelValueMin); + filter->SetHistogramValueMaximum(pixelValueMax); + + FilterType::RealType minDistance = std::stod(argv[7]); + FilterType::RealType maxDistance = std::stod(argv[8]); + filter->SetHistogramDistanceMinimum(minDistance); + filter->SetHistogramDistanceMaximum(maxDistance); + + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[9]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + + using FeatureImageType = itk::Image; + using IndexSelectionType = itk::VectorIndexSelectionCastImageFilter; + IndexSelectionType::Pointer indexSelectionFilter = IndexSelectionType::New(); + indexSelectionFilter->SetInput(filter->GetOutput()); + + for (unsigned int i = 0; i < VectorComponentDimension; i++) + { + indexSelectionFilter->SetIndex(i); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + std::string outputFilename = argv[3]; + std::ostringstream ss; + ss << i; + std::string s = ss.str(); + writer->SetFileName(outputFilename + "_" + s + ".nrrd"); + writer->SetInput(indexSelectionFilter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + } + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestVectorImageSeparateFeatures.cxx b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestVectorImageSeparateFeatures.cxx new file mode 100644 index 00000000000..2e8c852c36c --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestVectorImageSeparateFeatures.cxx @@ -0,0 +1,126 @@ +/*========================================================================= + * + * 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 "itkRunLengthTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkImageAlgorithm.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkVectorIndexSelectionCastImageFilter.h" +#include "itkVectorImageToImageAdaptor.h" +#include "itkNthElementImageAdaptor.h" +#include "itkTestingMacros.h" + +int +RunLengthTextureFeaturesImageFilterTestVectorImageSeparateFeatures(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [minDistance]" + << " [maxDistance]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 10; + + // Declare types + using InputPixelType = int; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + ReaderType::Pointer maskReader = ReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = + itk::Statistics::RunLengthTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + filter->SetInput(reader->GetOutput()); + filter->SetMaskImage(maskReader->GetOutput()); + + if (argc >= 5) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[4]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[5]); + FilterType::PixelType pixelValueMax = std::stod(argv[6]); + filter->SetHistogramValueMinimum(pixelValueMin); + filter->SetHistogramValueMaximum(pixelValueMax); + + FilterType::RealType minDistance = std::stod(argv[7]); + FilterType::RealType maxDistance = std::stod(argv[8]); + filter->SetHistogramDistanceMinimum(minDistance); + filter->SetHistogramDistanceMaximum(maxDistance); + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[9]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + + using FeatureImageType = itk::Image; + using IndexSelectionType = itk::VectorIndexSelectionCastImageFilter; + IndexSelectionType::Pointer indexSelectionFilter = IndexSelectionType::New(); + indexSelectionFilter->SetInput(filter->GetOutput()); + + for (unsigned int i = 0; i < VectorComponentDimension; i++) + { + indexSelectionFilter->SetIndex(i); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + std::string outputFilename = argv[3]; + std::ostringstream ss; + ss << i; + std::string s = ss.str(); + writer->SetFileName(outputFilename + "_" + s + ".nrrd"); + writer->SetInput(indexSelectionFilter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + } + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestWithVectorImage.cxx b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestWithVectorImage.cxx new file mode 100644 index 00000000000..55292813fe8 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestWithVectorImage.cxx @@ -0,0 +1,110 @@ +/*========================================================================= + * + * 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 "itkRunLengthTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVectorImage.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkTestingMacros.h" + +int +RunLengthTextureFeaturesImageFilterTestWithVectorImage(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " maskImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [minDistance]" + << " [maxDistance]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + + // Declare types + using InputPixelType = int; + using OutputPixelType = float; + + using InputImageType = itk::Image; + using OutputImageType = itk::VectorImage; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + // Create and set up a maskReader + ReaderType::Pointer maskReader = ReaderType::New(); + maskReader->SetFileName(argv[2]); + + // Create the filter + using FilterType = + itk::Statistics::RunLengthTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + ITK_EXERCISE_BASIC_OBJECT_METHODS(filter, RunLengthTextureFeaturesImageFilter, ImageToImageFilter); + + + filter->SetInput(reader->GetOutput()); + filter->SetMaskImage(maskReader->GetOutput()); + + if (argc >= 5) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[4]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[5]); + FilterType::PixelType pixelValueMax = std::stod(argv[6]); + filter->SetHistogramValueMinimum(pixelValueMin); + filter->SetHistogramValueMaximum(pixelValueMax); + + + FilterType::RealType minDistance = std::stod(argv[7]); + FilterType::RealType maxDistance = std::stod(argv[8]); + filter->SetHistogramDistanceMinimum(minDistance); + filter->SetHistogramDistanceMaximum(maxDistance); + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[9]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(argv[3]); + writer->SetInput(filter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestWithoutMask.cxx b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestWithoutMask.cxx new file mode 100644 index 00000000000..621b282b037 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/RunLengthTextureFeaturesImageFilterTestWithoutMask.cxx @@ -0,0 +1,102 @@ +/*========================================================================= + * + * 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 "itkRunLengthTextureFeaturesImageFilter.h" + +#include "itkImage.h" +#include "itkVector.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkNeighborhood.h" +#include "itkTestingMacros.h" + +int +RunLengthTextureFeaturesImageFilterTestWithoutMask(int argc, char * argv[]) +{ + if (argc < 3) + { + std::cerr << "Missing parameters." << std::endl; + std::cerr << "Usage: " << argv[0] << " inputImageFile" + << " outputImageFile" + << " [numberOfBinsPerAxis]" + << " [pixelValueMin]" + << " [pixelValueMax]" + << " [minDistance]" + << " [maxDistance]" + << " [neighborhoodRadius]" << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int ImageDimension = 3; + constexpr unsigned int VectorComponentDimension = 10; + + // Declare types + using InputPixelType = float; + using OutputPixelComponentType = float; + using OutputPixelType = itk::Vector; + + using InputImageType = itk::Image; + using OutputImageType = itk::Image; + using ReaderType = itk::ImageFileReader; + using NeighborhoodType = itk::Neighborhood; + + // Create and set up a reader + ReaderType::Pointer reader = ReaderType::New(); + std::string inputFilename = argv[1]; + reader->SetFileName(argv[1]); + + // Create the filter + using FilterType = itk::Statistics::RunLengthTextureFeaturesImageFilter; + FilterType::Pointer filter = FilterType::New(); + + filter->SetInput(reader->GetOutput()); + + if (argc >= 4) + { + unsigned int numberOfBinsPerAxis = std::stoi(argv[3]); + filter->SetNumberOfBinsPerAxis(numberOfBinsPerAxis); + + FilterType::PixelType pixelValueMin = std::stod(argv[4]); + FilterType::PixelType pixelValueMax = std::stod(argv[5]); + filter->SetHistogramValueMinimum(pixelValueMin); + filter->SetHistogramValueMaximum(pixelValueMax); + + FilterType::RealType minDistance = std::stod(argv[6]); + FilterType::RealType maxDistance = std::stod(argv[7]); + filter->SetHistogramDistanceMinimum(minDistance); + filter->SetHistogramDistanceMaximum(maxDistance); + + NeighborhoodType::SizeValueType neighborhoodRadius = std::stoi(argv[8]); + NeighborhoodType hood; + hood.SetRadius(neighborhoodRadius); + filter->SetNeighborhoodRadius(hood.GetRadius()); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update()); + + // Create and set up a writer + using WriterType = itk::ImageFileWriter; + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(argv[2]); + writer->SetInput(filter->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/test/itkFirstOrderTextureFeaturesImageFilterGTest.cxx b/Modules/Filtering/TextureFeatures/test/itkFirstOrderTextureFeaturesImageFilterGTest.cxx new file mode 100644 index 00000000000..58177d693b6 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/itkFirstOrderTextureFeaturesImageFilterGTest.cxx @@ -0,0 +1,181 @@ +/*========================================================================= + * + * 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 "itkFirstOrderTextureFeaturesImageFilter.h" +#include "itkFlatStructuringElement.h" +#include "itkAdditiveGaussianNoiseImageFilter.h" +#include "itkSimpleFilterWatcher.h" + +#include "gtest/gtest.h" + +namespace +{ + +template +void +print_feature(const T & p, std::ostream & out = std::cout) +{ + out << "mean: " << p[0] << std::endl; + out << "minimum: " << p[1] << std::endl; + out << "maximum: " << p[2] << std::endl; + out << "variance: " << p[3] << std::endl; + out << "standard deviation: " << p[4] << std::endl; + out << "skewness: " << p[5] << std::endl; + out << "kurtosis: " << p[6] << std::endl; + out << "entropy: " << p[7] << std::endl; +} + +} // namespace + +TEST(TextureFeatures, FirstOrder_Test1) +{ + constexpr unsigned int ImageDimension = 2; + using ImageType = itk::Image; + using OImageType = itk::Image, ImageDimension>; + using KernelType = itk::FlatStructuringElement; + + + unsigned int r = 50u; + unsigned int d = r * 2 + 1; + ImageType::SizeType imageSize = { { d, d } }; + ImageType::SpacingValueType imageSpacing[] = { 1.0f, 1.0f }; + + ImageType::Pointer image = ImageType::New(); + + image->SetRegions(ImageType::RegionType(imageSize)); + image->SetSpacing(imageSpacing); + image->Allocate(); + image->FillBuffer(0); + + + using ImageNoiseType = itk::AdditiveGaussianNoiseImageFilter; + ImageNoiseType::Pointer noiseFilter = ImageNoiseType::New(); + noiseFilter->SetSeed(124); + noiseFilter->SetMean(0.0); + noiseFilter->SetStandardDeviation(.1); + noiseFilter->SetInput(image); + + using TextureFilterType = itk::FirstOrderTextureFeaturesImageFilter; + + KernelType::SizeType radius; + radius.Fill(r); + KernelType kernel = KernelType::Box(radius); + TextureFilterType::Pointer filter = TextureFilterType::New(); + filter->SetKernel(kernel); + filter->SetInput(noiseFilter->GetOutput()); + + itk::SimpleFilterWatcher watcher(filter, "filter"); + + + ImageType::SizeType requestSize = { { 10, 10 } }; + ImageType::IndexType requestIndex = { { 45, 45 } }; + ImageType::RegionType request(requestIndex, requestSize); + filter->GetOutput()->SetRequestedRegion(request); + filter->Update(); + + OImageType::ConstPointer output = filter->GetOutput(); + + { + ImageType::IndexType idx = { { r, r } }; + const OImageType::PixelType & p = output->GetPixel(idx); + + print_feature(p); + + // The following is for a Gaussian sample with std dev of .1 + // The expected value was analytically computed, while the + // tolerances were estimated based on 10,000 different sample set + // distributions in numpy. + EXPECT_NEAR(0.0, p[0], 0.002) << "mean"; + EXPECT_GT(-.3, p[1]) << "min"; + EXPECT_LT(.3, p[2]) << "max"; + EXPECT_NEAR(0.01, p[3], .001) << "variance"; + EXPECT_NEAR(0.1, p[4], .01) << "standard deviation"; + EXPECT_NEAR(0, p[5], .1) << "skewness"; + EXPECT_NEAR(0, p[6], .2) << "kurtosis"; + EXPECT_NEAR(13.3, p[7], .1) << "entropy"; + } +} + + +TEST(TextureFeatures, FirstOrder_Test2) +{ + constexpr unsigned int ImageDimension = 2; + using ImageType = itk::Image; + using OImageType = itk::Image, ImageDimension>; + using KernelType = itk::FlatStructuringElement; + + + unsigned int r = 50u; + unsigned int d = r * 2 + 1; + ImageType::SizeType imageSize = { { d, d } }; + ImageType::SpacingValueType imageSpacing[] = { 1.0f, 1.0f }; + + ImageType::Pointer image = ImageType::New(); + + image->SetRegions(ImageType::RegionType(imageSize)); + image->SetSpacing(imageSpacing); + image->Allocate(); + image->FillBuffer(0); + + + using ImageNoiseType = itk::AdditiveGaussianNoiseImageFilter; + ImageNoiseType::Pointer noiseFilter = ImageNoiseType::New(); + noiseFilter->SetSeed(124); + noiseFilter->SetMean(100.0); + noiseFilter->SetStandardDeviation(1); + noiseFilter->SetInput(image); + + using TextureFilterType = itk::FirstOrderTextureFeaturesImageFilter; + + KernelType::SizeType radius; + radius.Fill(r); + KernelType kernel = KernelType::Box(radius); + TextureFilterType::Pointer filter = TextureFilterType::New(); + filter->SetKernel(kernel); + filter->SetInput(noiseFilter->GetOutput()); + + itk::SimpleFilterWatcher watcher(filter, "filter"); + + + ImageType::SizeType requestSize = { { 10, 10 } }; + ImageType::IndexType requestIndex = { { 45, 45 } }; + ImageType::RegionType request(requestIndex, requestSize); + filter->GetOutput()->SetRequestedRegion(request); + filter->Update(); + + OImageType::ConstPointer output = filter->GetOutput(); + + { + ImageType::IndexType idx = { { r, r } }; + const OImageType::PixelType & p = output->GetPixel(idx); + + print_feature(p); + + // The following is for a Gaussian sample with std dev of .1 + // The expected value was analytically computed, while the + // tolerances were estimated based on 10,000 different sample set + // distributions in numpy. + EXPECT_NEAR(100, p[0], 0.02) << "mean"; + EXPECT_GT(97, p[1]) << "min"; + EXPECT_LT(103, p[2]) << "max"; + EXPECT_NEAR(1, p[3], .1) << "variance"; + EXPECT_NEAR(1, p[4], .1) << "standard deviation"; + EXPECT_NEAR(0, p[5], .1) << "skewness"; + EXPECT_NEAR(0, p[6], .2) << "kurtosis"; + EXPECT_NEAR(13.3, p[7], .2) << "entropy"; + } +} diff --git a/Modules/Filtering/TextureFeatures/test/itkFirstOrderTextureFeaturesImageFilterTest.cxx b/Modules/Filtering/TextureFeatures/test/itkFirstOrderTextureFeaturesImageFilterTest.cxx new file mode 100644 index 00000000000..a9b86a1b59e --- /dev/null +++ b/Modules/Filtering/TextureFeatures/test/itkFirstOrderTextureFeaturesImageFilterTest.cxx @@ -0,0 +1,103 @@ +/*========================================================================= + * + * 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 "itkFirstOrderTextureFeaturesImageFilter.h" +#include "itkFlatStructuringElement.h" +#include "itkImage.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" + + +static void +Test1(const std::string & inFileName, const std::string & outFileName) +{ + constexpr unsigned int ImageDimension = 3; + using ImageType = itk::Image; + using OImageType = itk::Image, ImageDimension>; + using KernelType = itk::FlatStructuringElement; + + using TextureFilterType = itk::FirstOrderTextureFeaturesImageFilter; + + + using ReaderType = itk::ImageFileReader; + + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(inFileName); + reader->UpdateLargestPossibleRegion(); + + KernelType::SizeType radius; + radius.Fill(5); + KernelType kernel = KernelType::Box(radius); + TextureFilterType::Pointer filter = TextureFilterType::New(); + filter->SetKernel(kernel); + filter->SetInput(reader->GetOutput()); + filter->UpdateLargestPossibleRegion(); + + using WriterType = itk::ImageFileWriter; + + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(outFileName); + writer->SetInput(filter->GetOutput()); + writer->Update(); +} + + +static void +Test2(std::string inFileName) +{ + constexpr unsigned int ImageDimension = 3; + using ImageType = itk::Image; + using OImageType = itk::VectorImage; + using KernelType = itk::FlatStructuringElement; + + using TextureFilterType = itk::FirstOrderTextureFeaturesImageFilter; + + + using ReaderType = itk::ImageFileReader; + + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(inFileName); + reader->UpdateLargestPossibleRegion(); + + KernelType::SizeType radius; + radius.Fill(5); + KernelType kernel = KernelType::Box(radius); + TextureFilterType::Pointer filter = TextureFilterType::New(); + filter->SetKernel(kernel); + filter->SetInput(reader->GetOutput()); + filter->UpdateLargestPossibleRegion(); + + std::cout << "filter..." << std::endl; +} + +int +itkFirstOrderTextureFeaturesImageFilterTest(int argc, char * argv[]) +{ + + if (argc < 2) + { + std::cerr << "Usage: " << std::endl; + std::cerr << argv[0] << " inputImageFile outputImageFile" << std::endl; + return EXIT_FAILURE; + } + + + Test1(argv[1], argv[2]); + Test2(argv[1]); + + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/TextureFeatures/wrapping/CMakeLists.txt b/Modules/Filtering/TextureFeatures/wrapping/CMakeLists.txt new file mode 100644 index 00000000000..001b280b81f --- /dev/null +++ b/Modules/Filtering/TextureFeatures/wrapping/CMakeLists.txt @@ -0,0 +1,10 @@ +itk_wrap_module(TextureFeatures) + +set( + WRAPPER_SUBMODULE_ORDER + itkCoocurrenceTextureFeaturesImageFilter + itkRunLengthTextureFeaturesImageFilter +) + +itk_auto_load_submodules() +itk_end_wrap_module() diff --git a/Modules/Filtering/TextureFeatures/wrapping/itkCoocurrenceTextureFeaturesImageFilter.wrap b/Modules/Filtering/TextureFeatures/wrapping/itkCoocurrenceTextureFeaturesImageFilter.wrap new file mode 100644 index 00000000000..94f5b26aa7e --- /dev/null +++ b/Modules/Filtering/TextureFeatures/wrapping/itkCoocurrenceTextureFeaturesImageFilter.wrap @@ -0,0 +1,62 @@ +set(OutputVectorDim 8) + +itk_wrap_class("itk::Vector") +itk_wrap_template("${ITKM_F}${OutputVectorDim}" "${ITKT_F},${OutputVectorDim}") +itk_end_wrap_class() + +wrap_type("itk::Image" "I" "itkImage.h") +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + add_template("V${ITKM_F}${OutputVectorDim}${d}" "itk::Vector<${ITKT_F},${OutputVectorDim}>,${d}") +endforeach() +end_wrap_type() + +itk_wrap_class("itk::Image" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + itk_wrap_template("V${ITKM_F}${OutputVectorDim}${d}" "itk::Vector<${ITKT_F},${OutputVectorDim}>,${d}") +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::ImageSource" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + itk_wrap_template("IV${ITKM_F}${OutputVectorDim}${d}" "itk::Image,${d}>") +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::ImageToImageFilter" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + itk_wrap_template("${ITKM_I${t}${d}}IV${ITKM_F}${OutputVectorDim}${d}" + "${ITKT_I${t}${d}}, itk::Image,${d}>" + ) + endforeach() +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::ImageFileReader" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + itk_wrap_template("IV${ITKM_F}${OutputVectorDim}${d}" + "itk::Image,${d}>" + ) +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::ImageFileWriter" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + itk_wrap_template("IV${ITKM_F}${OutputVectorDim}${d}" + "itk::Image,${d}>" + ) +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::Statistics::CoocurrenceTextureFeaturesImageFilter" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + itk_wrap_template("${ITKM_I${t}${d}}${ITKM_VI${ITKM_F}${d}}" + "${ITKT_I${t}${d}}, ${ITKT_VI${ITKM_F}${d}}" + ) + itk_wrap_template("${ITKM_I${t}${d}}IV${ITKM_F}${OutputVectorDim}${d}" + "${ITKT_I${t}${d}}, itk::Image,${d}>" + ) + endforeach() +endforeach() +itk_end_wrap_class() diff --git a/Modules/Filtering/TextureFeatures/wrapping/itkRunLengthTextureFeaturesImageFilter.wrap b/Modules/Filtering/TextureFeatures/wrapping/itkRunLengthTextureFeaturesImageFilter.wrap new file mode 100644 index 00000000000..3aeac481842 --- /dev/null +++ b/Modules/Filtering/TextureFeatures/wrapping/itkRunLengthTextureFeaturesImageFilter.wrap @@ -0,0 +1,62 @@ +set(OutputVectorDim 10) + +itk_wrap_class("itk::Vector") +itk_wrap_template("${ITKM_F}${OutputVectorDim}" "${ITKT_F},${OutputVectorDim}") +itk_end_wrap_class() + +wrap_type("itk::Image" "I" "itkImage.h") +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + add_template("V${ITKM_F}${OutputVectorDim}${d}" "itk::Vector<${ITKT_F},${OutputVectorDim}>,${d}") +endforeach() +end_wrap_type() + +itk_wrap_class("itk::Image" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + itk_wrap_template("V${ITKM_F}${OutputVectorDim}${d}" "itk::Vector<${ITKT_F},${OutputVectorDim}>,${d}") +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::ImageSource" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + itk_wrap_template("${ITKM_IV${ITKM_F}${OutputVectorDim}${d}}" "${ITKT_IV${ITKM_F}${OutputVectorDim}${d}}") +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::ImageToImageFilter" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + itk_wrap_template("${ITKM_I${t}${d}}IV${ITKM_F}${OutputVectorDim}${d}" + "${ITKT_I${t}${d}}, itk::Image,${d}>" + ) + endforeach() +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::ImageFileReader" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + itk_wrap_template("IV${ITKM_F}${OutputVectorDim}${d}" + "itk::Image,${d}>" + ) +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::ImageFileWriter" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + itk_wrap_template("IV${ITKM_F}${OutputVectorDim}${d}" + "itk::Image,${d}>" + ) +endforeach() +itk_end_wrap_class() + +itk_wrap_class("itk::Statistics::RunLengthTextureFeaturesImageFilter" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + itk_wrap_template("${ITKM_I${t}${d}}${ITKM_VI${ITKM_F}${d}}" + "${ITKT_I${t}${d}}, ${ITKT_VI${ITKM_F}${d}}" + ) + itk_wrap_template("${ITKM_I${t}${d}}IV${ITKM_F}${OutputVectorDim}${d}" + "${ITKT_I${t}${d}}, itk::Image,${d}>" + ) + endforeach() +endforeach() +itk_end_wrap_class() diff --git a/Modules/Remote/TextureFeatures.remote.cmake b/Modules/Remote/TextureFeatures.remote.cmake deleted file mode 100644 index 10a9785d83e..00000000000 --- a/Modules/Remote/TextureFeatures.remote.cmake +++ /dev/null @@ -1,61 +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) -#-- - [ ] API | executable interface is considered mostly stable and feature complete -#-- - [ ] 10% C0-code coverage demonstrated for testing suite -#-- - [ ] 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 - -# Contact: Jean-Baptiste VIMORT -itk_fetch_module( - TextureFeatures - "Filters to estimate texture feature maps from N-dimensional grayscale -images. This includes first-order texture features, grey level co-occurrence -matrix (GLCM) features, and grey level run-length matrix (GLRLM) features. - -For more information, see: - - Vimort J., McCormick M., Budin F., Paniagua B. - Computing Textural Feature Maps for N-Dimensional images - The Insight Journal. January-December. 2017. - https://doi.org/10.54294/qy48ty -" - MODULE_COMPLIANCE_LEVEL 2 - GIT_REPOSITORY https://github.com/InsightSoftwareConsortium/ITKTextureFeatures.git - GIT_TAG 54e259e445905279a996cca9da92af68af3191f6 - ) diff --git a/Utilities/Maintenance/RemoteModuleIngest/whitelists/TextureFeatures.list b/Utilities/Maintenance/RemoteModuleIngest/whitelists/TextureFeatures.list new file mode 100644 index 00000000000..ec6877ac997 --- /dev/null +++ b/Utilities/Maintenance/RemoteModuleIngest/whitelists/TextureFeatures.list @@ -0,0 +1,23 @@ +# TextureFeatures — files that migrate into ITK at +# Modules/Filtering/TextureFeatures/ +# +# Format: one path per line, relative to the upstream repo root. +# Globs supported via filter-repo --paths-from-file (treated as +# path prefixes; trailing /** matches everything beneath the dir). +# Lines starting with '#' are comments; blank lines ignored. + +# Public headers +include + +# Tests + fixtures +test + +# Python wrapping +wrapping + +# CMake / module metadata +CMakeLists.txt +itk-module.cmake + +# License (always migrates with the code) +LICENSE diff --git a/pyproject.toml b/pyproject.toml index c73ae38c83d..b35d46425a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ cmd = '''cmake \ -DModule_MorphologicalContourInterpolation:BOOL=ON \ -DModule_RLEImage:BOOL=ON \ -DModule_SubdivisionQuadEdgeMeshFilter:BOOL=ON \ + -DModule_TextureFeatures:BOOL=ON \ -DITK_COMPUTER_MEMORY_SIZE:STRING=11 \ -DModule_StructuralSimilarity:BOOL=ON''' description = "Configure ITK for CI (with ccache compiler launcher)"