| ✅ 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) |
| ✅ B52 |
niftilib nifti_read_buffer (vendored) |
non-finite float/double/complex components zeroed on read (isfinite macro path) — NaN/±Inf pixels silently destroyed — fix: #6683 (merged), via ISC/nifti_clib f24a6078; opt-in fix_floats keeps the old default for other consumers |
| ✅ 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) |
| ✅ B63 |
NrrdImageIO::Write metadata dispatch |
NRRD_-prefixed keys matched by strncmp of the candidate's length — NRRD_space directions swallowed by the space handler; unmatched NRRD_ keys silently dropped — fix: #6681 (merged) |
| ✅ B64 |
MetaIO ElementDataFile = LIST (vendored) |
out-of-range file dimension and a short slice list both report success — fix: Kitware/MetaIO#141, vendored via #6692 (merged) |
| ✅ B65 |
MetaIO M_ReadElements (vendored) |
compressed image without CompressedDataSize measures from the start of the file — a LOCAL image inflates its own ASCII header — fix: Kitware/MetaIO#142, vendored via #6692 (merged) |
| ✅ B66 |
MetaIO MET_PerformUncompression (vendored) |
unconditionally return true; dest_pos accounting skipped the final inflate — both fixed, call sites honour the result — fix: Kitware/MetaIO#142, vendored via #6692 (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) |
| ✅ B91 |
vnl vnl_matlab_readhdr::read_hdr (vendored) |
the no-swap switch lists 7 of the 8 legal type_t encodings — native big-endian single-precision 1010 byte-swapped as though foreign — fix: #6682 (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-26 by @hjmjohnson)
The bug ledger is complete: all 92 items are resolved — 88 fixed and merged, 4 withdrawn after investigation. The final six landed the week of 2026-07-21: #6681 (B63/Q82), #6682 (B91), #6683 (B52), and the MetaIO vendor update #6692 (B64/B65/B66 via Kitware/MetaIO#141 and #142). Remaining lower-priority quirks are tracked in #6684.
✅ Outstanding work — none
Final six items (now merged)
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 (merged), 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 (merged), 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 (merged)ElementDataFile = LIST(vendored)LIST -1indexesm_DimSize[-1]/m_SubQuantity[-1];LIST NDimsreads nothing) and a short slice list both report success — fix: Kitware/MetaIO#141 (merged), vendored via #6692M_ReadElements(vendored)CompressedDataSizemeasures from the start of the file, so aLOCALimage inflates its own ASCII header — fix: Kitware/MetaIO#142 (merged), vendored via #6692MET_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#142 (merged), vendored via #6692Quirks — 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 dispositioned and merged (Q25–Q38 via #6671, Q82 via #6681, Q84 via #6674); the remaining 102 are untriaged and tracked there.
✅ Completed — 88 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::Readnifti_read_buffer(vendored)isfinitemacro path) — NaN/±Inf pixels silently destroyed — fix: #6683 (merged), via ISC/nifti_clibf24a6078; opt-infix_floatskeeps the old default for other consumersGiplImageIO::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)NrrdImageIO::Writemetadata dispatchNRRD_-prefixed keys matched bystrncmpof the candidate's length —NRRD_space directionsswallowed by thespacehandler; unmatchedNRRD_keys silently dropped — fix: #6681 (merged)ElementDataFile = LIST(vendored)M_ReadElements(vendored)CompressedDataSizemeasures from the start of the file — aLOCALimage inflates its own ASCII header — fix: Kitware/MetaIO#142, vendored via #6692 (merged)MET_PerformUncompression(vendored)return true;dest_posaccounting skipped the finalinflate— both fixed, call sites honour the result — fix: Kitware/MetaIO#142, vendored via #6692 (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)vnl_matlab_readhdr::read_hdr(vendored)switchlists 7 of the 8 legaltype_tencodings — native big-endian single-precision1010byte-swapped as though foreign — fix: #6682 (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 (55 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.
f24a6078, both nifti1 and nifti2), imported viaUpdateFromUpstream.sh; opt-infix_floatskeeps the old default for other consumers; 49/49 NIfTI tests8c41a1d9)testMeta13ImageList,testMeta14ImageCompressed); 83/83 Meta tests❌ 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.