Repository navigation
Description
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)
| Bucket | Count |
|---|---|
| ✅ Bugs fixed and merged | 88 of 92 |
| ❌ Bugs withdrawn — investigated, not defects | 4 |
| Quirks (moved to #6684) | 16 of 118 dispositioned (all merged) |
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)
| # | Site | One-line symptom |
|---|---|---|
| ✅ B52 | niftilib nifti_read_buffer (vendored) |
maps every non-finite float/double/complex component to 0 on read wherever isfinite is 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.5 reads back 1.5 0 0 0 2.5) — fix: #6683 (merged), via ISC/nifti_clib f24a6078 + UpdateFromUpstream.sh |
| ✅ B91 | vnl vnl_matlab_readhdr::read_hdr (vendored) |
The no-swap switch lists 7 of the 8 legal type_t encodings — 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 decision |
| ✅ 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: #6681 (merged) |
| ✅ B64 | MetaIO ElementDataFile = LIST (vendored) |
out-of-range file dimension (LIST -1 indexes m_DimSize[-1]/m_SubQuantity[-1]; LIST NDims reads nothing) and a short slice list both report success — fix: Kitware/MetaIO#141 (merged), vendored via #6692 |
| ✅ B65 | MetaIO M_ReadElements (vendored) |
a compressed image whose header omits CompressedDataSize measures from the start of the file, so a LOCAL image inflates its own ASCII header — fix: Kitware/MetaIO#142 (merged), vendored via #6692 |
| ✅ B66 | MetaIO MET_PerformUncompression (vendored) |
unconditionally return true; also its dest_pos accounting skipped the final inflate call, so no byte-count check was possible — both fixed, and both call sites now honour the result — fix: Kitware/MetaIO#142 (merged), vendored via #6692 |
Quirks — 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)
| # | Site | One-line symptom |
|---|---|---|
| ✅ 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) |
✅ 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.
| Ledger item | PR | Author | State |
|---|---|---|---|
| B10 (MultiLabelSTAPLE tie increment) | #6579 | @physwkim | Merged |
| B24 (FastMarchingUpwindGradient AllTargets duplicates) | #6658 | @hjmjohnson | Merged |
| B67–B69, Q84 (vendored NrrdIO via ISC teem fork snapshot) | #6674 | @hjmjohnson | Merged |
| B52 (niftilib non-finite pixel zeroing) | #6683 | @hjmjohnson | Merged — fixed in ISC/nifti_clib (f24a6078, both nifti1 and nifti2), imported via UpdateFromUpstream.sh; opt-in fix_floats keeps the old default for other consumers; 49/49 NIfTI tests |
| B63, Q82 (NrrdImageIO, non-vendored) | #6681 | @hjmjohnson | Merged — exact-match reserved-key dispatch; ITK-side regression GTests for B67–B69; Q82 verbatim-space warning |
B64, B65, B66 (vendored MetaIO snapshot 8c41a1d9) |
#6692 | @hjmjohnson | Merged — upstream Kitware/MetaIO#141 (B64) and #142 (B65/B66), each with a regression test vendored in (testMeta13ImageList, testMeta14ImageCompressed); 83/83 Meta tests |
| B91 (vnl matlab reader native 1010 code) | #6682 | @hjmjohnson | Merged — endian-agnostic fail-before/pass-after GTests; vendored-only fix, no upstream vxl submission planned |
| 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, closed by #6588 |
| B32 follow-up (lattice centering, output-direction API, subsampling guards) | #6588 | @hjmjohnson | Merged — closed #6582 |
| B2, B3, B4, B9, B11, B12, B20, B23, B27, B29 (NaN / UB-cast guards) | #6589 | @hjmjohnson | Merged |
| B40–B45 (LabelMap / label-statistics correctness) | #6590 | @hjmjohnson | Merged |
| B1 (ProjectionImageFilter collapsed-axis origin) | #6594 | @physwkim | Merged — baselines uploaded |
| B13 (ThresholdMaximumConnectedComponents bisection seed) | #6595 | @physwkim | Merged |
| B14 (ScalarImageKmeans non-contiguous label interval) | #6596 | @physwkim | Merged |
| B15 (LabelVoting / MultiLabelSTAPLE undecided-label wrap) | #6597 | @physwkim | Merged |
| B16 (MaskedFFTNormalizedCorrelation integer squaring) | #6598 | @physwkim | Merged |
| B17 (IterativeDeconvolution ignores OutputRegionMode) | #6599 | @physwkim | Merged |
| B6 (GridImageSource ignores the image origin) | #6600 | @physwkim | Merged |
| B7 (MinMaxCurvatureFlow 3-D polar angle) | #6603 | @physwkim | Merged |
| B8 (CurvatureFlowFunction derivative scale vs radius) | #6604 | @physwkim | Merged |
| B21 (LevelSetNeighborhoodExtractor zero contour) | #6601 | @physwkim | Merged |
| B22 (StructureTensor single-axis smoothing) | #6602 | @physwkim | Merged — the 16 AnisotropicDiffusionLBR baselines were regenerated and uploaded |
| B28 (UniformRandomSpatialNeighborSubsampler infinite Search) | #6605 | @physwkim | Merged |
| B30 (RegionBasedLevelSetFunction reinitialization term) | #6606 | @physwkim | Merged |
| B36, B37, B38 (FastMarchingImageFilterBase boundary / topology / zero speed) | #6613 | @physwkim | Merged |
| B39 (colormap NumericTraits::min as a most-negative seed) | #6615 | @physwkim | Merged |
| B46 (NormalizedCorrelationImageFilter unguarded divisions) | #6612 | @physwkim | Merged |
| B47 (TransformIOBase CorrectTransformPrecisionType) | #6608 | @physwkim | Merged |
| B53–B56 (GiplImageIO write truncation, short-read detection, vector header) | #6611 | @physwkim | Merged |
| B57–B60, B62 (VTKImageIO short read, sscanf, attribute dispatch, ASCII precision, CanReadFile) | #6610 | @physwkim | Merged |
| B61 (ImageIOBase::ReadBuffer ASCII stream state) | #6609 | @physwkim | Merged |
| B70, B72 (PNGImageIO truncates the file before validating) | #6614 | @physwkim | Merged |
| B71 (PNGImageIO writes only the first slice) | — | @physwkim | Withdrawn — not a defect, the contract is documented and tested |
| B84, B85 (MultiLabelSTAPLE counter wrap, undecided label not propagated) | #6607 | @physwkim | Merged |
| B48–B50 (NIfTI multi-component rescale over-read) | #6625 | @physwkim | Merged |
| B73, B74 (HDF5 null metadata cast, unchecked geometry lengths) | #6626 | @physwkim | Merged |
| B76, B77 (JPEG centimetre density tag, YCCK conversion) | #6627 | @physwkim | Merged |
| B86 (GIPL header read in one checked call) | #6628 | @physwkim | Merged |
| B88, B89 (GDCM single-bit packing, single-valued PixelSpacing) | #6629 (reverted by #6638), replaced by #6639 | @physwkim, @hjmjohnson | Merged — #6639 decodes 1-bit Pixel Data contiguously per PS3.5 (including the trailing partial byte, which the pre-#6629 code dropped) and counts PixelSpacing values with GDCM's own multiplicity counter, closing the trailing-backslash gap. Verified against pydicom on a real multi-frame SEG file. |
| B87 (TileImageFilter zero Layout axis) | #6630 | @physwkim | Merged |
| B90 (LabelSet erode/dilate uninitialized output) | #6631 | @physwkim | Merged |
| B92 (ImageSeriesReader origin-override out-of-bounds) | #6632 | @physwkim | Merged |
| B26 (PatchBasedDenoising POISSON step collapse) | #6633 | @physwkim | Merged |
| B78–B83 (TIFF volume packing, per-page geometry, SampleFormat, MINISWHITE, grey+alpha, 2-component write) | #6636 | @physwkim | Merged |
| B39 family (NumericTraits::min as a most-negative seed, 5 further sites) | #6634 | @physwkim | Merged |
| B39 family (entropy threshold calculators) | #6635 | @physwkim | Merged — expected constants and baselines updated |
| B51 (NIfTI 2-D vector rank), B71 (PNG first slice), B75 (HDF5 memory type) | — | @physwkim | Withdrawn — not defects, see the bug table |
| B5 (CannySegmentationLevelSetFunction speed image) | — | @physwkim | Withdrawn — not a defect, see comment below |
| B33 (ConfidenceConnected empty re-estimation) | #6578 | @physwkim | Merged |
| Precursors | #6569 (JHMI marginals + derivative) | @physwkim | Merged |
| #6572 (ScaleLogarithmicTransform Jacobian) | @physwkim | Merged |
❌ Withdrawn — 4 items investigated and found not to be defects (table)
| # | Site | One-line symptom |
|---|---|---|
| ❌ B5 | CannySegmentationLevelSetFunction |
Propagation weight 0 → advection generated from a never-allocated speed image — not a defect; withdrawn (UpdateOutputInformation resets the empty requested region) |
| ❌ 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 — 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-identical dim[]; the rank is lost at write time and no read-side change can recover it |
| ❌ 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 — 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) |
| ❌ 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 — not a defect; withdrawn: ReadImageInformation() requires an exact H5Tequal match against a NATIVE_* predtype and throws otherwise, and it is what populates m_VoxelDataSet — so Read() is unreachable with a foreign-endian on-disk type (verified: writing STD_I16BE throws during ReadImageInformation()) |
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).
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
mainate46eb723a5, 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.
Activity
github-actions commentedon Jul 10, 2026
Thank you for contributing an issue! 🙏
Welcome to the ITK community! 🤗👋☀️
We are glad you are here and appreciate your contribution. Please keep in mind our community participation guidelines. 📜
Also, please check existing open issues and consider discussion on the ITK Discourse. 📖
This is an automatic message. Allow for time for the ITK community to be able to read the issue and comment on it.
physwkim commentedon Jul 10, 2026
physwkim commentedon Jul 10, 2026
BUG: Reset smallestError per pixel in IterativeInverseDisplacementField
BUG: Propagate input direction to subsampler in InverseDisplacementField
BUG: Propagate input direction to subsampler in InverseDisplacementField
BUG: Guard empty re-estimation pass in ConfidenceConnected filters
BUG: Skip voting-undecided pixels in MultiLabelSTAPLE seeding
BUG: Propagate rank to the last axis in FastApproximateRank
BUG: Sort the full pixel buffer in AttributeMorphologyBase
physwkim commentedon Jul 10, 2026
physwkim commentedon Jul 10, 2026
physwkim commentedon Jul 10, 2026
physwkim commentedon Jul 10, 2026
234 remaining items
BUG: Include region in WarpImageFilter same-information fast path
BUG: Compute displacement exponential scale without undefined shift
BUG: Dispatch NRRD reserved metadata keys by exact match
BUG: Dispatch NRRD reserved metadata keys by exact match
BUG: Recognize native big-endian single-precision MATLAB headers
hjmjohnson commentedon Jul 23, 2026
Status update: every identified bug is now either merged or in an open PR — nothing is unclaimed. The last five unclaimed vendored-ThirdParty items (B52, B64–B66, B91) have all been claimed.
Open PRs (2):
NrrdImageIOreserved-key dispatch (ready for review)8c41a1d9(draft; two-parent merge, 83/83 Meta tests pass)The 5 formerly-unclaimed vendored ThirdParty items — now all resolved
LISTdimension/slice validationMET_PerformUncompressionresult honouredB64–B66 required upstream Kitware/MetaIO fixes (#141, #142, both merged 2026-07-22) followed by a re-vendor of MetaIO into ITK; that re-vendor is #6692.
Merged since the previous ledger update
UpdateFromUpstream.sh(infrastructure enabling the B63 path)Draft #6669 was closed and split into the three reviewable PRs above.
Remaining work to fully close this issue
After both land, all bugs catalogued in this issue are resolved. Any remaining open items are quirk (Q) dispositions tracked separately.
Merge pull request InsightSoftwareConsortium#6692 from hjmjohnson/upd…
BUG: Add alternate baselines for corrected projection-filter origin
ENH: Add MinMaxCurvatureFlow alternate baselines for ITK 3D threshold…
ENH: Add MinMaxCurvatureFlow alternate baselines for ITK 3D threshold…
BUG: Recognize native big-endian single-precision MATLAB headers
BUG: Fix possible undefined behavior in Transform::ApplyToImageMetadata