Avoid eager C-order copy in NibabelReader (fixes #8107)#8825
Avoid eager C-order copy in NibabelReader (fixes #8107)#8825aymuos15 wants to merge 3 commits intoProject-MONAI:devfrom
Conversation
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>
📝 WalkthroughWalkthroughThe change removes the explicit Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 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
📒 Files selected for processing (2)
monai/data/image_reader.pytests/data/test_init_reader.py
Summary
NibabelReader._get_array_dataforcednp.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..reshape(data_shape, order="F").convert_to_tensor/convert_to_numpyinmonai/utils/type_conversion.py,monai/data/image_writer.py, recon utils) already callascontiguousarraywhere 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.gzstill 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 callingascontiguousarraywould see a different byte order. All in-repo consumers already guard themselves.Test plan
tests/data/test_init_reader.pyloads a small NIfTI throughNibabelReaderfor both.niiand.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.pypasses 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