Skip to content

ENH: Add StructuralSimilarityImageFilter at Modules/Filtering#6094

Merged
hjmjohnson merged 1 commit intoInsightSoftwareConsortium:mainfrom
hjmjohnson:ssim-standalone
Apr 22, 2026
Merged

ENH: Add StructuralSimilarityImageFilter at Modules/Filtering#6094
hjmjohnson merged 1 commit intoInsightSoftwareConsortium:mainfrom
hjmjohnson:ssim-standalone

Conversation

@hjmjohnson
Copy link
Copy Markdown
Member

@hjmjohnson hjmjohnson commented Apr 20, 2026

Adds Modules/Filtering/StructuralSimilarity/ containing StructuralSimilarityImageFilter (SSIM, Wang et al. 2004). Standalone module under Filtering — does not touch ImageCompare or any other existing module. Enabled in CI via configure-ci. Supersedes #6034 (which was stacked on the now-rejected Modules/Beta/ approach in #6085).

What changed from PR #6034
Module layout
Modules/Filtering/StructuralSimilarity/
├── CMakeLists.txt
├── itk-module.cmake          # DEPENDS      ITKImageFilterBase, ITKImageIntensity, ITKSmoothing
│                              # TEST_DEPENDS ITKTestKernel, ITKGoogleTest
├── include/
│   ├── itkStructuralSimilarityImageFilter.h
│   └── itkStructuralSimilarityImageFilter.hxx
├── test/
│   ├── CMakeLists.txt
│   └── itkStructuralSimilarityImageFilterGTest.cxx   # 31 GTest cases
└── wrapping/
    ├── CMakeLists.txt
    └── itkStructuralSimilarityImageFilter.wrap
Local verification
  • Configure: clean
  • Build: no errors, no warnings
  • Tests: 33/33 pass
  • Pre-commit: all hooks pass
Reviewer feedback addressed

From #6034:

  • @blowekamp: VerifyPreconditions override (moved all param/input checks there)
  • @blowekamp: Mini-pipeline reconnection using CompositeFilterExample pattern
  • @blowekamp: Standalone module — does not perturb ImageCompare's dependency surface
  • @thewtex: No Beta staging — direct integration into Filtering/

From #6094:

  • @N-Dekker: Initialize m_ScaleWeights via the Array(SizeValueType, const ValueType &) ctor directly in the header NSDMI; collapse declare-then-Fill patterns in tests to ImageType::SizeType::Filled(size); use CTAD on iterators and drop redundant GoToBegin().
  • Greptile (P1): Added m_MaximumKernelWidth == 0 guard in VerifyPreconditions() plus a new ZeroMaximumKernelWidth_Throws test case.
  • Greptile (P2): Hoisted subInterior.GetNumberOfPixels() > 0 out of the per-pixel inner loop into const bool subInteriorIsNonEmpty.
  • Greptile (P2): Promoted ITKImageFilterBase and ITKSmoothing from COMPILE_DEPENDS to DEPENDS (parallel to the fix in COMP: Fix COMPILE_DEPENDS → DEPENDS in three Filtering module declara… #6097).
AI assistance
  • Tool: Claude Code (claude-opus-4-7)
  • Role: filter design, GoogleTest suite generation, reviewer-feedback fixups
  • Contribution:
    • SSIM mini-pipeline (five-Gaussian composite filter, per-pixel parallelized SSIM combination, interior-region mean accumulation)
    • 31-case GoogleTest suite (identities, analytic closed-form checks, error-throw paths, N-D and multi-pixel-type variants, scikit-image cross-validation)
    • Review-pass fixups: N-Dekker style cleanups (Array(SizeValueType, const ValueType &) ctor, ::Filled(size), iterator CTAD); Greptile P1 MaximumKernelWidth == 0 guard + regression test; Greptile P2 loop-invariant hoisting; COMPILE_DEPENDS → DEPENDS alignment with COMP: Fix COMPILE_DEPENDS → DEPENDS in three Filtering module declara… #6097
  • All code was reviewed locally, built without warnings, and validated against the 33-test suite before each commit

@github-actions github-actions Bot added type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots type:Enhancement Improvement of existing methods or implementation area:Python wrapping Python bindings for a class type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct area:Filtering Issues affecting the Filtering module labels Apr 20, 2026
@hjmjohnson hjmjohnson requested a review from thewtex April 20, 2026 23:46
@hjmjohnson hjmjohnson requested a review from dzenanz April 21, 2026 10:48
@hjmjohnson hjmjohnson marked this pull request as ready for review April 21, 2026 10:48
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 21, 2026

Greptile Summary

This PR adds a new StructuralSimilarityImageFilter module at Modules/Filtering/StructuralSimilarity/ implementing the Wang et al. 2004 SSIM metric as an N-dimensional, multi-threaded ITK filter. The SSIM formula (both simplified and general exponent paths), the Gaussian mini-pipeline, and the interior-region mean accumulation are all mathematically correct and well tested across 30 GTest cases.

  • P1VerifyPreconditions() validates sigma and dynamic-range but not m_MaximumKernelWidth; a zero value would silently produce a degenerate Gaussian kernel.
  • P2ITKSmoothing and ITKImageFilterBase are declared COMPILE_DEPENDS, but GenerateData() instantiates and updates filters from both modules at runtime, which may require them to be DEPENDS for correct downstream linking.

Confidence Score: 4/5

Safe to merge after addressing the missing MaximumKernelWidth>0 guard; remaining findings are P2 dependency and style concerns.

One P1 finding exists: VerifyPreconditions() does not reject MaximumKernelWidth==0, which would silently produce a degenerate Gaussian. All other findings (dependency classification, inner-loop micro-optimisation, wrapping coverage) are P2. The algorithm itself is mathematically sound and thoroughly tested.

itkStructuralSimilarityImageFilter.hxx (missing kernel-width guard) and itk-module.cmake (COMPILE_DEPENDS vs DEPENDS classification for ITKSmoothing / ITKImageFilterBase).

Important Files Changed

Filename Overview
Modules/Filtering/StructuralSimilarity/include/itkStructuralSimilarityImageFilter.h Adds the SSIM filter header; well-structured ITK class with clean type aliases, itkSetMacro/itkGetConstMacro accessors, and proper concept check for numeric traits.
Modules/Filtering/StructuralSimilarity/include/itkStructuralSimilarityImageFilter.hxx Implements SSIM via a five-Gaussian mini-pipeline; algorithm is mathematically correct but VerifyPreconditions() misses a MaximumKernelWidth==0 guard, and subInterior.GetNumberOfPixels() is re-evaluated every inner-loop iteration.
Modules/Filtering/StructuralSimilarity/itk-module.cmake Module descriptor compiles cleanly but classifies ITKSmoothing and ITKImageFilterBase as COMPILE_DEPENDS when they are instantiated at runtime inside GenerateData(), which may require them to be DEPENDS for correct downstream linking.
Modules/Filtering/StructuralSimilarity/test/itkStructuralSimilarityImageFilterGTest.cxx Comprehensive 30-case GTest suite covering identities, analytic closed-form checks, error-throw paths, multi-dimensional support, pixel-type variants, and cross-validation against scikit-image reference values.
Modules/Filtering/StructuralSimilarity/wrapping/itkStructuralSimilarityImageFilter.wrap Wraps WRAP_ITK_REAL types with 2 template parameters, producing only same-type pairs; missing the (double→float) combination that matches the C++ default output type.
pyproject.toml Adds -DModule_StructuralSimilarity:BOOL=ON to the configure-ci task; correctly scoped to the CI preset without altering the default configure task.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Input1: TInputImage"] --> C1["Cast to RealImage x"]
    B["Input2: TInputImage"] --> C2["Cast to RealImage y"]
    C1 --> G_x["Gaussian → mu_x"]
    C2 --> G_y["Gaussian → mu_y"]
    C1 --> MXX["x times x"]
    C2 --> MYY["y times y"]
    C1 --> MXY["x times y"]
    C2 --> MXY
    MXX --> G_xx["Gaussian → E_xx"]
    MYY --> G_yy["Gaussian → E_yy"]
    MXY --> G_xy["Gaussian → E_xy"]
    G_x --> COMBINE["Per-pixel SSIM combination\nParallelizeImageRegion\nvar_x = E_xx - mu_x^2\nvar_y = E_yy - mu_y^2\ncov_xy = E_xy - mu_x*mu_y"]
    G_y --> COMBINE
    G_xx --> COMBINE
    G_yy --> COMBINE
    G_xy --> COMBINE
    COMBINE --> OUT["Output SSIM map"]
    COMBINE --> MEAN["m_MeanSSIM over interior region"]
Loading

Reviews (1): Last reviewed commit: "ENH: Add StructuralSimilarityImageFilter..." | Re-trigger Greptile

Comment thread Modules/Filtering/StructuralSimilarity/itk-module.cmake Outdated
@hjmjohnson hjmjohnson force-pushed the ssim-standalone branch 3 times, most recently from 9dc9057 to 396b0c5 Compare April 21, 2026 19:56
New standalone module at Modules/Filtering/StructuralSimilarity/
containing StructuralSimilarityImageFilter (SSIM, Wang et al. 2004).
Computes per-pixel SSIM map and scalar mean SSIM between two images
using a sliding Gaussian window over local luminance, contrast, and
structure terms.

Placed as a standalone module under Filtering/ rather than extending
the existing ImageCompare module, to avoid perturbing a core module's
dependency surface for a new filter. Enabled in CI via configure-ci.

Reference: Wang Z., Bovik A.C., Sheikh H.R., Simoncelli E.P.,
"Image quality assessment: From error visibility to structural
similarity," IEEE Trans. Image Processing 13(4), 2004.

Co-Authored-By: Hans Johnson <hans-johnson@uiowa.edu>
Copy link
Copy Markdown
Member

@blowekamp blowekamp left a comment

Choose a reason for hiding this comment

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

Looks OK after my looks.

@hjmjohnson hjmjohnson merged commit 7a3a506 into InsightSoftwareConsortium:main Apr 22, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Filtering Issues affecting the Filtering module area:Python wrapping Python bindings for a class type:Enhancement Improvement of existing methods or implementation type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots 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.

4 participants