Skip to content

fix: Image.Read returns C-contiguous pixels; drawing into a strided image raises - #162

Merged
petercorke merged 3 commits into
mainfrom
fix/convert-contiguous-opencv5
Oct 22, 2026
Merged

petercorke merged 3 commits into
mainfrom
fix/convert-contiguous-opencv5

Conversation

@petercorke

Copy link
Copy Markdown
Owner

Summary

Every Image.draw_* method failed on an image from Image.Read with Layout of the output array img is incompatible with cv::Mat. convert() reordered the color planes with fancy indexing, image[:, :, [2, 1, 0]], and NumPy does not promise a C-contiguous result for that (it returns a planar layout, strides (W, 1, H*W)). OpenCV's in-place drawing functions (line, circle, putText, drawFrameAxes, ...) refuse such an array. The BGRA→BGR slice, reduce= and roi= also returned strided views.

What changes:

  • convert() now reorders planes with np.take and returns np.ascontiguousarray(image) at its single exit (no copy if already contiguous). Images from Image.Read, iread and iread_iter are always drawable, and their docstrings say so.
  • Image(array) is unchanged: it still keeps a reference to the array it is given. An image built from a transposed or reversed view is therefore not contiguous. Rather than silently copying (which would make aliasing depend on the array's layout), every method that draws into an image in place now raises a ValueError, before anything is drawn, telling the user to use Image(array, copy=True), which always gives a C-contiguous copy.
  • The check is _require_c_contiguous() in base/graphics.py, called by draw_box, draw_labelbox, draw_text, draw_point, draw_line and draw_circle (so all Image.draw_* and BaseFeature2D.draw are covered), and explicitly by BaseFeature2D.draw2 and the two draw() methods in ImageFiducials.py that call cv2.drawFrameAxes.
  • Docs: a .. warning:: on each writing method and a .. note:: in Image() explaining the rule. Also fixes a malformed .. note::Wildcard directive in iread_iter.

Behavior change to be aware of: drawing into a non-contiguous image used to fail inside OpenCV with an obscure error; it now raises ValueError with an explanation.

Related issue

Fixes #158

Verification

  • New tests/test_image_contiguous.py, 29 tests: contiguity on every convert path, drawing on Image.Read images, the error and pixels left untouched, copy=True fixing it, Image(a) still referencing a, feature draw/draw2. Run against unfixed main, 16 of the 20 tests from the first version fail with the putText/line layout errors.
  • pytest tests: 1012 passed, 96 skipped.
  • Reproduced end to end on OpenCV 5.0.0 with NumPy 2.5.4. OpenCV 4.14.0 (NumPy 2.2.6) rejects strided arrays in cv2.line/circle/putText in the same way when called directly, so this is not specific to OpenCV 5. I did not run MVTB end to end on OpenCV 4, and did not test older 4.x.
  • Not tested: Fiducial.draw and the board draw() (a fiducial is heavy to build in a test; the check is the same one-line call).

Checklist

  • PR title follows Conventional Commits
  • Tests pass locally (pytest)
  • Added tests for this change
  • New code is type-hinted with modern syntax
  • Docstrings updated

🤖 Generated with Claude Code

…mage raises

Every draw_* method failed on an image from Image.Read with "Layout of the
output array img is incompatible with cv::Mat". convert() reordered the color
planes with fancy indexing, image[:, :, [2, 1, 0]]; NumPy does not promise a
C-contiguous result for that and returns a planar layout (strides (W, 1, H*W)).
OpenCV's in-place drawing functions refuse such an array. The BGRA->BGR slice,
reduce= and roi= also returned strided views.

- convert(): reorder planes with np.take (a contiguous copy, which is what the
  old comment about Torch wanted), and guarantee a C-contiguous result at the
  single exit point (no copy if it already is). Images from Image.Read, iread
  and iread_iter are therefore always drawable; their docstrings now say so.
- Image(array) still keeps a reference to the array it is given (no copy, no
  forced layout), so an image made from a transposed or reversed view is not
  contiguous. Rather than silently copying, every method that draws into an
  image in place checks first and raises a ValueError, before anything is
  drawn, that says to use Image(array, copy=True), which always gives a
  C-contiguous copy. The check is _require_c_contiguous() in base/graphics.py,
  called by draw_box, draw_labelbox, draw_text, draw_point, draw_line and
  draw_circle (so Image.draw_* and BaseFeature2D.draw are covered), and
  explicitly by BaseFeature2D.draw2 and the two Fiducial/Board draw() methods
  that call cv2.drawFrameAxes.
- Docs: a .. warning:: on each of those methods and a .. note:: in Image().
  Also fixes a malformed ".. note::Wildcard" directive in iread_iter.

Adds tests/test_image_contiguous.py (29 tests; most fail without the fix).
Reproduced end to end on OpenCV 5.0.0 with NumPy 2.5.4. OpenCV 4.14.0 (NumPy
2.2.6) rejects strided arrays in cv2.line/circle/putText in the same way when
called directly, so this is not specific to OpenCV 5; older 4.x not tested.

Fixes #158.

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

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 15 complexity · 0 duplication

Metric Results
Complexity 15
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 dee5e1b into main Oct 22, 2026
39 checks passed
@petercorke
petercorke deleted the fix/convert-contiguous-opencv5 branch October 22, 2026 01:48
@github-actions github-actions Bot mentioned this pull request Oct 22, 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.

Image.draw_* fail on images from Image.Read: pixel data is not C-contiguous (cv2 line/circle/putText layout error)

1 participant