Skip to content

Conversation

@N-Dekker
Copy link
Contributor

@N-Dekker N-Dekker commented Jan 6, 2026

The old kernel coefficients for 3D appeared inconsistent with the coefficients for 2D. This pull request allows generating these coefficients consistently, inspired by the AI generated function make_nd_sobel_kernels, presented by Hans Johnson (@hjmjohnson) at #5702 (comment)

The new coefficients are more commonly used by other toolkits, including SciPy's scipy.ndimage.sobel, and correspond with https://en.wikipedia.org/wiki/Sobel_operator

This PR removes the reference to "An Isotropic 3x3x3 Volume Gradient Operator", Irwin Sobel, 1995, as it cannot be found anymore.

Added a UseLegacyCoefficients option to SobelOperator, which allows the user to specify whether or not SobelOperator::GenerateCoefficients() should still produce the old coefficients for a 3D kernel. Enabled by default.

Removed the static_assert from SobelOperator that allowed only 2D and 3D. Added checks for 1D and 4D to the unit test.

@github-actions github-actions bot added type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct area:Core Issues affecting the Core module area:Documentation Issues affecting the Documentation module labels Jan 6, 2026
Copy link
Member

@dzenanz dzenanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we have dimensionality constraints somewhere? If so, we should lift them as part of this PR. Also, we probably want to add unit tests for higher dimensions.

@N-Dekker
Copy link
Contributor Author

N-Dekker commented Jan 6, 2026

Did we have dimensionality constraints somewhere? If so, we should lift them as part of this PR. Also, we probably want to add unit tests for higher dimensions.

Thanks for the reminder 😺 We would also have to generalize its Fill(const CoefficientVector &) member function, which is now 2D and 3D only:

SobelOperator<TPixel, VDimension, TAllocator>::Fill(const CoefficientVector & coeff)

So you see, it's really WIP!

@N-Dekker N-Dekker force-pushed the Make-SobelOperator-3D-consistent branch 2 times, most recently from 54d23a1 to 730a117 Compare January 7, 2026 17:07
@N-Dekker
Copy link
Contributor Author

N-Dekker commented Jan 7, 2026

FYI My intention is to squash most of these WIP commits before making the PR ready for review. (But before doing so, the PR should also offer a backward compatible legacy option, at least for 3D.)

@N-Dekker
Copy link
Contributor Author

N-Dekker commented Jan 8, 2026

/azp run ITK.Linux.Python

@N-Dekker
Copy link
Contributor Author

N-Dekker commented Jan 8, 2026

/azp run ITK.Linux

Comment on lines +134 to +138
const auto & kernel = sobelKernels[VDimension - 1 - direction];
return CoefficientVector(kernel.cbegin(), kernel.cend());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply kernel = sobelKernels[direction]? I still don't understand why the kernels returned by the AI generated function from #5702 (comment) are in the reverse order, compared by ITK 🤷

@N-Dekker N-Dekker force-pushed the Make-SobelOperator-3D-consistent branch from 4c20b2c to a20d509 Compare January 8, 2026 22:05
@blowekamp
Copy link
Member

When I am building SimpleITK with Elastic against the current ITK main ( or work derived from ). I am getting the following compilation error:

Elastix/Components/Metrics/NormalizedGradientCorrelation/elxNormalizedGradientCorrelationMetric.cxx:21:1:   required from here
ITK-prefix/include/ITK-6.0/itkSobelOperator.h:108:21: error: static assertion failed: The ND version of the Sobel operator has not been implemented. Currently only 2D and 3D versions are available.

Will this PR fix this issue?

@N-Dekker
Copy link
Contributor Author

N-Dekker commented Jan 9, 2026

Will this PR fix this issue?

Because this PR is already so elaborate, I would prefer to have the fix in a follow-up.

This PR aims to:

  • replace hard-coded Sobel kernel coordinates with coordinates computed by an algorithm (based on the AI generated function that Hans provided at A ND Sobel implementation is needed. #5702)
  • add a legacy option to get the old hard-coded coordinates
  • test both the algorithm based and the old hard-coded coordinates

The follow-up PR should then:

  • extend the Sobel kernel operator to 4-D
  • test 4-D

If SimpleITK does not need 4D Sobel, better just not wrap it at all! It never worked anyway. In the past, it just produced an exception.


Update: this PR now includes 4-D support! No need to wait for a follow-up 😃

@blowekamp
Copy link
Member

If SimpleITK does not need 4D Sobel, better just not wrap it at all! It never worked anyway. In the past, it just produced an exception.

The error comes from elastix usage of the Sobel operator:
https://open.cdash.org/viewBuildError.php?buildid=10948517

Should I wait for this PR and subsequent to fix Elastix or update the Elastic version in SimpleITK? Or something else?

Comment on lines +137 to +141
void
UseLegacyCoefficients(const bool useLegacyCoefficients)
{
m_UseLegacyCoefficients = useLegacyCoefficients;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that itkSetMacro(UseLegacyCoefficients, bool) does not compile, because itk::SobelOperator is not derived from itk::Object, and it does not support this->Modified().

The macro (itkSetMacro) would make it SetUseLegacyCoefficients(bool _arg) instead. But I think UseLegacyCoefficients(const bool useLegacyCoefficients) is more readable. OK?

What about the other member function, IsUsingLegacyCoefficients()? Is that clear enough?

@N-Dekker N-Dekker force-pushed the Make-SobelOperator-3D-consistent branch from a20d509 to 873d1df Compare January 9, 2026 22:44
@github-actions github-actions bot removed the type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct label Jan 9, 2026
The old kernel coefficients for 3D appeared inconsistent with the coefficients
for 2D. This commit allows generating these coefficients consistently, inspired
by the AI generated function `make_nd_sobel_kernels`, presented by Hans Johnson
at issue InsightSoftwareConsortium#5702

The new coefficients are more commonly used by other toolkits, including SciPy's
`scipy.ndimage.sobel`, and correspond with https://en.wikipedia.org/wiki/Sobel_operator

This commit removes the reference to "An Isotropic 3x3x3 Volume Gradient
Operator", Sobel, 1995, as it cannot be found anymore.

Added a `UseLegacyCoefficients` option to SobelOperator, allows the user to
specify whether or not `SobelOperator::GenerateCoefficients()` should still
produce the old coefficients for a 3D kernel. Enabled by default.
@N-Dekker N-Dekker force-pushed the Make-SobelOperator-3D-consistent branch from 873d1df to 43801b2 Compare January 10, 2026 10:07
@github-actions github-actions bot added the type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct label Jan 10, 2026
Removed the `static_assert` from `SobelOperator` that allowed only 2D and 3D.
Added checks for 1D and 4D to the `SobelOperator.CheckKernelCoordinates` unit
test.

Addresses issue InsightSoftwareConsortium#5702
"A ND Sobel implementation is needed."
@N-Dekker N-Dekker changed the title WIP: A ND Sobel implementation! Make 3D SobelOperator consistent with 2D, add UseLegacyCoefficients, add ND support Jan 10, 2026
@N-Dekker N-Dekker marked this pull request as ready for review January 10, 2026 11:33
@N-Dekker
Copy link
Contributor Author

Did we have dimensionality constraints somewhere? If so, we should lift them as part of this PR. Also, we probably want to add unit tests for higher dimensions.

@dzenanz Addressed by the last commit: ENH: Add ND support to SobelOperator

@N-Dekker
Copy link
Contributor Author

N-Dekker commented Jan 10, 2026

The error comes from elastix usage of the Sobel operator: https://open.cdash.org/viewBuildError.php?buildid=10948517

Should I wait for this PR and subsequent to fix Elastix or update the Elastic version in SimpleITK? Or something else?

@blowekamp My formal answer (however disappointing) is of course: Elastix does not support arbitrary revisions of ITK. It only supports official releases of ITK. (Elastix might occasionally support an alpha or a beta version of ITK, but we try to stick to official releases. Not an arbitrary revision from the main branch.) But your comment is very helpful as it revealed an elastix issue: SuperElastix/elastix#1391 ! And yes, this PR should fix the issue 🎉

@N-Dekker
Copy link
Contributor Author

My post at discourse.itk.org about this pull request: Sobel operator extended for 4D, legacy support for 3D, OK?

Copy link
Member

@dzenanz dzenanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not dived into the AI generated nD code, but it looks simple. You added 4D test. Can you add 5D test? Possibly by using coefficients from SciPy, or someplace else that has them. That would increase our confidence in this code. But good even as is.

@N-Dekker
Copy link
Contributor Author

@dzenanz Partially in response to your comment, the proposed new coefficients from this PR can be produced using SciPy, as follows:

import scipy
import numpy as np

def print_sobel(input):
  for itk_direction in range(np.ndim(input)):
    axis = np.ndim(input) - itk_direction - 1
    print(np.flip(scipy.ndimage.sobel(input, axis), axis).flatten())

input_2d = np.zeros((3, 3), dtype=int)
input_2d[1, 1] = 1
print_sobel(input_2d)

input_3d = np.zeros((3, 3, 3), dtype=int)
input_3d[1, 1, 1] = 1
print_sobel(input_3d)

Output:

[-1  0  1 -2  0  2 -1  0  1]
[-1 -2 -1  0  0  0  1  2  1]
[-1  0  1 -2  0  2 -1  0  1 -2  0  2 -4  0  4 -2  0  2 -1  0  1 -2  0  2 -1  0  1]
[-1 -2 -1  0  0  0  1  2  1 -2 -4 -2  0  0  0  2  4  2 -1 -2 -1  0  0  0  1  2  1]
[-1 -2 -1 -2 -4 -2 -1 -2 -1  0  0  0  0  0  0  0  0  0  1  2  1  2  4  2  1  2  1]

With help from my LKEB/LUMC colleague Baldur (@bldrvnlw).

@N-Dekker
Copy link
Contributor Author

For 5D, the expected coefficients are as follows. I doubt if it makes things clearer when copy-pasting all those numbers into the unit test 🤷

[ -1   0   1  -2   0   2  -1   0   1  -2   0   2  -4   0   4  -2   0   2
  -1   0   1  -2   0   2  -1   0   1  -2   0   2  -4   0   4  -2   0   2
  -4   0   4  -8   0   8  -4   0   4  -2   0   2  -4   0   4  -2   0   2
  -1   0   1  -2   0   2  -1   0   1  -2   0   2  -4   0   4  -2   0   2
  -1   0   1  -2   0   2  -1   0   1  -2   0   2  -4   0   4  -2   0   2
  -4   0   4  -8   0   8  -4   0   4  -2   0   2  -4   0   4  -2   0   2
  -4   0   4  -8   0   8  -4   0   4  -8   0   8 -16   0  16  -8   0   8
  -4   0   4  -8   0   8  -4   0   4  -2   0   2  -4   0   4  -2   0   2
  -4   0   4  -8   0   8  -4   0   4  -2   0   2  -4   0   4  -2   0   2
  -1   0   1  -2   0   2  -1   0   1  -2   0   2  -4   0   4  -2   0   2
  -1   0   1  -2   0   2  -1   0   1  -2   0   2  -4   0   4  -2   0   2
  -4   0   4  -8   0   8  -4   0   4  -2   0   2  -4   0   4  -2   0   2
  -1   0   1  -2   0   2  -1   0   1  -2   0   2  -4   0   4  -2   0   2
  -1   0   1  -2   0   2  -1   0   1]
[ -1  -2  -1   0   0   0   1   2   1  -2  -4  -2   0   0   0   2   4   2
  -1  -2  -1   0   0   0   1   2   1  -2  -4  -2   0   0   0   2   4   2
  -4  -8  -4   0   0   0   4   8   4  -2  -4  -2   0   0   0   2   4   2
  -1  -2  -1   0   0   0   1   2   1  -2  -4  -2   0   0   0   2   4   2
  -1  -2  -1   0   0   0   1   2   1  -2  -4  -2   0   0   0   2   4   2
  -4  -8  -4   0   0   0   4   8   4  -2  -4  -2   0   0   0   2   4   2
  -4  -8  -4   0   0   0   4   8   4  -8 -16  -8   0   0   0   8  16   8
  -4  -8  -4   0   0   0   4   8   4  -2  -4  -2   0   0   0   2   4   2
  -4  -8  -4   0   0   0   4   8   4  -2  -4  -2   0   0   0   2   4   2
  -1  -2  -1   0   0   0   1   2   1  -2  -4  -2   0   0   0   2   4   2
  -1  -2  -1   0   0   0   1   2   1  -2  -4  -2   0   0   0   2   4   2
  -4  -8  -4   0   0   0   4   8   4  -2  -4  -2   0   0   0   2   4   2
  -1  -2  -1   0   0   0   1   2   1  -2  -4  -2   0   0   0   2   4   2
  -1  -2  -1   0   0   0   1   2   1]
[ -1  -2  -1  -2  -4  -2  -1  -2  -1   0   0   0   0   0   0   0   0   0
   1   2   1   2   4   2   1   2   1  -2  -4  -2  -4  -8  -4  -2  -4  -2
   0   0   0   0   0   0   0   0   0   2   4   2   4   8   4   2   4   2
  -1  -2  -1  -2  -4  -2  -1  -2  -1   0   0   0   0   0   0   0   0   0
   1   2   1   2   4   2   1   2   1  -2  -4  -2  -4  -8  -4  -2  -4  -2
   0   0   0   0   0   0   0   0   0   2   4   2   4   8   4   2   4   2
  -4  -8  -4  -8 -16  -8  -4  -8  -4   0   0   0   0   0   0   0   0   0
   4   8   4   8  16   8   4   8   4  -2  -4  -2  -4  -8  -4  -2  -4  -2
   0   0   0   0   0   0   0   0   0   2   4   2   4   8   4   2   4   2
  -1  -2  -1  -2  -4  -2  -1  -2  -1   0   0   0   0   0   0   0   0   0
   1   2   1   2   4   2   1   2   1  -2  -4  -2  -4  -8  -4  -2  -4  -2
   0   0   0   0   0   0   0   0   0   2   4   2   4   8   4   2   4   2
  -1  -2  -1  -2  -4  -2  -1  -2  -1   0   0   0   0   0   0   0   0   0
   1   2   1   2   4   2   1   2   1]
[ -1  -2  -1  -2  -4  -2  -1  -2  -1  -2  -4  -2  -4  -8  -4  -2  -4  -2
  -1  -2  -1  -2  -4  -2  -1  -2  -1   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   1   2   1   2   4   2   1   2   1   2   4   2   4   8   4   2   4   2
   1   2   1   2   4   2   1   2   1  -2  -4  -2  -4  -8  -4  -2  -4  -2
  -4  -8  -4  -8 -16  -8  -4  -8  -4  -2  -4  -2  -4  -8  -4  -2  -4  -2
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   2   4   2   4   8   4   2   4   2
   4   8   4   8  16   8   4   8   4   2   4   2   4   8   4   2   4   2
  -1  -2  -1  -2  -4  -2  -1  -2  -1  -2  -4  -2  -4  -8  -4  -2  -4  -2
  -1  -2  -1  -2  -4  -2  -1  -2  -1   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   1   2   1   2   4   2   1   2   1   2   4   2   4   8   4   2   4   2
   1   2   1   2   4   2   1   2   1]
[ -1  -2  -1  -2  -4  -2  -1  -2  -1  -2  -4  -2  -4  -8  -4  -2  -4  -2
  -1  -2  -1  -2  -4  -2  -1  -2  -1  -2  -4  -2  -4  -8  -4  -2  -4  -2
  -4  -8  -4  -8 -16  -8  -4  -8  -4  -2  -4  -2  -4  -8  -4  -2  -4  -2
  -1  -2  -1  -2  -4  -2  -1  -2  -1  -2  -4  -2  -4  -8  -4  -2  -4  -2
  -1  -2  -1  -2  -4  -2  -1  -2  -1   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   1   2   1   2   4   2   1   2   1   2   4   2   4   8   4   2   4   2
   1   2   1   2   4   2   1   2   1   2   4   2   4   8   4   2   4   2
   4   8   4   8  16   8   4   8   4   2   4   2   4   8   4   2   4   2
   1   2   1   2   4   2   1   2   1   2   4   2   4   8   4   2   4   2
   1   2   1   2   4   2   1   2   1]

@dzenanz
Copy link
Member

dzenanz commented Jan 13, 2026

Putting it into a unit tests makes sure that the new implementation is working as expected, and prevents future regressions. You can put the code which produced the coefficients in a comment above it.

@N-Dekker
Copy link
Contributor Author

Putting it into a unit tests makes sure that the new implementation is working as expected, and prevents future regressions. You can put the code which produced the coefficients in a comment above it.

The proposed unit test is now testing 1D, 2D, 3D, and 4D. I think that's sufficient for now. When 1D, 2D, 3D, and 4D behave well, I think there is no reason for 5D to misbehave. 🤷

@dzenanz
Copy link
Member

dzenanz commented Jan 13, 2026

Then merge. Unless you are expecting reviews from more people.

@N-Dekker N-Dekker linked an issue Jan 13, 2026 that may be closed by this pull request
@N-Dekker N-Dekker requested a review from hjmjohnson January 13, 2026 14:57
@N-Dekker
Copy link
Contributor Author

@hjmjohnson Do you agree that this PR properly solves your issue #5702

@N-Dekker
Copy link
Contributor Author

N-Dekker commented Jan 15, 2026

For the record:

Below here some nice pictures of the kernels of a 3D Sobel operator, as posted by Sven Fritsch at https://stackoverflow.com/a/66698671. Their values appear to correspond with the proposed new coordinates for a 3D itk::SobelOperator from this pull request.

Here the sobel filters in X direction, Y direction and Z direction.

73lM4 1
gtW6o 1
11ekC 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Core Issues affecting the Core module area:Documentation Issues affecting the Documentation module type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A ND Sobel implementation is needed.

5 participants