Skip to content

fix: Image() drops maxintval when dtype= is also given, silently truncating - #95

Merged
petercorke merged 5 commits into
mainfrom
fix/image-ctor-drops-maxintval-before-dtype-cast
Aug 16, 2026
Merged

fix: Image() drops maxintval when dtype= is also given, silently truncating#95
petercorke merged 5 commits into
mainfrom
fix/image-ctor-drops-maxintval-before-dtype-cast

Conversation

@petercorke

Copy link
Copy Markdown
Owner

Summary

  • Image(im, dtype=..., **other_kwargs) (eg. maxintval=, mono=, ...) applied the explicit dtype= via a raw .astype() cast before the other kwargs ever reached convert(). For an out-of-range integer downcast this truncates to the low byte instead of scaling.
  • Concretely: Image(uint16_12bit_data, dtype='uint8', maxintval=4095) kept only the bottom 8 bits of each pixel, producing a corrupted, sawtooth-wrapping image instead of a properly rescaled one (convert()'s int_image() already implements the correct maxintval-aware scaling -- the constructor just never let it see the original data).
  • Found via RVC3-python's visodom.py, which reads 12-bit .pgm frames with exactly this dtype=+maxintval= combination. The resulting images looked broken; idisp()/cv2.imshow's own 16-bit display conversion was independently verified correct (empirically, via on-screen pixel readback) -- the data was already wrong before it ever reached display.

Fix

When other convert()-relevant kwargs are given alongside an explicit dtype=, defer the dtype cast to convert() (which threads dtype+maxintval through int_image()/float_image() correctly) instead of astype()'ing early. The common plain-dtype-only path (no other kwargs) is untouched -- verified with a dedicated regression test.

Test plan

  • New TestImageConstructorMaxintval class in tests/test_dtype_resolution.py: downscale-with-maxintval matches int_image()'s own scaling, result is monotonic (not byte-truncated), maxintval=None default still uses the source dtype's max, dtype=+maxintval=+mono= combined (the real-world case), dtype= alone stays an unscaled cast (regression guard), maxintval= without dtype= stays inert (regression guard).
  • Full test suite: 999 passed, 15 skipped, no regressions.

🤖 Generated with Claude Code

…cating

Image.__init__ applied an explicit dtype= via a raw .astype() cast before
convert()-only kwargs (eg. maxintval) ever reached convert(). For an
out-of-range integer downcast this truncates to the low byte instead of
scaling: Image(uint16_data, dtype='uint8', maxintval=4095) on 12-bit data
kept only the bottom 8 bits of each 16-bit pixel, producing a corrupted,
wrapping-sawtooth image instead of a properly rescaled one.

Found via RVC3-python's visodom.py, which reads 12-bit .pgm frames with
exactly this dtype+maxintval combination; the resulting images looked
broken even though idisp()/cv2.imshow's own 16-bit display conversion was
independently verified correct -- the data was already wrong before display.

Fix: when other convert()-relevant kwargs are also given alongside an
explicit dtype=, defer the cast to convert() (which threads dtype+maxintval
through int_image()/float_image() correctly) instead of astype()'ing
early. The plain dtype-only path (no other kwargs) is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 10 high

Alerts:
⚠ 10 issues (≤ 0 issues of at least minor severity)

Results:
10 new issues

Category Results
Security 10 high

View in Codacy

🟢 Metrics 6 complexity · 0 duplication

Metric Results
Complexity 6
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@petercorke
petercorke merged commit 3636461 into main Aug 16, 2026
28 of 29 checks passed
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.

1 participant