Skip to content

BUG: Findings collected while porting ~60 filters — reported together in one issue #6575

Description

@physwkim

Following up on #6569: while porting SimpleITK's filter surface to another language, I kept a ledger of everything in the ITK sources that looked wrong or surprising. It has grown to 83 suspected bugs and 118 quirks (originally 30 and 24; follow-up batches B31–B83 and Q25–Q118 are merged into the tables below) — too many to file individually without flooding the tracker, so I am reporting them together here for triage. Happy to split any subset into standalone issues if that is more useful; I am not planning PRs for all of these, so please treat this as raw material.

PR status (maintainer triage — updated 2026-07-11 by @hjmjohnson)

Ledger item PR Author State
B10 (MultiLabelSTAPLE tie increment) #6579 @physwkim Merged
B18 (FastApproximateRank last axis) #6580 @physwkim Merged
B19 (AttributeMorphologyBase sort range) #6581 @physwkim Merged
B25 (ApplyToImageMetadata null inverse) #6583 @dzenanz Merged
B31 (IterativeInverseDisplacementField stale error bar) #6576 @physwkim Merged
B32 (InverseDisplacementField subsampler direction) #6577 @physwkim Merged; adjacent geometry gaps split to #6582, addressed by #6588
B32 follow-up (lattice centering, output-direction API, subsampling guards) #6588 @hjmjohnson Draft, CI green — closes #6582
B2, B3, B4, B9, B11, B12, B20, B23, B27, B29 (NaN / UB-cast guards) #6589 @hjmjohnson Ready for review, CI green (17/17)
B34, B35, Q25–Q38 (displacement-field / warp / demons batch-2+3) branch bug-displacement-field-batch2-6575 @hjmjohnson Local, 9 commits, tested — PR pending
B24 (FastMarchingUpwindGradient AllTargets duplicates) branch bug-fastmarching-alltargets-6575 @hjmjohnson Local, tested (fail-before/pass-after GTest) — PR pending
B40–B45 (LabelMap / label-statistics correctness) #6590 @hjmjohnson Draft, CI running — 6 commits, 3 fail-before GTests + 136-test regression
B63, B67–B69, Q82, Q84 (NrrdIO) ITK bug-nrrdio-6575 + 4 teem branches on sourceforge @hjmjohnson Local/pushed upstream — vendored fixes sent to Gordon Kindlmann
B33 (ConfidenceConnected empty re-estimation) #6578 @physwkim Merged
Precursors #6569 (JHMI marginals + derivative) @physwkim Open, CI fully green, awaiting review
#6572 (ScaleLogarithmicTransform Jacobian) @physwkim Merged

All six draft PRs carry a single commit authored by @physwkim; each regression test was verified locally to fail on the pre-fix code and pass on the fix. Remaining items B1–B9, B11–B17, B20–B24, B26–B30 and the 24 quirks are unclaimed.

How these were found, and what was checked before filing

Each filter was reimplemented from the .hxx sources and the two implementations compared on synthetic inputs; every divergence was traced back to the ITK code path that caused it. Before filing, every item was re-checked against main at e46eb723a5: the cited lines re-read in context, declared types and constructor defaults verified, and each claimed consequence re-derived from the quoted code. Items whose original analysis did not survive that re-check were corrected or dropped — some of my own earlier conclusions turned out to be wrong, so I would not be surprised if a few of the remaining ones are too. Corrections welcome.

One item is flagged inline as source-analysis only (B28: non-termination identified from the loop structure, not run to a live hang).

Suspected bugs — wrong results, NaN, or UB in live code paths

Row status: ✅ completed (merged) · 🔄 in progress (open PR or prepared branch) · ⬜ not started.

Per-item detail blocks live in the collapsed comments below: B1–B30 in a dedicated comment, B31–B83 and Q25–Q118 in the follow-up batch comments.

# Site One-line symptom
⬜ B1 ProjectionImageFilter Collapsed-axis origin shift uses the axis index, unsigned; axis 0 wraps to a ~2³¹·spacing shift
🔄 B2 N4BiasFieldCorrectionImageFilter::SharpenImage Degenerate bin range → 0.0/0.0 = NaN → NaN→int cast (UB) → unchecked histogram index — fix: #6589
🔄 B3 SLICImageFilter Empty cluster → 0/0 NaN centroid → NaN→integer index cast (UB) — fix: #6589
🔄 B4 WienerDeconvolutionImageFilter Pf == Pn → complex division by zero → NaN output (guard passes on inf) — fix: #6589
⬜ B5 CannySegmentationLevelSetFunction Propagation weight 0 → advection generated from a never-allocated speed image
⬜ B6 GridImageSource Kernel loop walks only the first line per axis; origin cancels out of the pattern
⬜ B7 MinMaxCurvatureFlowFunction (3-D) acos taken on a gradient rescaled to length r, not 1 — wrong polar angle for radius ≥ 2
⬜ B8 MinMaxCurvatureFlowFunction Neighborhood scales divide by the stencil radius but only ±1 neighbors are sampled — update r² too small
🔄 B9 AdaptiveHistogramEqualization Constant input → iscale == 0 → NaN everywhere — fix: #6589
✅ B10 MultiLabelSTAPLEImageFilter Confusion-matrix increment walks into the next row on any voting tie — fix: #6579 (merged)
🔄 B11 STAPLEImageFilter All-background inputs → p_denom == 0 → NaN, unguarded — fix: #6589
🔄 B12 LabelOverlapMeasuresImageFilter false_positive_error guard tests the wrong quantity — NaN reachable — fix: #6589
⬜ B13 ThresholdMaximumConnectedComponentsImageFilter Initial bisection midpoint is (upper−lower)/2; unsigned wrap when it lands below lower
⬜ B14 ScalarImageKmeansImageFilter UseNonContiguousLabels interval underflows to 0xFFFFFFFF for > 255 classes
⬜ B15 LabelVotingImageFilter Undecided label max_label+1 wraps to 0 for a UInt8 image using all 256 labels
⬜ B16 MaskedFFTNormalizedCorrelationImageFilter Pixel squaring runs in the input integer type — int32 overflow before the cast to real
⬜ B17 IterativeDeconvolutionImageFilter Output regions clobbered before PadInput/CropOutput read them — OutputRegionMode silently ignored
✅ B18 FastApproximateRankImageFilter::SetRank Rank forwarded to only the first Dim−1 per-axis filters — last axis always median — fix: #6580 (merged)
✅ B19 AttributeMorphologyBaseImageFilter stable_sort end iterator off by one — last raster pixel never sorted — fix: #6581 (merged)
🔄 B20 BoxSigmaImageFilter Radius 0 → 0.0/0.0 → NaN for every pixel — fix: #6589
⬜ B21 LevelSetNeighborhoodExtractor Strict sign tests: a pixel exactly on the contour neutralizes its neighborhood; grid-aligned contour starves the outward march
⬜ B22 StructureTensorImageFilter (AnisotropicDiffusionLBR) Tensor smoothing K_ρ applied along axis 0 only (single-direction filter, SetDirection never called)
🔄 B23 CoherenceEnhancingDiffusionImageFilter Constant input → ∞ rescale → NaN tensors → negative step count via UB cast → zero iterations by accident — fix: #6589
🔄 B24 FastMarchingUpwindGradientImageFilter Duplicate target entries make AllTargets termination unreachable — fix: branch bug-fastmarching-alltargets-6575
✅ B25 Transform::ApplyToImageMetadata Null GetInverseTransform() (singular or non-linear transform) dereferenced without a check — fix: #6583 (merged)
⬜ B26 PatchBasedDenoisingImageFilter (POISSON) static_cast<PixelValueType>(0.99999) is 0 for integer types — step size collapses to 1e-5 always
🔄 B27 PatchBasedDenoisingImageFilter Update write-back casts an unclamped negative float to an unsigned pixel type — UB — fix: #6589
⬜ B28 UniformRandomSpatialNeighborSubsampler Search loops forever when the search box is exactly the query point (kernel-bandwidth path)
🔄 B29 GaussianRandomSpatialNeighborSubsampler Negative Gaussian variate cast to unsigned int — UB; rejection loop relies on the wrap — fix: #6589
⬜ B30 RegionBasedLevelSetFunction Reinitialization smoothing term silently degenerates to a bare Laplacian when CurvatureWeight == 0
✅ B31 IterativeInverseDisplacementFieldImageFilter smallestError carries over from the previous pixel when the mapped point starts outside the buffer — output depends on raster order — fix: #6576 (merged)
✅ B32 InverseDisplacementFieldImageFilter the field-subsampling resampler never gets the input's direction — with non-identity direction cosines the spline landmarks are not on the input lattice at all — fix: #6577 (merged)
✅ B33 VectorConfidenceConnectedImageFilter a re-estimation pass that visits zero pixels produces NaN statistics, which invert the Mahalanobis guard — the next flood includes every pixel in the image — fix: #6578 (merged)
🔄 B34 WarpImageFilter the same-information fast-path test omits size, so a valid larger-output configuration dies in an iterator containment check instead of taking the general path
🔄 B35 ExponentialDisplacementFieldImageFilter the field is divided by 1 << numiter — UB when numiter >= 31; unreachable by default (cap 20, clamped), but an explicit SetMaximumNumberOfIterations(>= 31) makes it live
⬜ B36 FastMarchingImageFilterBase::UpdateNeighbors the neighbor loop gates on the center being strictly interior, so a border seed never propagates along the boundary-normal axis — a face seed confines the march to that face, a corner seed marches nowhere
⬜ B37 FastMarchingImageFilterBase (NoHandles) marched-alive voxels never receive a connected-component label, so the facing-neighbor label test always passes and NoHandles degenerates to Strict
⬜ B38 FastMarchingImageFilterBase::Solve the zero-speed guard substitutes a finite coefficient, giving zero-speed pixels an arrival time ≈ 2⁵² instead of the old filter's ∞ — speed barriers leak
⬜ B39 ScalarToRGBColormapImageFilter / ColormapFunction NumericTraits<float/double>::min() (smallest positive) used as a "most negative" seed in two places — wrong rescale for non-positive float images, and a ≈FLT_MAX default input range
🔄 B40 MergeLabelMapFilter (KEEP) the deferred-collision deque is never cleared between inputs — with ≥ 3 inputs one LabelObject ends up aliased under two label keys — fix: #6590
🔄 B41 LabelMapContourOverlayImageFilter (SLICE_CONTOUR) the slice-radius loop tests the write cursor against m_SliceDimension where it means the read index — SliceDimension == 0 draws no contour at allfix: #6590
🔄 B42 LabelMapMaskImageFilter (Crop) an empty/zero-line object set leaves the bounding-box seeds untouched — signed overflow (UB), then a 2×2 region at index 2⁶³−1 — fix: #6590
🔄 B43 AttributeUniqueLabelMapFilter the "remove objects without lines" pass is while (it.IsAtEnd()) — inverted, never executes — fix: #6590
🔄 B44 LabelMap::PushLabelObject on a completely full label range the gap scan finds nothing, skips SetLabel, and silently overwrites the entry at the object's own label instead of throwing "map is full" — fix: #6590
🔄 B45 StatisticsLabelMapFilter weighted elongation/flatness: no eigenvalue clamp, one shared guard, no ratio > 0 check — an indefinite weighted covariance yields NaN where ShapeLabelMapFilter yields 0 — fix: #6590
⬜ B46 NormalizedCorrelationImageFilter both divisions unguarded — a constant template or a locally-constant neighborhood produces NaN
⬜ B47 TransformIOBaseTemplate::CorrectTransformPrecisionType find("float") result used unchecked — a transform-type string naming neither precision throws std::out_of_range past every ITK handler
⬜ B48 NiftiImageIO::Read rescale cast buffer sized per voxel, consumed per component — heap over-read for any multi-component image with scl_slope/scl_inter
⬜ B49 NiftiImageIO::Read RescaleFunction is given the voxel count — only the first numElts of a multi-component/complex buffer are rescaled
⬜ B50 NiftiImageIO::Read RAS↔LPS conversion never checks the 3-component requirement its own exception text names — 2/4-component vectors get a stride-3 sign flip across voxel boundaries
⬜ B51 NiftiImageIO spatial-dimension inference for intent-vector images never consults dim[1] — a 2-D vector image one row tall reads back as 1-D
⬜ B52 niftilib nifti_read_buffer (vendored) maps every non-finite float/double/complex component to 0 on read wherever isfinite is a macro (glibc: always) — NaN/±Inf pixels silently destroyed
⬜ B53 GiplImageIO::Write INT/UINT images throw "Pixel Type Unknown" only after the target file has been truncated and the header written — destroys existing data, leaves a 256-byte stub
⬜ B54 GiplImageIO::Read success test is !bad(); a short pixel block sets failbit/eofbit, never badbit — truncated file reads as success over an uninitialized tail
⬜ B55 GiplImageIO::Read (compressed) success flag is p != nullptr on the caller's always-non-null output buffer — gzread's byte count is never inspected
⬜ B56 GiplImageIO::Write header image_type is derived from the component type alone while the byte count multiplies by components — a vector image writes a scalar header plus N× the bytes, reading back as garbage
⬜ B57 VTKImageIO::Read ReadBufferAsBinary's bool (false on short read) is discarded — truncated BINARY file reads as success
⬜ B58 VTKImageIO::InternalReadImageInformation every sscanf return is discarded over uninitialized locals — an under-filled DIMENSIONS/SPACING/ORIGIN line sizes the image from indeterminate values (UB)
⬜ B59 VTKImageIO::InternalReadImageInformation attribute dispatch is an ordered substring test — SCALARS vector_field float matches the vector arm and is misparsed as a 3-component VECTORS line
⬜ B60 VTKImageIO ASCII write precision(16) is set on a throwaway header stream; pixel data is emitted through a second stream at the default 6 significant digits — silent precision loss
⬜ B61 ImageIOBase::ReadBuffer (ASCII) extraction variable declared outside the loop, stream state never checked — one bad/overflowing field latches into every later component (5 99999 7 8 as short reads [5, 32767, 32767, 32767])
⬜ B62 VTKImageIO::CanReadFile four GetNextLine calls outside the try block — a .vtk shorter than four lines throws "Premature EOF" through the factory instead of returning false
🔄 B63 NrrdImageIO::Write metadata dispatch NRRD_-prefixed keys are matched by strncmp of the candidate's length — NRRD_space directions is swallowed by the space handler, and unmatched NRRD_ keys (including the reader's own NRRD_pixel_original_axis) are silently dropped — fix: branch bug-nrrdio-6575
⬜ B64 MetaIO ElementDataFile = LIST (vendored) three unguarded paths: short slice list → success with uninitialized tail; negative file dimension → m_DimSize[-1]/m_SubQuantity[-1] OOB reads; == NDims → zero-length slice reads, still success
⬜ B65 MetaIO M_ReadElements (vendored) a LOCAL compressed header without CompressedDataSize seeks to file start and inflates its own header text over the uninitialized pixel buffer — success, garbage pixels
⬜ B66 MetaIO MET_PerformUncompression (vendored) unconditionally return true — truncated/corrupt/wrong-container streams and short output all report success
🔄 B67 NrrdIO per-axis field parsers (vendored) sizes/spacings/thicknesses/axis mins/axis maxs parse dim + 1 values into a NRRD_DIM_MAX(16)-element stack array — a 16-D header with a 17th token is a stack buffer overflow — fix: teem branch bug-nrrd-parse-axis-overflow (pushed to sourceforge; regenerates into ITK NrrdIO) + ITK-side bug-nrrdio-6575
🔄 B68 NrrdIO nrrdOriginCalculate (vendored) the gotMin loop tests axis[0]->min for every axis (literal 0, not the loop variable) — origins computed from NaN mins, or usable mins ignored — fix: teem branch bug-nrrd-origin-calculate (pushed to sourceforge; regenerates into ITK NrrdIO) + ITK-side bug-nrrdio-6575
🔄 B69 NrrdIO rnParse_kinds (vendored) the none token arm assigns the axis's centering, not its kindkinds: none … clobbers a previously parsed centers: value — fix: teem branch bug-nrrd-kinds-none-centering (pushed to sourceforge; regenerates into ITK NrrdIO) + ITK-side bug-nrrdio-6575
⬜ B70 PNGImageIO::WriteSlice the file is opened "wb" — truncating it — before the component-type switch whose default: throws; writing an int16 image destroys any existing .png and leaves it zero-byte
⬜ B71 PNGImageIO::WriteSlice height comes from GetDimensions(1) alone, no axis beyond it is consulted — a 3-D image writes only its first slice, with no error
⬜ B72 PNGImageIO::WriteSlice any component count > 4 declares 4 channels (PNG_COLOR_TYPE_RGB_ALPHA) while row pointers advance by the actual count — each row's trailing components are silently dropped
⬜ B73 HDF5ImageIO::WriteImageInformation the C-string meta-data branch calls constCstringObj->GetMetaDataObjectValue() under a guard that admits constCstringObj == nullptr — null dereference for a MetaDataObject<char *>
⬜ B74 HDF5ImageIO::ReadImageInformation every geometry dataset's length is taken on trust — spacing[i] / Dims[i] are indexed to numDims regardless of the file's actual dataset length, reading past the end of a heap vector before any pixel is touched
⬜ B75 HDF5ImageIO::Read the file datatype is passed as H5Dread's memory type, making the conversion the identity — a VoxelData stored STD_I16BE is copied without a byte swap and read back byte-swapped, silently
⬜ B76 JPEGImageIO::WriteSlice the "prefer centimetres" density branch tags the JFIF header density_unit = 0 ("aspect ratio only") while storing real dots-per-cm values — the reader's own density_unit > 0 guard then discards them, so the spacing is lost on a round-trip through ITK
⬜ B77 JPEGImageIO::Read the CMYK→RGB conversion assumes all four channels are inverted; that holds for plain CMYK, but for a YCCK source libjpeg un-inverts C/M/Y and passes K through — a white YCCK pixel decodes to black
⬜ B78 TIFFImageIO::ReadVolume each kept page is written at its raw directory index while the volume depth is sized from m_SubFiles — one ignored or untagged page before a SUBFILETYPE == 0 page and the last slice is written past the end of the buffer
⬜ B79 TIFFImageIO::ReadGenericImage the scanline buffer is sized from the current directory, but width/SamplesPerPixel/BitsPerSample stay pinned to directory 0 — a multi-page file whose later pages are narrower over-reads the buffer
⬜ B80 TIFFImageIO::ReadImageInformation the 32-bit arm of the component-type ladder has no default:, and CanRead never constrains SampleFormat — a 32-bit SAMPLEFORMAT_VOID page is read as whatever m_ComponentType already held
⬜ B81 TIFFImageIO::ReadGenericImage PHOTOMETRIC_MINISWHITE is read out un-inverted — the // check inverted comment above the plain-copy grayscale arm never grew a body
⬜ B82 TIFFImageIO::GetFormat MINISBLACK/MINISWHITE map to GRAYSCALE regardless of SamplesPerPixel, so a 2-sample grey+alpha page is declared 1-component and each row keeps only its first half
⬜ B83 TIFFImageIO::InternalWrite PHOTOMETRIC_RGB is written for every component count other than 1 — a 2-component image becomes an RGB-tagged file with SamplesPerPixel = 2 and no EXTRASAMPLES

Reproduced quirks and inconsistencies

These were verified the same way but may be judged intended, historical, or not worth changing — listed for the record, one line each, citations available on request (I have exact file:line and analysis for every row).

Verified quirks Q1–Q118 (table)
# Site Behavior
⬜ Q1 Deconvolution family The same KernelZeroMagnitudeThreshold ε is compared against |H| by Inverse but against the |H|²-carrying denominator by Tikhonov/Wiener — "regularization → 0 equals inverse" fails in the band ε ≤ |H| < √ε.
⬜ Q2 DiscreteGaussianDerivativeImageFilter Never calls FlipAxes(), unlike DerivativeImageFilter/GradientImageFilter (which do) — odd-order derivatives carry the opposite sign vs. its siblings. Also the inter-axis intermediate is Image<OutputPixelType>, so integer outputs truncate per axis pass (the local comments say "real to real").
⬜ Q3 ThresholdSegmentationLevelSetImageFilter The class doc (.h:63-65) states positive = inside — the opposite of the convention its own base class documents (itkSegmentationLevelSetImageFilter.h:82-84: negative inside).
⬜ Q4 Segmentation level-set functions CurvatureSpeed is overridden to PropagationSpeed only by GeodesicActiveContour and ShapeDetection; Threshold/Laplacian/Canny keep the base-class constant 1 — an undocumented asymmetry across siblings.
⬜ Q5 BinaryContourImageFilter Non-foreground pixels keep their original values verbatim (.hxx:164-179) — they are not normalized to BackgroundValue, so several distinct non-foreground input values all survive into the output.
⬜ Q6 SimpleContourExtractorImageFilter InputForegroundValue == InputBackgroundValue marks every foreground pixel as contour (the neighborhood scan includes the center pixel, .hxx:84).
⬜ Q7 BinaryReconstructionByErosionImageFilter Built on the complement via BinaryNot; BackgroundValue is consumed only as a self-consistent internal sentinel — the parameter has no effect on the output.
⬜ Q8 AnisotropicDiffusionImageFilter An unstable time step warns and proceeds; the clamp is present but commented out (.hxx:68).
⬜ Q9 ThresholdMaximumConnectedComponentsImageFilter m_LowerBoundary is written once in the constructor and read only by PrintSelf — dead configuration with no setter.
⬜ Q10 MinMaxCurvatureFlowFunction SetStencilRadius clamps to ≥ 1 (.hxx:43), making the radius == 0 early-outs (.hxx:217-220, 276-279) unreachable dead code.
⬜ Q11 ScalarImageKmeansImageFilter Overrides the estimator's defaults in GenerateData (SetMaximumIteration(200), threshold 0.0, .hxx:91-92), invisibly to users; and KdTreeBasedKmeansEstimator's loop tests the limit pre-increment after the refinement pass (.hxx:327-347) — MaximumIteration = n runs n+1 passes while GetCurrentIteration() reports n.
⬜ Q12 SLICImageFilter Enforce-connectivity relabels an undersized component to the previously-encountered label, whatever it is (.hxx:544-546); there is no final relabel/compaction pass; per-pixel distances accumulate in float.
⬜ Q13 BoxMean/BoxSigmaImageFilter The window crops at the image border and divides by the cropped count (itkBoxUtilities.h:322-324, 499-501) — border statistics use fewer samples. A ZeroFluxNeumannBoundaryCondition is declared (L215/389) but never used, suggesting replication was once intended.
⬜ Q14 BinShrinkImageFilter Throws when a shrink factor exceeds the axis size where ShrinkImageFilter silently clamps to 1 (.hxx:289-296 vs itkShrinkImageFilter.hxx:265-267); integer outputs round half-up while float outputs truncate (RoundIfInteger, .h:135-149).
⬜ Q15 AntiAliasBinaryImageFilter A constant input (even all-maximum) shifts to 0 under the max − (max−min)/2 iso-value and the strict > 0 sign test (.hxx:109; base .hxx:605) — the output is uniformly negative, i.e. "all inside".
⬜ Q16 ReinitializeLevelSetImageFilter Narrow-band mode pre-fills the whole output with ±NumericTraits<PixelType>::max() and only overwrites band points (.hxx:206-225) — far-field pixels keep ±max rather than a distance-like magnitude.
⬜ Q17 FastMarchingUpwindGradientImageFilter m_TargetValue is overwritten at every accepted point in NoTargets mode; in Some/AllTargets modes the reached test is sticky, so every later accepted point — target or not — keeps advancing it; OneTarget reports the last reached target, not the first (.hxx:154-227).
⬜ Q18 CollidingFrontsImageFilter ApplyConnectivity's flood fill is seeded on SeedPoints1 alone (.hxx:114-122), so the forced −1e-6 at every SeedPoints2 seed (.hxx:92-97) is dropped from the output unless that seed is face-connected to front 1's region.
⬜ Q19 itk::watershed::Segmenter Edge lists are sorted with a height-only operator< (itkWatershedSegmentTable.h:73-82); a NaN height compares "equivalent" to everything — a strict-weak-ordering violation (formal UB; benign merge-equal in libstdc++).
⬜ Q20 itkMath.h FloatAlmostEqual The absolute-difference test (default 0.1 · epsilon) returns equal before the signbit check (itkMath.h:334-352) — 0.0 == -0.0, and near-zero pairs millions of ULPs apart compare equal, partially defeating the documented ULP semantics.
⬜ Q21 MultiphaseDenseFiniteDifferenceImageFilter Computes the CFL time step then discards it: timeStep = 0.08; // FIXME !!! After all this work, assign a constant !!! Why ?? (.hxx:153-154).
⬜ Q22 Chan-Vese UseImageSpacing Builds m_ScaleCoefficients that nothing in the Chan-Vese chain reads; the PDE divides by m_InvSpacing from the feature image unconditionally while the re-distancing Maurer map inherits the level-set image's spacing — the flag's only live effect is maurer->SetUseImageSpacing (.hxx:227). Two images with different spacings drive the PDE and the re-distancing with different metrics.
⬜ Q23 ScalarChanAndVeseDenseLevelSet RMS/labeling m_ReinitializeCounter defaults to 1, so the reinit branch runs every iteration, zeroes the RMS accumulator, and refills it with the re-distancing residual (.hxx:214-248) — MaximumRMSError halting compares against the wrong quantity. CopyInputToOutput labels phi < 0 strictly (.hxx:69), so exactly-zero boundary pixels are excluded from the label.
⬜ Q24 ObjectMorphologyImageFilter BeforeThreadedGenerateData's fill-then-conditional-copy always reduces to output := input (the fill sentinel is constructed to differ from ObjectValue, .hxx:87-113); UseBoundaryCondition is dead configuration (installed but never enabled, .h:145 even warns "Don't forget to set UseBoundaryCondition to true!"); boundary detection is a hard-coded 3^dim box decoupled from the kernel radius (.hxx:130-198) — dilation is unaffected, erosion diverges substantially from true erosion (the header caveats this).
🔄 Q25 IterativeInverseDisplacementFieldImageFilter the coordinate-descent probe step is axis 0's spacing on every axis
🔄 Q26 DisplacementFieldJacobianDeterminantFilter SetDerivativeWeights silently flips UseImageSpacing off — precedence between the two is last-writer-wins, while the class doc describes only one ordering
🔄 Q27 WarpImageFilter m_OutputSize[0] == 0 alone keys "size unset"; output-grid defaults are unit/zero/identity from neither input; the field is sampled with edge replication, never out-of-domain
🔄 Q28 VectorConnectedComponentImageFilter zero vectors join anything under the default threshold (no special case); concept macro name typo InputValyeTypeIsFloatingCheck
🔄 Q29 MahalanobisDistanceMembershipFunction the det < 0 exception branch is unreachable; GetCovariance() never reflects the singular-case substitute
🔄 Q30 FastSymmetricForcesDemonsRegistrationFilter its difference function is ESMDemonsRegistrationFunction; the identically-named FastSymmetricForcesDemonsRegistrationFunction class is orphaned from its own filter
🔄 Q31 ExponentialDisplacementFieldImageFilter "take the ceil" comment vs + 1.0-then-truncate code (integral values overshoot by 1); a zero field still runs one squaring iteration
🔄 Q32 WarpVectorImageFilter (as wired by DiffeomorphicDemonsRegistrationFilter) under the nearest-neighbor-extrapolating interpolator, IsInsideBuffer is always true and m_EdgePaddingValue is dead code
🔄 Q33 ESMDemonsRegistrationFunction the WarpedMoving and MappedMoving gradient options coincide exactly at a zero displacement field (interior pixels, default linear interpolation, coincident grids)
🔄 Q34 LevelSetMotionRegistrationFilter/Function dt = 1/maxL1 makes the applied step at the governing pixel exactly Alpha-independent; thresholded pixels count toward the metric while out-of-buffer pixels don't; Halt adds an RMS==0 clause the base can never fire
🔄 Q35 LevelSetMotionRegistrationFunction one-sided difference steps add index-axis spacing to a physical coordinate — wrong direction and step under non-identity direction cosines
🔄 Q36 demons family zero-iteration runs report GetRMSChange() == 0 but GetMetric() == NumericTraits<double>::max() in the two filters that don't override GetRMSChange
🔄 Q37 LevelSetMotionRegistrationFunction smoothing the smoothed moving image is re-quantized to the moving image's own pixel type (u8 in → u8 smoothed) before the gradient; and any axis shorter than 4 pixels is a hard error for this family member only
🔄 Q38 FastMarchingImageFilterBase::IsChangeWellComposed3D builds its neighborhood iterator over GetRequestedRegion() (empty, never set) where the 2-D sibling and the strict-topology check use GetBufferedRegion() — harmless (only SetLocation+GetPixel are used) but inconsistent (.hxx:779 vs :583/:619)
⬜ Q39 LabelToRGBImageFilter background is always fixed black (m_BackgroundColor zero-filled at construction); the functor comment promising a "gray pixel matching the background intensity" describes LabelOverlayFunctor, not this class (itkLabelToRGBFunctor.h:93-98)
⬜ Q40 LabelToRGB/LabelOverlay palette the 0–255 palette constants are rescaled by NumericTraits<output RGB component>::max() rather than a fixed 255 (itkLabelToRGBFunctor.h:110-117) — a double RGB output scales the palette into [0, ≈1.8e308]
⬜ Q41 HessianToObjectnessMeasureImageFilter eigenvalues are sorted by |λ| with unstable std::sort (equal-magnitude order unspecified, .hxx:78-79); alpha == 0 skips only the R_A factor while beta == 0 (or a zero denominator) zeroes the whole measure (.hxx:111-151) — asymmetric parameter semantics
⬜ Q42 ComplexToModulusImageFilter computes sqrt(re*re + im*im) in the component type (.h:46-50): ComplexFloat32 overflows to inf above |component| ≈ 1.8e19 and flushes to 0 near denormals, where std::hypot would not
⬜ Q43 RelabelLabelMapFilter class doc says objects are reassigned via PushLabelObject; the inherited implementation uses SetLabel + AddLabelObject against a counter from 0 (itkAttributeRelabelLabelMapFilter.hxx:64-79)
⬜ Q44 LabelMap::HasLabel doc says the background label also returns true; the implementation is a plain find(label) != end(), which returns false for background (.hxx:151-153)
⬜ Q45 StatisticsLabelMapFilter weighted moments adds spacing²/12 (the pixel's own second moment) to every central-moment diagonal (.hxx:224-228) while ShapeLabelMapFilter does not — WeightedPrincipalMoments exceed PrincipalMoments by 1/12 per eigenvalue even on a constant feature image
⬜ Q46 StatisticsLabelMapFilter extremum indices min/max index updates are non-strict (v <= min, v >= max, .hxx:118-127) — on ties the recorded index is the last tie in raster order
⬜ Q47 itk::Statistics::Histogram::Initialize computes the bin interval and every non-final bin bound in float (itkHistogram.hxx:225-236) — a Histogram<double> gets float-quantized bin edges, which StatisticsLabelMapFilter's Median inherits
⬜ Q48 ImageBase::IsCongruentImageGeometry scales the coordinate tolerance by this->GetSpacing()[0] only (itkImageBase.hxx:400-405) — the predicate is asymmetric when the two images differ in axis-0 spacing, and axes 1+ are judged against axis 0's scale
⬜ Q49 DivideImageFilter::Div a near-zero divisor returns NumericTraits<TOutput>::max() regardless of the numerator's sign or magnitude (itkArithmeticOpsFunctors.h:142-151); through NormalizeToConstantImageFilter a zero-sum input sends the entire output to the type maximum
⬜ Q50 VotingBinaryHoleFillingImageFilter the else-branch stamps it.Set(foregroundValue) for any center value other than backgroundValue (.hxx:138-141) — a third pixel value is silently promoted to foreground without counting toward GetNumberOfPixelsChanged(); the iterative sibling inherits this
⬜ Q51 ExtractImageFilter the output origin ignores a collapsed axis's start index (and the collapsed↔retained direction coupling), so extracting slice k of an oblique volume reports slice 0's origin (.hxx:74-85, :162-179)
⬜ Q52 CenteredVersorTransformInitializer::ComputeRotation uses R = M·Fᵀ (.hxx:47), which does not satisfy the covariance-aligning identity R·Cov_f·Rᵀ = Cov_m (that requires Mᵀ·F); with per-image independent eigenvector sign canonicalization the initializer can 180°-flip an axis
⬜ Q53 CompositeTransform Get/SetParameters, Get/SetFixedParameters, and UpdateTransformParameters all flatten sub-transforms in reverse queue order (last-added first, .hxx:613,665-687,703,734,847) while GetNthTransform(n) indexes forward — two orderings in one class
⬜ Q54 FFTPadImageFilter SizeGreatestPrimeFactor == 1 pads each axis to an even size (.hxx:64-68) while the class doc says "1 or less … disable the extra padding" (.h:90) — only 0 disables
⬜ Q55 MaskedFFTNormalizedCorrelationImageFilter pads FFT sizes using only factors 2/3/5 (.hxx:528-545) while FFTConvolutionImageFilter uses the backend's greatest prime factor (11 under PocketFFT) — identical inputs get different padded sizes across the two FFT-based filters
⬜ Q56 RegistrationParameterScalesFromIndexShift the step scale is computed in continuous-index (voxel) units while MaximumStepSizeInPhysicalUnits defaults to the minimum virtual spacing (mm) — the gradient-descent learning rate divides mm by voxels, dimensionally mixed on anisotropic spacing (.hxx:66-79; itkGradientDescentOptimizerv4.hxx:272)
⬜ Q57 RegistrationParameterScalesEstimator CentralRegionRadius consulted only by CentralRegionSampling, which is selected only for displacement-field/B-spline transforms (.hxx:346-367, :203-211); linear/affine transforms take Corner/Random sampling and never read it, and for displacement fields the scales estimate is radius-independent
⬜ Q58 ObjectToObjectOptimizerBase weights optimizer weights within 1e-4 of 1.0 are flagged identity and never multiplied into the gradient (strict >, so exactly 1e-4 is also discarded, itkObjectToObjectOptimizerBase.cxx:154-164); the sibling scales-identity test uses a 100× looser 0.01
⬜ Q59 v4 optimizer weights application every v4 optimizer validates the weights-array length in the base StartOptimization, but the vnl-based LBFGS/LBFGSB and Amoeba optimizers then apply only scales and silently ignore well-sized weights (itkSingleValuedNonLinearVnlOptimizerv4.cxx:53-58); LBFGS2Optimizerv4, by contrast, does apply them
⬜ Q60 NiftiImageIO ITK_InputFilterName is encapsulated (:1102) and then wiped six lines later by SetImageIOMetadataFromNIfTI, whose first act is thisDic.Clear() (:630)
⬜ Q61 NiftiImageIO the "original file was non-orthogonal" warning is dead: stored under ITK_sform_corrected (:632), looked up under nifti_sform_corrected (:2038)
⬜ Q62 NiftiImageIO getQFormCodeFromDictionary/getSFormCodeFromDictionary results are unconditionally overwritten with NIFTI_XFORM_SCANNER_ANAT (:2077-2078) — yet their StringToInt32 still throws on a non-numeric dictionary value that would have been discarded anyway
⬜ Q63 NiftiImageIO IMG.NII passes CanWriteFile (extension check lowercases) but WriteImageInformation compares the raw uppercase extension against lowercase spellings only and throws "Bad Nifti file name" (:1173-1202)
⬜ Q64 niftilib (vendored) a magic-less (Analyze-7.5) header in a single .nii reports nifti_type == 1: convert_nhdr2nim sets ANALYZE, then nifti_set_type_from_names forces NIFTI1_1 whenever fname == iname — true for every single-file case (nifti1_io.c:3730, :3453-3454)
⬜ Q65 GiplImageIO the magic number is validated only in CanReadFile; ReadImageInformation re-reads the same 4 bytes and discards them, so a file supplied via SetImageIO gets no magic check (:566-584)
⬜ Q66 GiplImageIO NDims is a population count of non-trivial dims — a 2-D image round-trips as 3-D [nx, ny, 1], and malformed dims like [4, 0, 5, 1] give NDims == 2 with the 5 silently dropped (:294-312)
⬜ Q67 GiplImageIO image_type is byte-swapped only in the BigEndian arm and min/max are never swapped — unobservable, since the constructor pins BigEndian and all these header fields are read then discarded (:78, :325-328, :443-461)
⬜ Q68 GiplImageIO registers no read/write extensions (constructor omits AddSupported*Extension) and its real gate is a case-sensitive suffix test — IMAGE.GIPL is not recognized (:1079-1090)
⬜ Q69 GiplImageIO the compressed path uses gzopen/gzread throughout, which transparently pass through a non-gzip stream — a .gipl.gz containing a plain GIPL file is accepted and read verbatim
⬜ Q70 VTKImageIO::GetNextLine tolerates 5 consecutive empty lines and throws on the 6th, while its message says "consecutive 5 empty lines" — off by one from its own text (:44-46)
⬜ Q71 VTKImageIO::GetNextLine tests eof() before using the line, so a header whose final line lacks a trailing newline throws "Premature EOF" even though getline delivered the content (:50-56)
⬜ Q72 VTKImageIO the 2-D/3-D test is written twice and the first copy is dead — the surviving rule is "2-D iff dims[2] <= 1", so DIMENSIONS 4 1 3 is a one-row-tall 3-D volume (:210-221)
⬜ Q73 VTKImageIO a round-trip collapses ComplexFloat32 to a 2-component VectorFloat32, and a 1-component vector image to a scalar — the legacy format cannot express either distinction (:701-709, :311-329)
⬜ Q74 VTKImageIO the component count is never range-checked on either side (writer carries a // todo this should be asserted, :705) — COLOR_SCALARS c 7 reads as a 7-component VectorUInt8
⬜ Q75 ImageIOBase ASCII read is >> temp with an integer PrintType: a negative literal for an unsigned pixel type wraps rather than failing, and out-of-range 8-bit values narrow silently (−1 → 255, 300 → 44) (itkImageIOBase.cxx:804-811)
⬜ Q76 MetaImageIO complex pixels cannot round-trip: ElementNumberOfChannels == 2 is indistinguishable from a 2-vector, and the reader forces VECTOR for any channels > 1 (itkMetaImageIO.cxx:241-244, in-source // BUG: 8732)
⬜ Q77 MetaIO (vendored) MetaImage::CanRead re-tests the suffix case-sensitively against .mha/.mhd while the ITK factory and CanWriteFile match case-insensitively — IMG.MHA is writable but not readable (metaImage.cxx:1184-1199)
⬜ Q78 MetaIO (vendored) booleans test only value[0] == 'T'|'t'|'1'trash parses as true, yes as false (metaObject.cxx:1605-1642)
⬜ Q79 MetaIO (vendored) header-alias precedence is M_Read's apply order, not file order: Origin > Position > Offset, TransformMatrix > Rotation > Orientation, BinaryDataByteOrderMSB > ElementByteOrderMSB — a trailing ElementByteOrderMSB = False loses to an earlier BinaryDataByteOrderMSB = True (metaObject.cxx:1618-1707)
⬜ Q80 MetaIO (vendored) writes a zlib (RFC-1950) stream but reads zlib or gzip (inflateInit2(…, 47)) — the reader accepts a superset of what the writer produces (metaUtils.cxx:808, :862)
⬜ Q81 vendored gzip readers NrrdIO's gzio and zlib's gz_look fall back to a byte-for-byte copy when the gzip magic is absent, while niftilib's nifti_is_gzfile is a pure name test — a genuine gzip stream named .nii is never inflated, and a .nii.gz holding raw bytes reads via the copy fallback (gzio.c:501-519; nifti1_io.c:2656-2668) — reviewed, no change (see below)
🔄 Q82 NrrdImageIO only RAS/LAS/LPS are converted to LPS; every other space value (scanner-xyz, right-up, …) has its geometry used verbatim through an empty default: with no warning (itkNrrdImageIO.cxx:766-786) — fix: branch bug-nrrdio-6575
⬜ Q83 NrrdIO (vendored) data file: LIST reads filenames until EOF, so a blank line becomes an empty filename that later fails fopen("") — there is no in-band list terminator (parseNrrd.c:1347-1400) — reviewed, no change (see below)
🔄 Q84 NrrdIO (vendored) formatNRRD_write emits comments and key/value pairs after the data file: field, defeating the writer's own "data file must be last" ordering — latent header corruption for LIST-form writes with comments (formatNRRD.c:783-857; cf. write.c:724-728) — fix: teem branch bug-nrrd-datafile-last (pushed to sourceforge; regenerates into ITK NrrdIO) + ITK-side bug-nrrdio-6575
⬜ Q85 TxtTransformIO Parameters: lines are parsed by vnl read_ascii, which stops at the first unparseable token and reports success — Parameters: 1 2 oops 4 silently yields [1, 2] (itkTxtTransformIO.cxx:183-186)
⬜ Q86 TxtTransformIO one transform local is reassigned per Transform: line and buffered parameters apply only once both Parameters and FixedParameters have been seen — unpaired lines bind one transform's parameters to a different transform, silently (:168-232)
⬜ Q87 TxtTransformIO the #Insight Transform File V1.0 header is swallowed by the #-comment skip and never validated; CanReadFile checks only the .txt/.tfm extension (:148, :39-46)
⬜ Q88 TxtTransformIO an empty FixedParameters vector writes the line FixedParameters: with a trailing space (:238-250, :303-305)
⬜ Q89 HDF5TransformIO CanWriteFile claims .hdf/.h4/.hdf4/.he4 — HDF4 container names — though Write always creates an HDF5 file, and the lowercase extension table rejects .H5/.HDF5 (itkHDF5TransformIO.cxx:83-84, :424)
⬜ Q90 PNGImageIO::ReadImageInformation is a bare return; on a wrong PNG signature (:334-338) where Read throws "File is not png type: …" for the identical png_sig_cmp (:139-143) — a caller reaching it via SetImageIO (bypassing CanReadFile) gets a fabricated success with m_Dimensions unset. A missing file pointer returns silently the same way (:321-324)
⬜ Q91 PNGImageIO::CanWriteFile passes ignoreCase = false to HasSupportedWriteExtension (:497), unlike the trait's own case-insensitive default; .png and .PNG are both registered (:281-287), so foo.PNG is claimed but foo.PnG is not
⬜ Q92 m_ExpandRGBPalette's in-class initializer is false (itkImageIOBase.h:853) but ImageIOBase's constructor calls Reset(false), which unconditionally sets it true (itkImageIOBase.cxx:28,45) — PNGImageIO never touches the flag, so the header default is dead and every instance starts with palette expansion on
⬜ Q93 HDF5TransformIO's ReadParameters/ReadFixedParameters emit "Wrong data type for " << DataSetName << "in HDF5 File" — no space before in, so the dataset name runs into the word (itkHDF5TransformIO.cxx:171, :217)
⬜ Q94 the rank-1 check in both of those readers raises "Wrong # of dims for TransformType in HDF5 File" (:176, :222) — TransformType is the one dataset the check never covers
⬜ Q95 HDF5TransformIO::Read loops i < transformGroup.getNumObjs() and then openGroup(GetTransformName(i)) (:297-302); getNumObjs counts every link, so one stray dataset beside the 0, 1, … subgroups makes the loop ask for a group N that was never written and the read dies inside libhdf5 rather than reporting an extra object
⬜ Q96 HDF5TransformIO::CanReadFile is h5file.exists("/TransformGroup") (:59-66), i.e. H5Lexists — a dataset of that name passes, the factory hands the file over, and Read's openGroup (:295) throws an H5::Exception instead of the factory's clean "could not create Transform IO object"
⬜ Q97 HDF5ImageIO stores three axis conventions in one group: Dimension/Origin/Spacing in ITK order, fastest-moving first (:1151-1161); only VoxelData's dataspace is reversed (:1170-1182); and Directions writes m_Direction[i] as its row i (:490-510) while that is column i of the direction matrix — so a 4×3 2-D image writes Dimension = [4, 3] and a VoxelData of shape [3, 4], and the Directions dataset is the matrix transpose
⬜ Q98 HDF5ImageIO's ReadScalar (:410) and ReadVector (:479) both raise "Wrong # of dims for TransformType in HDF5 File" — the transform IO's message (Q94), verbatim, in a file that holds no transform. ReadDirections (:521) at least names the image directions
⬜ Q99 VoxelType is written on every Write (:1163-1166) and read on no Read: m_ComponentType is derived from imageSet.getDataType() (:752-753). A file whose VoxelType reads FLOAT over int16 voxels loads as Int16; a file with no VoxelType at all loads fine. The class comment (itkHDF5ImageIO.h:68-71) says it exists to make the file "user friendly with respect to HDF5 viewers", so this is by design — worth stating, since it looks authoritative
⬜ Q100 HDF5ImageIO::CanReadFile is h5file.exists("/ITKImage") (:647-654) — same H5Lexists shape as Q96, so a dataset named /ITKImage is claimed and ReadImageInformation's first openDataSet throws
⬜ Q101 three of ReadImageInformation's meta-data branches are unreachable: WriteScalar(unsigned long) stores NATIVE_UINT tagged isUnsignedLong (:322-342), so no ITK-written dataset is ever NATIVE_INT + isUnsignedLong as :801-805 expects; and on every LP64 platform H5Tequal(NATIVE_LONG, NATIVE_LLONG) and H5Tequal(NATIVE_ULONG, NATIVE_ULLONG) are true, so the arms at :874-897 swallow every 64-bit integer and those at :898-905 never run (the isLLong/isULLong tags inside the surviving arms are what actually distinguish the two)
⬜ Q102 a meta-data dataset of rank > 1 is skipped by continue (:785-789), and the if/else if chain over the fourteen NATIVE_* types ends in an else that tests only metaDataType == strType with no else of its own (:914-922) — so a compound, enum, array or variable-length-sequence dataset leaves no dictionary entry and raises nothing. A subgroup under MetaData, by contrast, is fatal: the loop opens every link with openDataSet (:776-782)
⬜ Q103 HDF5ImageIO's constructor registers .hdf, .h4, .hdf4, .he4 — HDF4 container names — for both reading and writing alongside the HDF5 ones (:36-43), the same eight-name list already noted on the transform side (Q89). Here CanWriteFile is HasSupportedWriteExtension(name) with ignoreCase = true (itkImageIOBase.h:788), so IMAGE.H5 is claimed for writing where the transform IO's case-sensitive CanWriteFile declines it
⬜ Q104 JPEGImageIO::Write throws "JPEG Writer can only write 2-dimensional images" before opening anything (:459-462), where PNGImageIO::WriteSlice silently writes only the first slice (B71) — the two sibling IOs disagree on whether a 3-D source is an error
⬜ Q105 JPEGImageIO::CanReadFile is markedly stricter than PNG's: case-sensitive extension (:102, ignoreCase = false), then the 0xFFD8 magic (:117-130), then a full jpeg_read_header parse (:152) — where PNGImageIO::CanReadFile stops at the 8-byte signature
⬜ Q106 JPEGImageIO::CanWriteFile passes ignoreCase = false (:449) against four registered spellings (:308-314) — .jpg, .JPG, .jpeg, .JPEG are claimed, .Jpg is not. Same shape as PNG's (Q91)
⬜ Q107 m_UseCompression is assigned false in the constructor (:299) and never read: jpeg_set_quality is called unconditionally (:537), so only Quality/CompressionLevel has any effect and UseCompressionOn() is silently inert for this IO
⬜ Q108 Two quirks of the TIFF resolution tags. (a) Spacing survives a round-trip only to float precision: WriteImageInformation stores 25.4 / m_Spacing[i] (:587, :794) into XRESOLUTION, which libtiff holds as a float (tif_dir.h:98; TIFF_RATIONAL/TIFF_SETGET_FLOAT, tif_dirinfo.c:90) and m_XResolution mirrors as float — so a spacing of 1.0 reads back as 1.0000000150…. (b) TIFFReaderInternal::Initialize reads XRESOLUTION with the non-defaulted TIFFGetField (:202) over Clean()'s seed of 1, while the read guard is only unit > 0 && m_XResolution > 0 (:375-385) — so a page with RESOLUTIONUNIT = 2 (inch) and no XRESOLUTION tag yields a spacing of 25.4 rather than the default 1.0. (An absent RESOLUTIONUNIT seeds unit = 1, which takes neither branch, so the plain case is safe.)
⬜ Q109 FastChamferDistanceImageFilter the anti-raster sweep is for (it.GoToEnd(), --it; !it.IsAtBegin(); --it) (.hxx:194), so flat index 0 is never the center of the backward pass — harmless, because the pass writes to lower-index neighbours and flat index 1 still updates it. The 3-D weights are the float literals 0.92644, 1.34065, 1.65849 (.hxx:38, :41, :44)
⬜ Q110 IsolatedConnectedImageFilter the bisection bounds and midpoint are typed NumericTraits<InputPixelType>::AccumulateType (.hxx:147, :178-180, :243-245), which for unsigned int is unsigned int — no widening (itkNumericTraits.h:1027). guess = (upper + lower) / 2 (.hxx:231, :296) can therefore overflow for thresholds near UINT_MAX
⬜ Q111 STAPLEImageFilter contrary to the common description of the algorithm, there is no p = q = 0.99999 seeding: W is seeded from the rater mean (.hxx:116), the prior g_t is computed once before the loop (:123), per-rater convergence is (dp)² , (dq)² ≤ 1e-14 (:51, :215-220), and the foreground test is a two-sided ε-window with ε = 1e-10 (:94). Worth stating because implementations that copy the papers' seeding will not reproduce ITK
⬜ Q112 BinaryMedianImageFilter the majority rule is strict > against neighborhoodSize / 2 (.hxx:121, :136-143), so a tie goes to background; and the output is always exactly ForegroundValue or BackgroundValue — a pixel whose value matches neither is not passed through
⬜ Q113 ChangeLabelImageFilter the change map is std::map<InputPixelType, OutputPixelType> (.h:57), so keys are quantized through the pixel type and a repeated key is last-wins (.h:77); operator() performs a single lookup (.h:92-101), so a→b together with b→c is not transitive — a maps to b
⬜ Q114 BSplineDecompositionImageFilter the causal-coefficient initialization switches between an accelerated truncated sum and the exact mirror-boundary sum on horizon < dataLength, where `horizon = ceil(log(tol)/log(
⬜ Q115 PatchBasedDenoisingImageFilter noise-model fidelity on the first iteration the output equals the input, so GAUSSIAN's 2·(in − out) (.hxx:2015) and POISSON's (in − out)/(out + 1e-5) (:2049) are both identically zero. With SimpleITK's default NumberOfIterations = 1, neither noise model contributes anything; RICIAN's Bessel-ratio term (:2031-2032) is nonzero and is unaffected
⬜ Q116 PatchBasedDenoisingImageFilter internals three of a kind: ResolveSigmaUpdate writes m_KernelBandwidthSigma (.hxx:1854, :1860) before returning, and the caller then compares |sigmaUpdate| against the already-updated sigma (:1427); m_MinProbability = 2.2e-306 (:43) is added to the entropy probability and to its first and second derivatives (:1734-1738); and on the skipped-query path the subsampler's offset keeps its zero initialization rather than being recomputed by ComputeOffset (itkSpatialNeighborSubsampler.hxx:140, :147-151)
⬜ Q117 JoinSeriesImageFilter size mismatch asymmetric and undocumented. Each input's requested region is derived from the primary input's size (.hxx:209-216), so a subsequent input that is smaller throws InvalidRequestedRegionError from region propagation, while one that is larger is silently corner-cropped to [0, primary_size). (Physical congruency is checked separately, with coordinateTol = m_CoordinateTolerance · spacing[0] — the first dimension's spacing for every axis, itkImageToImageFilter.hxx:184-185)
⬜ Q118 TransformToDisplacementFieldFilter linear fast path the scan-line endpoints are the continuous indices idx[0] and idx[0] + size[0] — one past the last pixel — with alpha = i / size[0] (.hxx:239, :245, :255-256). This is exact rather than off-by-one, because for a linear transform T(p) − p is affine, so interpolating between those two endpoints reproduces the displacement at every interior pixel. Recorded because it reads like an off-by-one and is not

Also: LinearAnisotropicDiffusionLBRImageFilter's Selling decomposition bails out after 200 superbase flips by printing "Warning: Selling's algorithm not stabilized." to std::cerr and continuing with a possibly non-obtuse superbase — which can yield negative stencil weights. The 2-D branch guards obtuseness with a debug-only assert; the 3-D branch has no check at all (.hxx:185-263).

AI assistance
  • Tool: Claude Code (claude-opus-4-8)
  • Role: found the defects while cross-checking an independent reimplementation of these filters against this source; performed the pre-filing re-verification pass (re-reading every cited line on main at e46eb723a5, checking declared types and constructor defaults, re-deriving each consequence)
  • Contribution: the inventory above
  • Testing: source-level verification against e46eb723a5; behavioral claims cross-checked against the independent reimplementation's pinned tests. B28 is source analysis only (not run to a live hang) and is labeled as such inline.
  • Items whose original analysis did not survive re-verification were corrected or dropped before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions