Skip to content

Fix uint32/int32 TIFF decoding (missing dtype mapping) - #6510

Open
jantonguirao wants to merge 8 commits into
NVIDIA:mainfrom
jantonguirao:fix/tiff-uint32-decode
Open

jantonguirao wants to merge 8 commits into
NVIDIA:mainfrom
jantonguirao:fix/tiff-uint32-decode

Conversation

@jantonguirao

@jantonguirao jantonguirao commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Category:

Bug fix (non-breaking change which fixes an issue)

Description:

to_dali_dtype() in dali/operators/imgcodec/util/nvimagecodec_types.h was missing case
branches for NVIMGCODEC_SAMPLE_DATA_TYPE_UINT32 and NVIMGCODEC_SAMPLE_DATA_TYPE_INT32. Any
source image whose decoded sample type is 32-bit integer (e.g. a uint32 TIFF) hit the
default: return DALI_NO_TYPE; branch, which the decoder then turned into a hard runtime error:
Invalid sample_type: 8198.

While adding a proper regression test with a real signed 32-bit source, a second, related bug
surfaced: dali/operators/imgcodec/image_decoder.h's dynamic-range-scaling logic silently
corrupted pixel values for any signed integer source using the full width of its type (the
common case) — e.g. a genuinely signed int32 TIFF decoded to exactly half its correct value. This
is fixed too (see commit "Fix dynamic-range scaling for full-width signed integer sources").

The rest of the imgcodec conversion pipeline (dali/operators/imgcodec/util/convert.h's
IMGCODEC_TYPES, used by both the CPU and GPU conversion paths) already supports uint32_t/
int32_t samples, and DALI_UINT32/DALI_INT32 already exist as DALI dtypes — so both were
narrow, contained bugs rather than missing features.

This fixes the decoding half of NVIDIA/DALI#1246
for 32-bit TIFF. Note: the cpu (libtiff) backend still explicitly rejects any signed-format
TIFF outright (SAMPLEFORMAT_INT), regardless of bit depth — that's a separate limitation inside
nvImageCodec's LibTIFF extension itself, out of scope here. Only the mixed (GPU/nvtiff)
backend can currently decode a signed-integer TIFF at all.

Additional information:

Affected modules and functionalities:

  • dali/operators/imgcodec/util/nvimagecodec_types.h — to_dali_dtype().
  • dali/operators/imgcodec/image_decoder.h — dynamic-range-scaling precision handling for
    signed dtypes.
  • dali/test/python/decoder/test_imgcodec.py — new test_tiff_uint32 and test_tiff_int32
    test cases.
  • DALI_EXTRA_VERSION — bumped to pick up new uint32/int32 TIFF fixtures and their reference
    .npy files (NVIDIA/DALI_extra#136).

Key points relevant for the review:

The core dtype-mapping change is a pure switch-statement extension; no other code paths change.
The dynamic-range-scaling fix is narrowly scoped to signed dtypes (IsSigned(orig_dtype)), so it
cannot affect any existing unsigned-type behavior — verified by regression-testing 7 other TIFF
variants + JPEG/PNG/BMP on both backends after the change (see Tests below).

FIXME before merge: DALI_EXTRA_VERSION currently points at a commit on my fork
(jantonguirao/DALI_extra), not NVIDIA/DALI_extra — it's only there so CI can see the new
fixtures ahead of the DALI_extra PR being merged. This must be updated to a commit on
NVIDIA/DALI_extra main once
NVIDIA/DALI_extra#136 merges, or CI checkout of
DALI_extra will fail for anyone without that fork remote configured.

Tests:

  • Existing tests apply
  • New tests added
    • Python tests
    • GTests
    • Benchmark
    • Other
  • N/A

test_tiff_uint32 and test_tiff_int32 in dali/test/python/decoder/test_imgcodec.py:

  • test_tiff_uint32: decodes a uint32 TIFF with dtype=types.UINT32 on both cpu and mixed,
    and compares the full-precision output against an independent reference decode (tifffile,
    checked into DALI_extra as a .npy).
  • test_tiff_int32: decodes a signed int32 TIFF. On cpu, asserts the current (documented)
    rejection from the LibTIFF extension. On mixed, compares the full-precision output against
    the same kind of independent reference — this is what caught the dynamic-range scaling bug.

Also manually regression-checked 7 other TIFF variants (default uint8, uint16, grayscale, tiled,
BigTIFF, palette, JPEG-compressed-in-TIFF) plus JPEG/PNG/BMP on both cpu/mixed — all
unchanged after the scaling fix.

Checklist

Documentation

  • Existing documentation applies
  • Documentation updated
    • Docstring
    • Doxygen
    • RST
    • Jupyter
    • Other
  • N/A

DALI team only

Requirements

  • Implements new requirements
  • Affects existing requirements
  • N/A

REQ IDs: N/A

JIRA TASK: N/A

@copy-pr-bot

copy-pr-bot Bot commented Sep 29, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Copilot AI balanced review requested due to automatic review settings September 29, 2026 08:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The decoding regression needs an automated uint32 TIFF test covering CPU and mixed backends.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds missing 32-bit integer sample-type mappings to enable uint32 TIFF decoding.

Changes:

  • Maps nvImageCodec UINT32 and INT32 samples to corresponding DALI types.
  • Completes the existing imgcodec conversion pipeline’s type support.
File Description
dali/​operators/​imgcodec/​util/​nvimagecodec_types.h Adds UINT32 and INT32 dtype mappings.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread dali/operators/imgcodec/util/nvimagecodec_types.h
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Fixes signed integer TIFF decoding and color conversion.

No new or outstanding review finding blocks this PR, but the fixture pin still needs the upstream update noted by the author before merge.

Summary

The PR adds 32-bit integer TIFF dtype mappings, corrects signed-sample scaling and CPU color conversion, and adds reference-based decoder and conversion tests. The latest change shares TIFF fixture filtering with the fused decoder tests.

Reviews (7) · Last reviewed commit: "Exclude signed TIFF fixture from test_im..."

Comment thread dali/test/python/decoder/test_imgcodec.py Outdated
Comment thread dali/test/python/decoder/test_imgcodec.py
Comment thread DALI_EXTRA_VERSION
Comment thread dali/test/python/decoder/test_imgcodec.py
Comment thread dali/test/python/decoder/test_imgcodec.py
Comment thread dali/operators/imgcodec/util/convert.h
to_dali_dtype() was missing cases for NVIMGCODEC_SAMPLE_DATA_TYPE_UINT32
and NVIMGCODEC_SAMPLE_DATA_TYPE_INT32, causing the image decoder to throw
"Invalid sample_type" for any 32-bit integer source (e.g. uint32 TIFF),
even though the rest of the imgcodec conversion pipeline already supports
these types via IMGCODEC_TYPES.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Bumps DALI_EXTRA_VERSION to pick up the new
db/single/tiff/0/cat-300572_640_uint32.tiff fixture (NVIDIA/DALI_extra#136).

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
PositiveBits(dtype) subtracts one bit for signed DALI dtypes (reserving
the sign bit), but the source `precision` reported by nvImageCodec (e.g.
TIFF BitsPerSample) is the raw storage width, which already includes the
sign bit for a signed sample format. Comparing them directly caused
NeedDynamicRangeScaling() to spuriously trigger for any signed source
using the full width of its type (the common case), silently corrupting
pixel values (observed: exactly half the correct value for a signed
int32 TIFF, since (2^31-1)/(2^32-1) ~= 0.5).

Convert `precision` to the same "positive bits" basis as PositiveBits()
before comparing, for signed sources.

Also strengthens the uint32 TIFF decoder test to compare full-precision
output against an independent reference (tifffile-derived .npy) on both
cpu and mixed backends instead of only the top byte on cpu, and adds an
analogous int32 test -- including an explicit assertion that the cpu
(libtiff) backend still rejects signed TIFF samples, a known, separate
limitation.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Addresses the repo's test-edge-cases convention: covers a genuinely
malformed/truncated 32-bit TIFF (valid header, no pixel data), distinct
from the existing cpu signed-format-rejection case which tests a known
backend limitation rather than invalid input.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
ConvertCPU ran the color-conversion matrix directly on raw sample
values, but the coefficients/biases are calibrated for a channel in
[0, max_value<T>()], as for a typical unsigned pixel type. A signed
source's raw value spans [min_value<In>(), max_value<In>()], so a
negative sample contributed a large negative term to the matrix
instead of a small positive one, producing wildly wrong, unclamped
output (only reachable now that mixed_decoder maps int32/uint32 dtypes
and the CPU nvImageCodec extension can decode signed TIFF).

ConvertGPU already avoids this by normalizing to the output dtype
first and only then running the color conversion on same-typed data.
Do the same on the CPU path, reusing the already-correct signed-aware
scaling in ConvertSatNorm instead of teaching the color-math formulas
about signedness.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Addresses Greptile review comment: the new signed int32 TIFF tests
exercise ConvertCPU only through the CPU TIFF decoder, which rejects
all signed sample formats outright, so the color-conversion fix in
53afab5 was never actually exercised by a CPU-reachable path. Add a
ConvertCPU-level GTest instead, calling the conversion directly with a
synthetic signed source and comparing against an independently
computed expected value. Confirmed it fails without the 53afab5 fix
and passes with it.

Also bump convert.h's copyright year for this substantially modified
file.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
…r tests

test_tiff_int32 assumed the CPU (libtiff) backend now decodes signed
int32 TIFF, but the CPU nvImageCodec extension rejects all signed TIFF
sample formats outright ("Unsupported sample format: 2"), regardless
of bit depth - confirmed by actually building and running this on GPU
hardware. Restore the cpu-rejection assertion; only the mixed/GPU
backend is expected to decode it, per the PR description.

The same fixture also broke test_image_decoder, test_FastDCT, and
test_image_decoder_consistency: those tests glob/list every file under
db/single/tiff/ and decode it via the generic fn.decoders.image() path
without requesting dtype=INT32, which fails on both cpu and mixed for
this file. Exclude it from those directory-wide file lists; it has its
own dedicated coverage in test_tiff_int32.

Also bump this substantially modified file's copyright year.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
Comment thread dali/test/python/decoder/test_imgcodec.py
Addresses Greptile review comment: create_decoder_slice_pipeline,
create_decoder_crop_pipeline and create_decoder_random_crop_pipeline
independently list every file in the TIFF directory via
fn.readers.file(file_root=...), same as decoder_pipe, so they hit the
same known CPU signed-TIFF rejection for a reason unrelated to fused
decoding. Factor the exclude_names filtering used by decoder_pipe into
a shared _reader_file_kwargs helper and apply it here too.

Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
@jantonguirao

Copy link
Copy Markdown
Collaborator Author

!build

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [70490760]: BUILD STARTED

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [70490760]: BUILD FAILED

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants