| ✅ B1 |
ProjectionImageFilter |
Collapsed-axis origin shift uses the axis index, unsigned; axis 0 wraps to a ~2³¹·spacing shift — fix: #6594 (merged) |
| ✅ B2 |
N4BiasFieldCorrectionImageFilter::SharpenImage |
Degenerate bin range → 0.0/0.0 = NaN → NaN→int cast (UB) → unchecked histogram index — fix: #6589 (merged) |
| ✅ B3 |
SLICImageFilter |
Empty cluster → 0/0 NaN centroid → NaN→integer index cast (UB) — fix: #6589 (merged) |
| ✅ B4 |
WienerDeconvolutionImageFilter |
Pf == Pn → complex division by zero → NaN output (guard passes on inf) — fix: #6589 (merged) |
| ✅ B6 |
GridImageSource |
Kernel loop walks only the first line per axis; origin cancels out of the pattern — fix: #6600 (merged) |
| ✅ B7 |
MinMaxCurvatureFlowFunction (3-D) |
acos taken on a gradient rescaled to length r, not 1 — wrong polar angle for radius ≥ 2 — fixed: #6603 (merged) |
| ✅ B8 |
MinMaxCurvatureFlowFunction |
Neighborhood scales divide by the stencil radius but only ±1 neighbors are sampled — update r² too small — fix: #6604 (merged) |
| ✅ B9 |
AdaptiveHistogramEqualization |
Constant input → iscale == 0 → NaN everywhere — fix: #6589 (merged) |
| ✅ 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 (merged) |
| ✅ B12 |
LabelOverlapMeasuresImageFilter |
false_positive_error guard tests the wrong quantity — NaN reachable — fix: #6589 (merged) |
| ✅ B13 |
ThresholdMaximumConnectedComponentsImageFilter |
Initial bisection midpoint is (upper−lower)/2; unsigned wrap when it lands below lower — fixed: #6595 (merged) |
| ✅ B14 |
ScalarImageKmeansImageFilter |
UseNonContiguousLabels interval underflows to 0xFFFFFFFF for > 255 classes — fix: #6596 (merged) |
| ✅ B15 |
LabelVotingImageFilter |
Undecided label max_label+1 wraps to 0 for a UInt8 image using all 256 labels — fix: #6597 (merged) |
| ✅ B16 |
MaskedFFTNormalizedCorrelationImageFilter |
Pixel squaring runs in the input integer type — int32 overflow before the cast to real — fix: #6598 (merged) |
| ✅ B17 |
IterativeDeconvolutionImageFilter |
Output regions clobbered before PadInput/CropOutput read them — OutputRegionMode silently ignored — fixed: #6599 (merged) |
| ✅ 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 (merged) |
| ✅ B21 |
LevelSetNeighborhoodExtractor |
Strict sign tests: a pixel exactly on the contour neutralizes its neighborhood; grid-aligned contour starves the outward march — fixed: #6601 (merged) |
| ✅ B22 |
StructureTensorImageFilter (AnisotropicDiffusionLBR) |
Tensor smoothing K_ρ applied along axis 0 only (single-direction filter, SetDirection never called) — fix: #6602 (merged) |
| ✅ B23 |
CoherenceEnhancingDiffusionImageFilter |
Constant input → ∞ rescale → NaN tensors → negative step count via UB cast → zero iterations by accident — fix: #6589 (merged) |
| ✅ B24 |
FastMarchingUpwindGradientImageFilter |
AllTargets stop condition counts arrival events, not distinct targets — a duplicated target point ends the march early — fix: #6658 (merged) |
| ✅ 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 — fix: #6633 (merged) |
| ✅ B27 |
PatchBasedDenoisingImageFilter |
Update write-back casts an unclamped negative float to an unsigned pixel type — UB — fix: #6589 (merged) |
| ✅ B28 |
UniformRandomSpatialNeighborSubsampler |
Search loops forever when the search box is exactly the query point (kernel-bandwidth path) — fix: #6605 (merged) |
| ✅ B29 |
GaussianRandomSpatialNeighborSubsampler |
Negative Gaussian variate cast to unsigned int — UB; rejection loop relies on the wrap — fix: #6589 (merged) |
| ✅ B30 |
RegionBasedLevelSetFunction |
Reinitialization smoothing term silently degenerates to a bare Laplacian when CurvatureWeight == 0 — fix: #6606 (merged) |
| ✅ 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 — fix: #6672 (merged) |
| ✅ 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 — fix: #6673 (merged) |
| ✅ 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 — fixed: #6613 (merged) |
| ✅ 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 — fixed: #6613 (merged) |
| ✅ 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 — fixed: #6613 (merged) |
| ✅ 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 — fixed: #6615 (merged) |
| ✅ 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 (merged) |
| ✅ 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 all — fix: #6590 (merged) |
| ✅ 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 (merged) |
| ✅ B43 |
AttributeUniqueLabelMapFilter |
the "remove objects without lines" pass is while (it.IsAtEnd()) — inverted, never executes — fix: #6590 (merged) |
| ✅ 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 (merged) |
| ✅ 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 (merged) |
| ✅ B46 |
NormalizedCorrelationImageFilter |
both divisions unguarded — a constant template or a locally-constant neighborhood produces NaN — fixed: #6612 (merged) |
| ✅ B47 |
TransformIOBaseTemplate::CorrectTransformPrecisionType |
find("float") result used unchecked — a transform-type string naming neither precision throws std::out_of_range past every ITK handler — fixed: #6608 (merged) |
| ✅ 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 — fix: #6625 (merged) |
| ✅ B49 |
NiftiImageIO::Read |
RescaleFunction is given the voxel count — only the first numElts of a multi-component/complex buffer are rescaled — fix: #6625 (merged) |
| ✅ 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 — fix: #6625 (merged) |
| ✅ 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 — fixed: #6611 (merged) |
| ✅ 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 — fixed: #6611 (merged) |
| ✅ 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 — fixed: #6611 (merged) |
| ✅ 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 — fixed: #6611 (merged) |
| ✅ B57 |
VTKImageIO::Read |
ReadBufferAsBinary's bool (false on short read) is discarded — truncated BINARY file reads as success — fixed: #6610 (merged) |
| ✅ 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) — fixed: #6610 (merged) |
| ✅ 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 — fixed: #6610 (merged) |
| ✅ 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 — fixed: #6610 (merged) |
| ✅ 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]) — fixed: #6609 (merged) |
| ✅ 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 — fixed: #6610 (merged) |
| ✅ 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 — fixed: #6674 (merged), upstream teem branch bug-nrrd-parse-axis-overflow on sourceforge |
| ✅ 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 — fixed: #6674 (merged), upstream teem branch bug-nrrd-origin-calculate on sourceforge |
| ✅ B69 |
NrrdIO rnParse_kinds (vendored) |
the none token arm assigns the axis's centering, not its kind — kinds: none … clobbers a previously parsed centers: value — fixed: #6674 (merged), upstream teem branch bug-nrrd-kinds-none-centering on sourceforge |
| ✅ 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 — fixed: #6614 (merged) |
| ✅ 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 — fixed: #6614 (merged) |
| ✅ 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 *> — fix: #6626 (merged) |
| ✅ 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 — fix: #6626 (merged) |
| ✅ 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 — fix: #6627 (merged) |
| ✅ 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 — fix: #6627 (merged) |
| ✅ 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 — fix: #6636 (merged) |
| ✅ 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 — fix: #6636 (merged) |
| ✅ 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 — fix: #6636 (merged) |
| ✅ B81 |
TIFFImageIO::ReadGenericImage |
PHOTOMETRIC_MINISWHITE is read out un-inverted — the // check inverted comment above the plain-copy grayscale arm never grew a body — fix: #6636 (merged) |
| ✅ 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 — fix: #6636 (merged) |
| ✅ 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 — fix: #6636 (merged) |
| ✅ B84 |
MultiLabelSTAPLEImageFilter |
label loop counters are typed InputPixelType but bounded by m_TotalLabelCount — a uint8 input using all 256 labels wraps the counter 255 → 0 and InitializePriorProbabilities() never terminates (reproduced as a live hang, not source analysis) — fix: #6607 (merged) |
| ✅ B85 |
MultiLabelSTAPLEImageFilter::InitializeConfusionMatrixArrayFromVoting |
the internal seeding LabelVotingImageFilter never receives the outer filter's SetLabelForUndecidedPixels(), so the seed pass always uses automatic label selection regardless of the caller's setting — fix: #6607 (merged) |
| ✅ B86 |
GiplImageIO::ReadImageInformation / CanReadFile |
the 256-byte header is consumed as 15 separate gzread/istream::read pairs, none of whose byte counts is checked — a truncated or non-GIPL file populates dimensions, spacing and origin from uninitialized locals (same shape as B54/B55, but no success flag is computed at all) — fix: #6628 (merged) |
| ✅ B87 |
TileImageFilter |
A 0 in any non-last Layout axis is never validated: with a non-zero last axis it indexes an empty std::vector and runs a ~2³² iteration loop; with a zero last axis (the constructor default) it divides by zero — fix: #6630 (merged) |
| ✅ B88 |
GDCMImageIO::Read (SINGLEBIT) |
The 1-bit expansion walks the buffer as an unpadded bitstream, but GDCM pads each row to a byte boundary — every row after the first is decoded from the wrong bits when the width is not a multiple of 8 — fix: #6639 (merged), replacing the reverted #6629 |
| ✅ B89 |
GDCMImageIO::InternalReadImageInformation (ultrasound/hardcopy arm) |
A single-valued (0028,0030) leaves the second Element slot never written — axis-0 spacing is read from indeterminate heap memory; the assert(GetLength() == 2) guard is vacuous and cannot fire even in a Debug build — fix: #6639 (merged), replacing the reverted #6629 |
| ✅ B90 |
LabelSetErodeImageFilter / LabelSetDilateImageFilter |
GenerateData only calls AllocateOutputs() and every write is gated on m_Scale[d] > 0; under UseImageSpacing a zero radius on the last axis skips erosion's only output-writing pass — the filter returns uninitialized memory — fix: #6631 (merged) |
| ✅ B92 |
ImageSeriesReader::GenerateOutputInformation |
An ITK_ImageOrigin override array shorter than the output dimension is silently shrunk by Array::operator=, and the next loop still indexes to ImageDimension - 1 — unchecked out-of-bounds read. No in-tree ImageIO writes that key, so it is live only for an out-of-tree/custom ImageIO — fix: #6632 (merged) |
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 92 suspected bugs and 118 quirks (originally 30 and 24; follow-up batches B31–B92 and Q25–Q118) — too many to file individually without flooding the tracker, so I am reporting them together here for triage. The quirks now live in #6684; this issue tracks the 92 suspected bugs only. 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.
Status at a glance (maintainer triage — updated 2026-07-23 by @hjmjohnson)
Every ledger PR that has completed review has merged (latest: #6672 (B34) and #6673 (B35), split from superseded draft #6669). Three are open now: #6681 (B63/Q82), #6682 (B91), #6683 (B52). The completed work is collapsed below; what follows is only what still needs someone.
⬅ Outstanding work
Bugs fixed, PR open (3)
nifti_read_buffer(vendored)isfiniteis a macro — always, since it is a C99<math.h>macro everywhere — so NaN/±Inf pixels are silently destroyed; confirmed empirically (1.5 nan inf -inf 2.5reads back1.5 0 0 0 2.5) — fix: #6683 (open), via ISC/nifti_clibf24a6078+UpdateFromUpstream.shvnl_matlab_readhdr::read_hdr(vendored)switchlists 7 of the 8 legaltype_tencodings —1010(big-endian, column-wise, single-precision), which vnl's own writer emits on a big-endian host, is byte-swapped as though foreign — fix: #6682 (open), vendored-only by maintainer decisionNrrdImageIO::Writemetadata dispatchNRRD_-prefixed keys are matched bystrncmpof the candidate's length —NRRD_space directionsis swallowed by thespacehandler, and unmatchedNRRD_keys (including the reader's ownNRRD_pixel_original_axis) are silently dropped — fix: #6681 (open)f24a6078, both nifti1 and nifti2) and imported viaUpdateFromUpstream.sh; opt-infix_floatsoption keeps the old default for other consumers; 49/49 NIfTI testsVendored MetaIO — upstream PRs open at Kitware/MetaIO (3)
All three are fixed in draft PRs against Kitware/MetaIO, the repo
Modules/ThirdParty/MetaIO/UpdateFromUpstream.shimports from. ITK picks them up by re-running that script once they merge; no ITK-side change is needed. Each carries a new MetaIO regression test verified fail-before/pass-after.ElementDataFile = LIST(vendored)LIST -1indexesm_DimSize[-1]/m_SubQuantity[-1];LIST NDimsreads nothing) and a short slice list both report success — fix: Kitware/MetaIO#141M_ReadElements(vendored)CompressedDataSizemeasures from the start of the file, so aLOCALimage inflates its own ASCII header — fix: Kitware/MetaIO#142MET_PerformUncompression(vendored)return true; also itsdest_posaccounting skipped the finalinflatecall, so no byte-count check was possible — both fixed, and both call sites now honour the result — fix: Kitware/MetaIO#142Quirks — moved to #6684
All 118 quirks were split into #6684 so this issue can be driven to completion. They are lower priority and deliberately deferred until the bug ledger closes. Of them, 16 are already dispositioned (Q25–Q38 in open PR #6671, Q82 in open PR #6681, Q84 merged via #6674); the remaining 102 are untriaged and tracked there.
✅ Completed — 82 merged bug fixes (table)
ProjectionImageFilterunsigned; axis 0 wraps to a ~2³¹·spacing shift — fix: #6594 (merged)N4BiasFieldCorrectionImageFilter::SharpenImage0.0/0.0 = NaN→ NaN→int cast (UB) → unchecked histogram index — fix: #6589 (merged)SLICImageFilter0/0NaN centroid → NaN→integer index cast (UB) — fix: #6589 (merged)WienerDeconvolutionImageFilterPf == Pn→ complex division by zero → NaN output (guard passes on inf) — fix: #6589 (merged)GridImageSourceMinMaxCurvatureFlowFunction(3-D)acostaken on a gradient rescaled to length r, not 1 — wrong polar angle for radius ≥ 2 — fixed: #6603 (merged)MinMaxCurvatureFlowFunctionAdaptiveHistogramEqualizationiscale == 0→ NaN everywhere — fix: #6589 (merged)MultiLabelSTAPLEImageFilterSTAPLEImageFilterp_denom == 0→ NaN, unguarded — fix: #6589 (merged)LabelOverlapMeasuresImageFilterfalse_positive_errorguard tests the wrong quantity — NaN reachable — fix: #6589 (merged)ThresholdMaximumConnectedComponentsImageFilter(upper−lower)/2; unsigned wrap when it lands belowlower— fixed: #6595 (merged)ScalarImageKmeansImageFilterUseNonContiguousLabelsinterval underflows to0xFFFFFFFFfor > 255 classes — fix: #6596 (merged)LabelVotingImageFiltermax_label+1wraps to 0 for a UInt8 image using all 256 labels — fix: #6597 (merged)MaskedFFTNormalizedCorrelationImageFilterIterativeDeconvolutionImageFilterPadInput/CropOutputread them —OutputRegionModesilently ignored — fixed: #6599 (merged)FastApproximateRankImageFilter::SetRankAttributeMorphologyBaseImageFilterstable_sortend iterator off by one — last raster pixel never sorted — fix: #6581 (merged)BoxSigmaImageFilter0.0/0.0→ NaN for every pixel — fix: #6589 (merged)LevelSetNeighborhoodExtractorStructureTensorImageFilter(AnisotropicDiffusionLBR)K_ρapplied along axis 0 only (single-direction filter,SetDirectionnever called) — fix: #6602 (merged)CoherenceEnhancingDiffusionImageFilterFastMarchingUpwindGradientImageFilterTransform::ApplyToImageMetadataGetInverseTransform()(singular or non-linear transform) dereferenced without a check — fix: #6583 (merged)PatchBasedDenoisingImageFilter(POISSON)static_cast<PixelValueType>(0.99999)is 0 for integer types — step size collapses to 1e-5 always — fix: #6633 (merged)PatchBasedDenoisingImageFilterUniformRandomSpatialNeighborSubsamplerSearchloops forever when the search box is exactly the query point (kernel-bandwidth path) — fix: #6605 (merged)GaussianRandomSpatialNeighborSubsamplerunsigned int— UB; rejection loop relies on the wrap — fix: #6589 (merged)RegionBasedLevelSetFunctionCurvatureWeight == 0— fix: #6606 (merged)IterativeInverseDisplacementFieldImageFiltersmallestErrorcarries over from the previous pixel when the mapped point starts outside the buffer — output depends on raster order — fix: #6576 (merged)InverseDisplacementFieldImageFilterVectorConfidenceConnectedImageFilterWarpImageFilterExponentialDisplacementFieldImageFilter1 << numiter— UB whennumiter >= 31; unreachable by default (cap 20, clamped), but an explicitSetMaximumNumberOfIterations(>= 31)makes it live — fix: #6673 (merged)FastMarchingImageFilterBase::UpdateNeighborsFastMarchingImageFilterBase(NoHandles)NoHandlesdegenerates toStrict— fixed: #6613 (merged)FastMarchingImageFilterBase::SolveScalarToRGBColormapImageFilter/ColormapFunctionNumericTraits<float/double>::min()(smallest positive) used as a "most negative" seed in two places — wrong rescale for non-positive float images, and a ≈FLT_MAXdefault input range — fixed: #6615 (merged)MergeLabelMapFilter(KEEP)LabelObjectends up aliased under two label keys — fix: #6590 (merged)LabelMapContourOverlayImageFilter(SLICE_CONTOUR)m_SliceDimensionwhere it means the read index —SliceDimension == 0draws no contour at all — fix: #6590 (merged)LabelMapMaskImageFilter(Crop)AttributeUniqueLabelMapFilterwhile (it.IsAtEnd())— inverted, never executes — fix: #6590 (merged)LabelMap::PushLabelObjectSetLabel, and silently overwrites the entry at the object's own label instead of throwing "map is full" — fix: #6590 (merged)StatisticsLabelMapFilterratio > 0check — an indefinite weighted covariance yields NaN whereShapeLabelMapFilteryields 0 — fix: #6590 (merged)NormalizedCorrelationImageFilterTransformIOBaseTemplate::CorrectTransformPrecisionTypefind("float")result used unchecked — a transform-type string naming neither precision throwsstd::out_of_rangepast every ITK handler — fixed: #6608 (merged)NiftiImageIO::Readscl_slope/scl_inter— fix: #6625 (merged)NiftiImageIO::ReadRescaleFunctionis given the voxel count — only the firstnumEltsof a multi-component/complex buffer are rescaled — fix: #6625 (merged)NiftiImageIO::ReadGiplImageIO::WriteGiplImageIO::Read!bad(); a short pixel block setsfailbit/eofbit, neverbadbit— truncated file reads as success over an uninitialized tail — fixed: #6611 (merged)GiplImageIO::Read(compressed)p != nullptron the caller's always-non-null output buffer —gzread's byte count is never inspected — fixed: #6611 (merged)GiplImageIO::Writeimage_typeis 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 — fixed: #6611 (merged)VTKImageIO::ReadReadBufferAsBinary'sbool(false on short read) is discarded — truncated BINARY file reads as success — fixed: #6610 (merged)VTKImageIO::InternalReadImageInformationsscanfreturn is discarded over uninitialized locals — an under-filledDIMENSIONS/SPACING/ORIGINline sizes the image from indeterminate values (UB) — fixed: #6610 (merged)VTKImageIO::InternalReadImageInformationSCALARS vector_field floatmatches thevectorarm and is misparsed as a 3-component VECTORS line — fixed: #6610 (merged)VTKImageIOASCII writeprecision(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 — fixed: #6610 (merged)ImageIOBase::ReadBuffer(ASCII)5 99999 7 8asshortreads[5, 32767, 32767, 32767]) — fixed: #6609 (merged)VTKImageIO::CanReadFileGetNextLinecalls outside the try block — a.vtkshorter than four lines throws "Premature EOF" through the factory instead of returning false — fixed: #6610 (merged)sizes/spacings/thicknesses/axis mins/axis maxsparsedim + 1values into aNRRD_DIM_MAX(16)-element stack array — a 16-D header with a 17th token is a stack buffer overflow — fixed: #6674 (merged), upstream teem branchbug-nrrd-parse-axis-overflowon sourceforgenrrdOriginCalculate(vendored)gotMinloop testsaxis[0]->minfor every axis (literal 0, not the loop variable) — origins computed from NaN mins, or usable mins ignored — fixed: #6674 (merged), upstream teem branchbug-nrrd-origin-calculateon sourceforgernParse_kinds(vendored)nonetoken arm assigns the axis's centering, not its kind —kinds: none …clobbers a previously parsedcenters:value — fixed: #6674 (merged), upstream teem branchbug-nrrd-kinds-none-centeringon sourceforgePNGImageIO::WriteSlice"wb"— truncating it — before the component-type switch whosedefault:throws; writing anint16image destroys any existing.pngand leaves it zero-byte — fixed: #6614 (merged)PNGImageIO::WriteSlicePNG_COLOR_TYPE_RGB_ALPHA) while row pointers advance by the actual count — each row's trailing components are silently dropped — fixed: #6614 (merged)HDF5ImageIO::WriteImageInformationconstCstringObj->GetMetaDataObjectValue()under a guard that admitsconstCstringObj == nullptr— null dereference for aMetaDataObject<char *>— fix: #6626 (merged)HDF5ImageIO::ReadImageInformationspacing[i]/Dims[i]are indexed tonumDimsregardless of the file's actual dataset length, reading past the end of a heap vector before any pixel is touched — fix: #6626 (merged)JPEGImageIO::WriteSlicedensity_unit = 0("aspect ratio only") while storing real dots-per-cm values — the reader's owndensity_unit > 0guard then discards them, so the spacing is lost on a round-trip through ITK — fix: #6627 (merged)JPEGImageIO::ReadTIFFImageIO::ReadVolumem_SubFiles— one ignored or untagged page before aSUBFILETYPE == 0page and the last slice is written past the end of the buffer — fix: #6636 (merged)TIFFImageIO::ReadGenericImagewidth/SamplesPerPixel/BitsPerSamplestay pinned to directory 0 — a multi-page file whose later pages are narrower over-reads the buffer — fix: #6636 (merged)TIFFImageIO::ReadImageInformationdefault:, andCanReadnever constrainsSampleFormat— a 32-bitSAMPLEFORMAT_VOIDpage is read as whateverm_ComponentTypealready held — fix: #6636 (merged)TIFFImageIO::ReadGenericImagePHOTOMETRIC_MINISWHITEis read out un-inverted — the// check invertedcomment above the plain-copy grayscale arm never grew a body — fix: #6636 (merged)TIFFImageIO::GetFormatMINISBLACK/MINISWHITEmap toGRAYSCALEregardless ofSamplesPerPixel, so a 2-sample grey+alpha page is declared 1-component and each row keeps only its first half — fix: #6636 (merged)TIFFImageIO::InternalWritePHOTOMETRIC_RGBis written for every component count other than 1 — a 2-component image becomes an RGB-tagged file withSamplesPerPixel = 2and noEXTRASAMPLES— fix: #6636 (merged)MultiLabelSTAPLEImageFilterInputPixelTypebut bounded bym_TotalLabelCount— auint8input using all 256 labels wraps the counter 255 → 0 andInitializePriorProbabilities()never terminates (reproduced as a live hang, not source analysis) — fix: #6607 (merged)MultiLabelSTAPLEImageFilter::InitializeConfusionMatrixArrayFromVotingLabelVotingImageFilternever receives the outer filter'sSetLabelForUndecidedPixels(), so the seed pass always uses automatic label selection regardless of the caller's setting — fix: #6607 (merged)GiplImageIO::ReadImageInformation/CanReadFilegzread/istream::readpairs, none of whose byte counts is checked — a truncated or non-GIPL file populates dimensions, spacing and origin from uninitialized locals (same shape as B54/B55, but no success flag is computed at all) — fix: #6628 (merged)TileImageFilter0in any non-lastLayoutaxis is never validated: with a non-zero last axis it indexes an emptystd::vectorand runs a ~2³² iteration loop; with a zero last axis (the constructor default) it divides by zero — fix: #6630 (merged)GDCMImageIO::Read(SINGLEBIT)GDCMImageIO::InternalReadImageInformation(ultrasound/hardcopy arm)(0028,0030)leaves the secondElementslot never written — axis-0 spacing is read from indeterminate heap memory; theassert(GetLength() == 2)guard is vacuous and cannot fire even in a Debug build — fix: #6639 (merged), replacing the reverted #6629LabelSetErodeImageFilter/LabelSetDilateImageFilterGenerateDataonly callsAllocateOutputs()and every write is gated onm_Scale[d] > 0; underUseImageSpacinga zero radius on the last axis skips erosion's only output-writing pass — the filter returns uninitialized memory — fix: #6631 (merged)ImageSeriesReader::GenerateOutputInformationITK_ImageOriginoverride array shorter than the output dimension is silently shrunk byArray::operator=, and the next loop still indexes toImageDimension - 1— unchecked out-of-bounds read. No in-tree ImageIO writes that key, so it is live only for an out-of-tree/customImageIO— fix: #6632 (merged)✅ Completed — PR-by-PR triage record (51 settled rows)
#6629 (B88/B89, GDCM) was reverted by #6638 — its single-bit decode assumed row-padded 1-bit Pixel Data, but DICOM PS3.5 packs contiguously — and is now replaced by #6639, which decodes contiguously (verified pixel-for-pixel against pydicom on a real 13×12×8 highdicom SEG file) and validates PixelSpacing multiplicity via
gdcm::VM::GetNumberOfElementsFromArray.Every @physwkim PR carries a single commit; each regression test was verified locally to fail on the pre-fix code and pass on the fix.
❌ Withdrawn — 4 items investigated and found not to be defects (table)
CannySegmentationLevelSetFunctionNiftiImageIOdim[1]— a 2-D vector image one row tall reads back as 1-D — not a defect; withdrawn: the write side collapses trailing degenerate dimensions too, so a 2-D 5x1 vector image and a 1-D 5 vector image produce byte-identicaldim[]; the rank is lost at write time and no read-side change can recover itPNGImageIO::WriteSliceheightcomes fromGetDimensions(1)alone, no axis beyond it is consulted — a 3-D image writes only its first slice, with no error — not a defect; withdrawn: writing the first slice of an image of 3 or more dimensions is the documented, tested contract (Modules/IO/PNG/test/itkPNGImageIOTest.cxx:152-160)HDF5ImageIO::ReadH5Dread's memory type, making the conversion the identity — aVoxelDatastoredSTD_I16BEis copied without a byte swap and read back byte-swapped, silently — not a defect; withdrawn:ReadImageInformation()requires an exactH5Tequalmatch against aNATIVE_*predtype and throws otherwise, and it is what populatesm_VoxelDataSet— soRead()is unreachable with a foreign-endian on-disk type (verified: writingSTD_I16BEthrows duringReadImageInformation())How these were found, and what was checked before filing
Each filter was reimplemented from the
.hxxsources 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 againstmainate46eb723a5: 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).
AI assistance
mainate46eb723a5, checking declared types and constructor defaults, re-deriving each consequence)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.