Skip to content

Avoid eager C-order copy in NibabelReader (fixes #8107)#8825

Open
aymuos15 wants to merge 3 commits intoProject-MONAI:devfrom
aymuos15:fix-8107-nibabel-load
Open

Avoid eager C-order copy in NibabelReader (fixes #8107)#8825
aymuos15 wants to merge 3 commits intoProject-MONAI:devfrom
aymuos15:fix-8107-nibabel-load

Conversation

@aymuos15
Copy link
Copy Markdown
Contributor

Summary

  • NibabelReader._get_array_data forced np.asanyarray(img.dataobj, order="C"), triggering a full dense memory reorder on every load on top of the file read/decompression step. This is the hot path reported in Very Slow Loading of NIfTI (.nii.gz) Files Compared to SimpleITK #8107.
  • Drop the forced C-order conversion and keep nibabel's native (F-order) layout. This aligns the CPU path with the existing GPU/cupy branch just above, which already returns F-order via .reshape(data_shape, order="F").
  • Downstream MONAI conversion paths (convert_to_tensor/convert_to_numpy in monai/utils/type_conversion.py, monai/data/image_writer.py, recon utils) already call ascontiguousarray where they actually need C-contiguous memory, so the reader does not need to pay that cost eagerly at load time.

Biggest wins are on uncompressed .nii, where nibabel's memmap view is returned lazily rather than being materialized by a forced reorder. Compressed .nii.gz still pays the decompression cost but skips the subsequent reorder pass, which matches the "twice as long" observation from @ericspod in the issue thread.

Compatibility note

The returned array's memory layout changes from C-contiguous to whatever nibabel provides (typically F-contiguous). Any external caller consuming reader.get_data(...)[0] directly via .tobytes() or a raw C-extension buffer without first calling ascontiguousarray would see a different byte order. All in-repo consumers already guard themselves.

Test plan

  • New regression test in tests/data/test_init_reader.py loads a small NIfTI through NibabelReader for both .nii and .nii.gz, asserts array equality, and asserts the returned data is not C-contiguous (i.e. no eager C copy).
  • pytest tests/data/test_init_reader.py passes locally.
  • runtests.sh-equivalent checks: ruff, black --skip-magic-trailing-comma --check, isort --check, pycln, pre-commit hooks — all clean on touched files.

Fixes #8107

Nibabel exposes NIfTI voxel buffers in their native Fortran layout, but
MONAI was forcing np.asanyarray(img.dataobj, order="C") in
NibabelReader._get_array_data(). For compressed .nii.gz inputs that adds
a full dense memory reorder on top of the file read/decompression step,
which is the hot path reported in issue Project-MONAI#8107.

Drop the forced C-order conversion and keep nibabel's native array layout
instead. Downstream MONAI conversion paths already handle contiguity when
they actually need it, so the reader does not need to pay that cost
eagerly at load time.

Add a regression test that loads a small NIfTI image through
NibabelReader and asserts the returned data is still correct while
preserving the native F-contiguous layout. This guards against
reintroducing the eager copy in the reader path.

Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk>
Exercise both .nii and .nii.gz inputs in the tiny layout regression test so the reader path stays covered without adding a benchmark or a heavier fixture.

Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

The change removes the explicit order="C" argument from np.asanyarray(img.dataobj) in the NibabelReader's _get_array_data method, allowing NumPy to use its default memory layout handling instead of forcing C-contiguous conversion. A new test was added to verify that loaded NIfTI data is not C-contiguous, confirming the optimization avoids unnecessary data copying.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: removing eager C-order conversion in NibabelReader and references the fixed issue.
Description check ✅ Passed Description includes comprehensive summary, rationale, compatibility notes, and test plan matching the template structure.
Linked Issues check ✅ Passed Code changes directly address issue #8107: removes forced C-order conversion causing slow NIfTI load times and adds regression tests.
Out of Scope Changes check ✅ Passed All changes are in-scope: one-line fix in image_reader.py and new regression test in test_init_reader.py directly related to issue #8107.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@aymuos15 aymuos15 changed the base branch from main to dev April 13, 2026 10:26
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
tests/data/test_init_reader.py (1)

84-84: Add a docstring to the new test method.

Line 84 introduces a new definition without a docstring; add a short Google-style docstring.

As per coding guidelines, "Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/data/test_init_reader.py` at line 84, Add a Google-style docstring to
the test function test_nibabel_reader_avoids_eager_c_order_copy describing what
the test verifies (that the nibabel reader avoids an eager C-order copy),
include a short "Args:" section only if the test takes parameters (omit
otherwise), and include a "Raises:" or "Returns:" section only if the test
explicitly raises or returns something (omit otherwise); place the docstring
immediately under the def line in triple quotes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/data/test_init_reader.py`:
- Line 84: Add a Google-style docstring to the test function
test_nibabel_reader_avoids_eager_c_order_copy describing what the test verifies
(that the nibabel reader avoids an eager C-order copy), include a short "Args:"
section only if the test takes parameters (omit otherwise), and include a
"Raises:" or "Returns:" section only if the test explicitly raises or returns
something (omit otherwise); place the docstring immediately under the def line
in triple quotes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: be4e5a1e-b10b-4097-a28d-5d3c9fda14f0

📥 Commits

Reviewing files that changed from the base of the PR and between 5a2d0a7 and f1873ec.

📒 Files selected for processing (2)
  • monai/data/image_reader.py
  • tests/data/test_init_reader.py

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.

Very Slow Loading of NIfTI (.nii.gz) Files Compared to SimpleITK

1 participant