Skip to content

io_image read_image bugfix & add feature read_image_16 (bit)#681

Merged
mratsim merged 3 commits intomratsim:masterfrom
voidpunk:io_image_fix_and_add_read_16_bit
Jan 2, 2026
Merged

io_image read_image bugfix & add feature read_image_16 (bit)#681
mratsim merged 3 commits intomratsim:masterfrom
voidpunk:io_image_fix_and_add_read_16_bit

Conversation

@voidpunk
Copy link
Copy Markdown
Contributor

@voidpunk voidpunk commented Apr 8, 2025

  • fixed a bug in the reshape of read_image from buffer proc, which swapped height and width
  • added 2 new read_image_16 proc to be able to read 16-bit images (useful in order to work with medical images)

Summary by CodeRabbit

  • New Features

    • 16-bit image reading: Users can now read 16-bit images directly from files and memory buffers, enabling support for extended dynamic range imaging workflows.
  • Bug Fixes

    • Enhanced automatic channel detection in 8-bit image reading, improving consistency and reliability in standard image format processing.

✏️ Tip: You can customize this high-level summary in your review settings.

Nil ☿ added 3 commits April 8, 2025 15:26
Fixed a bug in the read_image proc that swapped height and width
Replaced int with apposite stb_image enum & improved proc doc
Added read_image_16 proc for reading 16-bit images from a file or buffer
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 26, 2025

📝 Walkthrough

Walkthrough

Introduces 16-bit image reading capabilities through two new procedures (read_image_16 for filepath and buffer inputs) alongside a helper load16FromMemory. Adjusts 8-bit read path to use Default for channel autodetection. Standardizes reshape operations across both bitdepths to ensure consistent height × width × channels ordering before channel-first conversion.

Changes

Cohort / File(s) Summary
16-bit Image Reading
src/arraymancer/io/io_image.nim
Added read_image_16*(filepath: string): Tensor[uint16] and read_image_16*(buffer: seq[byte]): Tensor[uint16] procedures. Introduced load16FromMemory helper for buffer-based decoding. Both paths apply consistent reshape (height × width × channels) before hwc_to_chw conversion.
8-bit Path Updates
src/arraymancer/io/io_image.nim
Changed desired_channels parameter from explicit 0 to Default (autodetection). Applied matching reshape adjustment from (width, height, channels) to (height, width, channels) with subsequent channel-first conversion for consistency.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant FileIO as File/Buffer
    participant Decoder as Image Decoder
    participant Reshape as Reshape Ops
    participant Tensor as Tensor Output

    rect rgba(100, 150, 200, 0.2)
    Note over Caller,Tensor: 16-bit Path (New)
    Caller->>FileIO: read_image_16(input)
    FileIO->>Decoder: load16FromMemory(buffer)
    Decoder->>Reshape: raw_data [width, height, channels]
    Reshape->>Reshape: reshape to [height, width, channels]
    Reshape->>Reshape: hwc_to_chw() → [channels, height, width]
    Reshape->>Tensor: Tensor[uint16]
    end

    rect rgba(150, 150, 150, 0.15)
    Note over Caller,Tensor: 8-bit Path (Updated)
    Caller->>FileIO: read_image(input)
    FileIO->>Decoder: decode with Default channels
    Decoder->>Reshape: raw_data [width, height, channels]
    Reshape->>Reshape: reshape to [height, width, channels]
    Reshape->>Reshape: hwc_to_chw() → [channels, height, width]
    Reshape->>Tensor: Tensor[uint8]
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Sixteen bits of color so fine,
We read from file, from buffer divine,
Heights and widths in harmony aligned,
Channels first, a pattern refined—
Images now painted in deeper design!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: a bugfix for read_image and the addition of read_image_16 feature for 16-bit image support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/arraymancer/io/io_image.nim (1)

51-54: Consistent with filepath version—same verification needed.

The changes here mirror those in the filepath version: correct reshape ordering and the Default enum for channel autodetection. The same verification and error-handling considerations apply.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84af537 and 9722f42.

📒 Files selected for processing (1)
  • src/arraymancer/io/io_image.nim
🔇 Additional comments (1)
src/arraymancer/io/io_image.nim (1)

68-83: load16FromMemory is correctly integrated—note that it's marked as untested in the nim-stb_image wrapper.

The function exists in nim-stb_image and maps to stb_image's stbi_load_16_from_memory. The implementation mirrors the file-based version and correctly uses the wrapper's conversion of seq[byte] to pointer + length internally. However, the function is listed as an untested binding in the wrapper, so validate with known 16-bit test images before deployment.

Comment on lines +34 to 37
let desired_channels = Default # Channel autodetection

let raw_pixels = load(filepath, width, height, channels, desired_channels)
result = raw_pixels.toTensor.reshape(height, width, channels).hwc_to_chw
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.nim" | head -20

Length of output: 13


🏁 Script executed:

#!/bin/bash
# Find Default enum definition in stb_image related files
rg -n --type nim 'Default' -g '*/stb*' | head -20

Repository: arraymancer

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Search for load function signature to understand desired_channels parameter
rg -n --type nim 'proc load\*?\(' -g '*/stb*' -A5 | head -40

Repository: arraymancer

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Read the actual file being reviewed to see the context
head -90 src/arraymancer/io/io_image.nim | tail -70

Repository: arraymancer

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Check if there's error handling for nil returns elsewhere in the file
rg -n 'if.*nil\|isNil' src/arraymancer/io/io_image.nim -B2 -A2

Repository: arraymancer

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Look for the stb_image read module imports and usage
head -30 src/arraymancer/io/io_image.nim

Repository: arraymancer

Length of output: 166


🌐 Web query:

stb_image nim wrapper Default enum channel autodetection

💡 Result:

Short answer: The Nim stb_image wrapper uses the same enum as stb_image: Default corresponds to STBI_default (0) which tells the loader to autodetect the number of channels. Other enum values map to STBI_grey (1), STBI_grey_alpha (2), STBI_rgb (3) and STBI_rgb_alpha (4). Use Default (or 0) for autodetection, or pick a specific enum to force channel count. [1][2][3]

Sources:

  • stb_image-Nim README (usage & Default example). [1]
  • stb_image.h enum definitions (STBI_default = 0, STBI_grey = 1, …). [2]
  • StbImage docs showing channels: 0 = auto-detection. [3]

🌐 Web query:

stb_image library desired_channels parameter documentation

💡 Result:

The desired_channels (sometimes called req_comp) argument to stbi_load / stbi_load_from_memory (and the 16-bit/float variants) tells stb_image how many components per pixel you want in the returned buffer:

  • If desired_channels == 0: the loader returns the image with its native number of components and writes that native count into *channels_in_file.
  • If desired_channels != 0: stb_image will force the output to have exactly desired_channels components per pixel (padding or truncating channels as needed); *channels_in_file is still filled with the original number of components in the file. Typical values are 1 (gray), 2 (gray+alpha), 3 (RGB), 4 (RGBA). [1][2]

Sources: stb_image.h function comments and prototypes. [1] [2]

References
[1] stb_image.h header (function doc/comments).
[2] API reference / header listings for stbi_load variants.


The Default enum and reshape logic are correct, but add error handling for nil returns.

The change from 0 to Default for channel autodetection is semantically correct—stb_image's Default enum value equals STBI_default (0) and tells the loader to autodetect the native channel count. The reshape with (height, width, channels) is also correct, as stb_image returns pixels in row-major order matching this layout.

However, the load() function can return nil on failure. Add a nil-check before calling .toTensor() to handle cases where image loading fails.

🤖 Prompt for AI Agents
In src/arraymancer/io/io_image.nim around lines 34 to 37, the call to load(...)
can return nil on failure but the code immediately calls .toTensor() which will
crash; add a nil-check after raw_pixels is assigned and before calling
toTensor(), and if nil handle the error by returning a clear failure (e.g.,
raise an IO/ValueError or return nil/error result) that includes the filepath
and loader error context so callers can handle image-load failures gracefully.

Comment on lines +56 to +66
proc read_image_16*(filepath: string): Tensor[uint16] =
## Read a 16-bit image file and loads it into a Tensor[uint16] of shape
## Channel x Height x Width. Channel is 1 for greyscale, 3 for RGB.
##
## Supports JPEG, PNG, TGA, BMP, PSD, GIF, HDR, PIC, PNM
## See stb_image https://github.com/nothings/stb/blob/master/stb_image.h
var width, height, channels: int
let desired_channels = Default # Channel autodetection

let raw_pixels = load16(filepath, width, height, channels, desired_channels)
result = raw_pixels.toTensor.reshape(height, width, channels).hwc_to_chw
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for load16 function definition
echo "=== Searching for load16 function ==="
rg -n 'load16' --type nim

echo ""
echo "=== Searching for write functions related to 16-bit ==="
rg -n 'write.*16|proc write' --type nim | grep -i "image\|write"

echo ""
echo "=== Checking io_image.nim file structure ==="
wc -l src/arraymancer/io/io_image.nim

echo ""
echo "=== Reading the entire io_image.nim file to understand context ==="
head -100 src/arraymancer/io/io_image.nim

Repository: arraymancer

Length of output: 166


🌐 Web query:

stb_image load16 16-bit image support formats

💡 Result:

stb_image provides a 16‑bit-per‑channel load API (stbi_load_16 / stbi_load_16_from_memory). Supported formats that can yield 16‑bit/channel data are:

  • PNG — supports 16‑bit-per‑channel PNGs. [1][2]
  • PSD — PSD can be decoded with 8/16‑bit-per‑channel. [1][2]

Notes/limitations: JPEG via stb_image is treated as 8‑bit (no 12‑bit JPEG/arithmetic coding support), HDR is a float (f32) interface, and many formats (TGA, BMP, GIF, PIC, PNM) are primarily 8‑bit in stb_image. Use stbi_is_16_bit / stbi_is_16_bit_from_memory to detect 16‑bit images before calling stbi_load_16. [1][2][3]

Sources: [1] stb_image function list/header, [2] stb bindings/docs summary, [3] stb-related documentation.


🏁 Script executed:

#!/bin/bash
# Get file size first
wc -l src/arraymancer/io/io_image.nim

# Read first 100 lines to see imports and context
head -100 src/arraymancer/io/io_image.nim

# Search for load16 definition
rg -n 'proc load16' --type nim

# Search for write_image functions
rg -n 'proc write_image' --type nim

Repository: arraymancer

Length of output: 166


Update documentation to accurately reflect 16-bit format support.

The listed formats in the documentation are misleading. According to stb_image specifications, only PNG and PSD actually support 16-bit reading via stbi_load_16. The other listed formats—JPEG, TGA, BMP, GIF, PIC, PNM—are 8-bit only in stb_image, and HDR is float (f32), not uint16. Update the docstring to reflect only the formats that truly support 16-bit depth.

Also verify that load16() exists in the stb_image/read module and consider whether the absence of corresponding write_image_16 functions is intentional.

🤖 Prompt for AI Agents
In src/arraymancer/io/io_image.nim around lines 56 to 66, the docstring wrongly
claims many formats support 16-bit images; update it to state that only PNG and
PSD are supported for 16-bit reads via stbi_load_16 (HDR is float32 and the
others are 8-bit only). Change the listed supported formats accordingly, confirm
that load16() is present in the stb_image/read binding and adjust the comment to
reference stbi_load_16, and add a note or TODO about the lack of corresponding
write_image_16 functions if that is intentional so callers are aware.

@mratsim
Copy link
Copy Markdown
Owner

mratsim commented Jan 2, 2026

Thank you!

@mratsim mratsim merged commit adc2863 into mratsim:master Jan 2, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Jan 2, 2026
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.

2 participants