Skip to content

Commit 3c1b13d

Browse files
committed
ENH: Expose kernel parameters in DiscreteGaussianImageFilter interface
`itk::DiscreteGaussianImageFilter` exposes parameters for generating a Gaussian kernel to error and size specifications, but previously did not make information on the kernel that was actually generated available to other classes. These changes expose protected methods to allow subclasses to create kernels to the same specifications and also publicly exposes information on the kernel sigma when image spacing is being accounted for.
1 parent b886472 commit 3c1b13d

File tree

3 files changed

+110
-76
lines changed

3 files changed

+110
-76
lines changed

Modules/Filtering/Smoothing/include/itkDiscreteGaussianImageFilter.h

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#ifndef itkDiscreteGaussianImageFilter_h
1919
#define itkDiscreteGaussianImageFilter_h
2020

21+
#include "itkGaussianOperator.h"
2122
#include "itkImageToImageFilter.h"
2223
#include "itkImage.h"
2324
#include "itkZeroFluxNeumannBoundaryCondition.h"
@@ -113,6 +114,10 @@ class ITK_TEMPLATE_EXPORT DiscreteGaussianImageFilter : public ImageToImageFilte
113114
using SigmaArrayType = ArrayType;
114115
using ScalarRealType = double;
115116

117+
/** Type of kernel to be used in blurring */
118+
using KernelType = GaussianOperator<RealOutputPixelValueType, ImageDimension>;
119+
using RadiusType = typename KernelType::RadiusType;
120+
116121
/** The variance for the discrete Gaussian kernel. Sets the variance
117122
* independently for each dimension, but
118123
* see also SetVariance(const double v). The default is 0.0 in each
@@ -130,8 +135,8 @@ class ITK_TEMPLATE_EXPORT DiscreteGaussianImageFilter : public ImageToImageFilte
130135

131136
/** Set the kernel to be no wider than MaximumKernelWidth pixels,
132137
* even if MaximumError demands it. The default is 32 pixels. */
133-
itkGetConstMacro(MaximumKernelWidth, int);
134-
itkSetMacro(MaximumKernelWidth, int);
138+
itkGetConstMacro(MaximumKernelWidth, unsigned int);
139+
itkSetMacro(MaximumKernelWidth, unsigned int);
135140

136141
/** Set the number of dimensions to smooth. Defaults to the image
137142
* dimension. Can be set to less than ImageDimension, smoothing all
@@ -334,6 +339,18 @@ class ITK_TEMPLATE_EXPORT DiscreteGaussianImageFilter : public ImageToImageFilte
334339
void
335340
GenerateData() override;
336341

342+
/** Build a directional kernel to match user specifications */
343+
void
344+
GenerateKernel(const unsigned int dimension, KernelType & oper) const;
345+
346+
/** Get the radius of the generated directional kernel */
347+
unsigned int
348+
GetKernelRadius(const unsigned int dimension) const;
349+
350+
/** Get the variance, optionally adjusted for pixel spacing */
351+
ArrayType
352+
GetKernelVarianceArray() const;
353+
337354
private:
338355
/** The variance of the gaussian blurring kernel in each dimensional
339356
direction. */
@@ -346,7 +363,7 @@ class ITK_TEMPLATE_EXPORT DiscreteGaussianImageFilter : public ImageToImageFilte
346363

347364
/** Maximum allowed kernel width for any dimension of the discrete Gaussian
348365
approximation */
349-
int m_MaximumKernelWidth;
366+
unsigned int m_MaximumKernelWidth;
350367

351368
/** Number of dimensions to process. Default is all dimensions */
352369
unsigned int m_FilterDimensionality;

Modules/Filtering/Smoothing/include/itkDiscreteGaussianImageFilter.hxx

Lines changed: 63 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,59 @@
2626

2727
namespace itk
2828
{
29+
template <typename TInputImage, typename TOutputImage>
30+
void
31+
DiscreteGaussianImageFilter<TInputImage, TOutputImage>::GenerateKernel(const unsigned int dimension,
32+
KernelType & oper) const
33+
{
34+
// Determine the size of the operator in this dimension. Note that the
35+
// Gaussian is built as a 1D operator in each of the specified directions.
36+
oper.SetDirection(dimension);
37+
oper.SetMaximumError(m_MaximumError[dimension]);
38+
oper.SetMaximumKernelWidth(m_MaximumKernelWidth);
39+
40+
oper.SetVariance(this->GetKernelVarianceArray()[dimension]);
41+
42+
oper.CreateDirectional();
43+
}
44+
45+
template <typename TInputImage, typename TOutputImage>
46+
unsigned int
47+
DiscreteGaussianImageFilter<TInputImage, TOutputImage>::GetKernelRadius(const unsigned int dimension) const
48+
{
49+
KernelType oper;
50+
this->GenerateKernel(dimension, oper);
51+
return oper.GetRadius(dimension);
52+
}
53+
54+
template <typename TInputImage, typename TOutputImage>
55+
typename DiscreteGaussianImageFilter<TInputImage, TOutputImage>::ArrayType
56+
DiscreteGaussianImageFilter<TInputImage, TOutputImage>::GetKernelVarianceArray() const
57+
{
58+
if (m_UseImageSpacing)
59+
{
60+
if (this->GetInput() == nullptr)
61+
{
62+
itkExceptionMacro("UseImageSpacing is ON but no input image was provided");
63+
}
64+
65+
ArrayType adjustedVariance;
66+
// Adjusted variance = var / (spacing ^ 2)
67+
for (unsigned int dim = 0; dim < ImageDimension; ++dim)
68+
{
69+
// convert the variance from physical units to pixels
70+
double s = this->GetInput()->GetSpacing()[dim];
71+
s = s * s;
72+
adjustedVariance[dim] = m_Variance[dim] / s;
73+
}
74+
return adjustedVariance;
75+
}
76+
else
77+
{
78+
return this->GetVariance();
79+
}
80+
}
81+
2982
template <typename TInputImage, typename TOutputImage>
3083
void
3184
DiscreteGaussianImageFilter<TInputImage, TOutputImage>::GenerateInputRequestedRegion()
@@ -42,39 +95,18 @@ DiscreteGaussianImageFilter<TInputImage, TOutputImage>::GenerateInputRequestedRe
4295
return;
4396
}
4497

45-
// Build an operator so that we can determine the kernel size
46-
GaussianOperator<OutputPixelValueType, ImageDimension> oper;
47-
48-
typename TInputImage::SizeType radius;
49-
98+
// Determine the kernel size in each direction
99+
RadiusType radius;
50100
for (unsigned int i = 0; i < TInputImage::ImageDimension; ++i)
51101
{
52-
// Determine the size of the operator in this dimension. Note that the
53-
// Gaussian is built as a 1D operator in each of the specified directions.
54-
oper.SetDirection(i);
55-
if (m_UseImageSpacing == true)
102+
if (i < m_FilterDimensionality)
56103
{
57-
if (this->GetInput()->GetSpacing()[i] == 0.0)
58-
{
59-
itkExceptionMacro(<< "Pixel spacing cannot be zero");
60-
}
61-
else
62-
{
63-
// convert the variance from physical units to pixels
64-
double s = this->GetInput()->GetSpacing()[i];
65-
s = s * s;
66-
oper.SetVariance(m_Variance[i] / s);
67-
}
104+
radius[i] = GetKernelRadius(i);
68105
}
69106
else
70107
{
71-
oper.SetVariance(m_Variance[i]);
108+
radius[i] = 0;
72109
}
73-
oper.SetMaximumError(m_MaximumError[i]);
74-
oper.SetMaximumKernelWidth(m_MaximumKernelWidth);
75-
oper.CreateDirectional();
76-
77-
radius[i] = oper.GetRadius(i);
78110
}
79111

80112
// get a copy of the input requested region (should equal the output
@@ -86,26 +118,9 @@ DiscreteGaussianImageFilter<TInputImage, TOutputImage>::GenerateInputRequestedRe
86118
inputRequestedRegion.PadByRadius(radius);
87119

88120
// crop the input requested region at the input's largest possible region
89-
if (inputRequestedRegion.Crop(inputPtr->GetLargestPossibleRegion()))
90-
{
91-
inputPtr->SetRequestedRegion(inputRequestedRegion);
92-
return;
93-
}
94-
else
95-
{
96-
// Couldn't crop the region (requested region is outside the largest
97-
// possible region). Throw an exception.
98-
99-
// store what we tried to request (prior to trying to crop)
100-
inputPtr->SetRequestedRegion(inputRequestedRegion);
101-
102-
// build an exception
103-
InvalidRequestedRegionError e(__FILE__, __LINE__);
104-
e.SetLocation(ITK_LOCATION);
105-
e.SetDescription("Requested region is (at least partially) outside the largest possible region.");
106-
e.SetDataObject(inputPtr);
107-
throw e;
108-
}
121+
inputRequestedRegion.Crop(inputPtr->GetLargestPossibleRegion());
122+
123+
inputPtr->SetRequestedRegion(inputRequestedRegion);
109124
}
110125

111126
template <typename TInputImage, typename TOutputImage>
@@ -160,9 +175,8 @@ DiscreteGaussianImageFilter<TInputImage, TOutputImage>::GenerateData()
160175
using SingleFilterPointer = typename SingleFilterType::Pointer;
161176

162177
// Create a series of operators
163-
using OperatorType = GaussianOperator<RealOutputPixelValueType, ImageDimension>;
164178

165-
std::vector<OperatorType> oper;
179+
std::vector<KernelType> oper;
166180
oper.resize(filterDimensionality);
167181

168182
// Create a process accumulator for tracking the progress of minipipeline
@@ -177,30 +191,7 @@ DiscreteGaussianImageFilter<TInputImage, TOutputImage>::GenerateData()
177191
// the largest dimension will be split slice wise for streaming
178192
unsigned int reverse_i = filterDimensionality - i - 1;
179193

180-
// Set up the operator for this dimension
181-
oper[reverse_i].SetDirection(i);
182-
if (m_UseImageSpacing == true)
183-
{
184-
if (localInput->GetSpacing()[i] == 0.0)
185-
{
186-
itkExceptionMacro(<< "Pixel spacing cannot be zero");
187-
}
188-
else
189-
{
190-
// convert the variance from physical units to pixels
191-
double s = localInput->GetSpacing()[i];
192-
s = s * s;
193-
oper[reverse_i].SetVariance(m_Variance[i] / s);
194-
}
195-
}
196-
else
197-
{
198-
oper[reverse_i].SetVariance(m_Variance[i]);
199-
}
200-
201-
oper[reverse_i].SetMaximumKernelWidth(m_MaximumKernelWidth);
202-
oper[reverse_i].SetMaximumError(m_MaximumError[i]);
203-
oper[reverse_i].CreateDirectional();
194+
this->GenerateKernel(i, oper[reverse_i]);
204195
}
205196

206197
// Create a chain of filters

Modules/Filtering/Smoothing/test/itkDiscreteGaussianImageFilterTest.cxx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ itkDiscreteGaussianImageFilterTest(int argc, char * argv[])
7171
filter->SetSigmaArray(sigma);
7272
ITK_TEST_SET_GET_VALUE(sigma, filter->GetSigmaArray());
7373

74+
// Verify spacing-adjusted variance is not available without input image
75+
filter->SetUseImageSpacing(true);
76+
ITK_TRY_EXPECT_EXCEPTION(filter->GetImageSpacingVarianceArray());
77+
7478
// Set the boundary condition used by the filter
7579
itk::ConstantBoundaryCondition<ImageType> constantBoundaryCondition;
7680
filter->SetInputBoundaryCondition(&constantBoundaryCondition);
@@ -92,7 +96,7 @@ itkDiscreteGaussianImageFilterTest(int argc, char * argv[])
9296
filter->SetMaximumError(maximumError);
9397
ITK_TEST_SET_GET_VALUE(maximumError, filter->GetMaximumError());
9498

95-
int maximumKernelWidth = 32;
99+
unsigned int maximumKernelWidth = 32;
96100
filter->SetMaximumKernelWidth(maximumKernelWidth);
97101
ITK_TEST_SET_GET_VALUE(maximumKernelWidth, filter->GetMaximumKernelWidth());
98102

@@ -124,6 +128,28 @@ itkDiscreteGaussianImageFilterTest(int argc, char * argv[])
124128

125129
ITK_TRY_EXPECT_NO_EXCEPTION(test1.Execute());
126130

131+
// Test variance adjustments for image spacing
132+
ImageType::Pointer inputImage = ImageType::New();
133+
typename ImageType::SpacingType spacing;
134+
spacing[0] = 1.5;
135+
spacing[1] = 1.0;
136+
spacing[2] = 0.5;
137+
inputImage->SetSpacing(spacing);
138+
filter->SetInput(inputImage);
139+
140+
filter->SetUseImageSpacing(false);
141+
for (itk::IndexValueType dim = 0; dim < Dimension; ++dim)
142+
{
143+
ITK_TEST_EXPECT_EQUAL(sigmaValue, filter->GetVariance()[dim])
144+
ITK_TEST_EXPECT_EQUAL(sigmaValue, filter->GetImageSpacingVarianceArray()[dim]);
145+
}
146+
147+
filter->SetUseImageSpacing(true);
148+
for (itk::IndexValueType dim = 0; dim < Dimension; ++dim)
149+
{
150+
ITK_TEST_EXPECT_EQUAL(sigmaValue, filter->GetVariance()[dim]);
151+
ITK_TEST_EXPECT_EQUAL((sigmaValue / (spacing[dim] * spacing[dim])), filter->GetImageSpacingVarianceArray()[dim]);
152+
}
127153

128154
std::cout << "Test finished" << std::endl;
129155
return EXIT_SUCCESS;

0 commit comments

Comments
 (0)