Skip to content

Fix 2D grayscale NumPy array prediction on color models - #24751

Merged
glenn-jocher merged 10 commits into
ultralytics:mainfrom
maxime2476:fix/grayscale-ndarray-predict
Jul 5, 2026
Merged

Fix 2D grayscale NumPy array prediction on color models#24751
glenn-jocher merged 10 commits into
ultralytics:mainfrom
maxime2476:fix/grayscale-ndarray-predict

Conversation

@maxime2476

@maxime2476 maxime2476 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Fix grayscale NumPy array prediction on color models

Closes #24750

What this does

Makes LoadPilAndNumpy._single_check expand a 2D grayscale numpy array (H, W) to the model's channel count (3 for a color model, 1 for a grayscale model), the same way the PIL branch just above it already does. Adds a regression test.

Why

Passing a 2D grayscale numpy array to a normal 3-channel model crashed inside PyTorch:

RuntimeError: Given groups=1, weight of size [16, 3, 3, 3], expected input[1, 1, 640, 640] to have 3 channels, but got 1 channels instead

The same image works when passed as a PIL image or a file path, because both get normalized to the model's channel count. Only the raw numpy array failed: the grayscale branch added a single channel (im[..., None]) and ignored flag, unlike the PIL branch that calls .convert(flag). This change just makes the numpy path behave like the PIL and file paths.

Before / after

import numpy as np
from ultralytics import YOLO

model = YOLO("yolo11n.pt")
model.predict(np.zeros((640, 640), dtype=np.uint8), verbose=False)

Before: RuntimeError (3 vs 1 channels).
After: runs normally and returns 1 result, same as the PIL image / file path.

Scope

  • Only the im.ndim == 2 (grayscale) branch changes.
  • The im.ndim == 3 numpy path is left as-is, so multispectral/multichannel models aren't affected (test_multichannel feeds np.zeros((32, 32, 10))).
  • Grayscale models (channels=1, flag="L") keep the old 1-channel behavior.

Tests

  • New test_predict_grayscale_ndarray fails before the change (RuntimeError) and passes after.
  • Ran locally on Windows 11 / Python 3.13.13 / torch 2.12.0+cpu: test_predict_img (all models including grayscale), test_predict_gray_and_4ch, test_grayscale, test_multichannel and the new test all pass (16 passed).
  • ruff check and ruff format --check are clean.

Not included

A 4-channel (H, W, 4) RGBA numpy array fails the same way, but the loader can't tell it apart from a real 4-channel multispectral input (both are ndim == 3), so I left it out to avoid breaking multichannel models. Can be a separate discussion.

🛠️ PR Summary

Made with ❤️ by Ultralytics Actions

🌟 Summary

Fixes grayscale NumPy array inference for color YOLO models by aligning 2D ndarray handling with existing PIL and file input behavior. 🖼️

📊 Key Changes

  • Updates _single_check() in ultralytics/data/loaders.py to convert 2D grayscale NumPy arrays to 3-channel BGR when used with color models.
  • Preserves 1-channel behavior for grayscale models by keeping flag == "L" inputs as single-channel arrays.
  • Clarifies the loader docstring to document the new NumPy grayscale expansion behavior.
  • Adds test_predict_grayscale_ndarray() in tests/test_python.py to verify that genuine 2D grayscale NumPy arrays run successfully through a default color model.

🎯 Purpose & Impact

  • Fixes an inconsistency where grayscale PIL/file inputs worked, but equivalent 2D NumPy arrays could fail or be handled differently.
  • Improves prediction reliability for users passing raw NumPy grayscale images directly into the Python API.
  • Reduces input-format surprises and brings ndarray preprocessing in line with expected model channel requirements.
  • Adds regression coverage to help prevent this grayscale input bug from reappearing. ✅

A 2D grayscale NumPy array (H, W) passed to a 3-channel model crashed deep
in conv2d ("expected input to have 3 channels, but got 1"), while the same
image as a PIL object or file path worked (both auto-expand to 3 channels).

LoadPilAndNumpy._single_check now expands a 2D grayscale array to match the
model channels (3 for color, 1 for grayscale), mirroring the PIL branch. The
ndim==3 NumPy path (N-channel multispectral) is untouched. Adds a regression
test feeding a 2D grayscale array to the default color model.
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

All Contributors have signed the CLA. ✅
Posted by the CLA Assistant Lite bot.

@UltralyticsAssistant UltralyticsAssistant added bug Something isn't working as intended in the official Ultralytics package. fixed Bug has been resolved python Pull requests that update python code labels Jun 9, 2026
@UltralyticsAssistant

Copy link
Copy Markdown
Member

👋 Hello @maxime2476, thank you for submitting a ultralytics/ultralytics 🚀 PR! This is an automated message to help streamline review, and an engineer will assist you shortly. Please review the checklist below to help ensure a smooth integration of your grayscale NumPy prediction fix 🧪

-✅ Define a Purpose: Clearly explain the purpose of your fix or feature in your PR description, and link to any relevant issues. Ensure your commit messages are clear, concise, and adhere to the project's conventions.
-✅ Synchronize with Source: Confirm your PR is synchronized with the ultralytics/ultralytics main branch. If it's behind, update it by clicking the 'Update branch' button or by running git pull and git merge main locally.
-✅ Ensure CI Checks Pass: Verify all Ultralytics Continuous Integration (CI) checks are passing. If any checks fail, please address the issues.
-✅ Update Documentation: Update the relevant documentation for any new or modified features.
-✅ Add Tests: If applicable, include or update tests to cover your changes, and confirm that all tests are passing.
-✅ Sign the CLA: Please ensure you have signed our Contributor License Agreement if this is your first Ultralytics PR by writing "I have read the CLA Document and I sign the CLA" in a new message.
-✅ Minimize Changes: Limit your changes to the minimum necessary for your bug fix or feature addition. "It is not daily increase but daily decrease, hack away the unessential. The closer to the source, the less wastage there is." — Bruce Lee

For more guidance, please refer to our Contributing Guide. Don't hesitate to leave a comment if you have any questions. Thank you for contributing to Ultralytics! 🚀

@UltralyticsAssistant UltralyticsAssistant left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔍 PR Review

Made with ❤️ by Ultralytics Actions

Good targeted fix and test coverage for the reported uint8 grayscale ndarray case. One issue to address before merging: the new cv2.cvtColor expansion introduces a dtype regression for non-OpenCV-compatible NumPy grayscale arrays, so a NumPy-based channel repeat would be safer here.

💬 Posted 1 inline comment

Comment thread ultralytics/data/loaders.py Outdated
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Address review: cv2.cvtColor(GRAY2BGR) rejects non-OpenCV dtypes
(float64, int64, bool), which would make some 2D arrays fail at the
expansion step. np.repeat produces 3 channels while keeping the input
dtype. Extends the regression test with a float64 case.
@maxime2476

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I sign the CLA

@glenn-jocher glenn-jocher changed the title Fix grayscale NumPy array prediction on color models Fix 2D grayscale NumPy array prediction on color models Jul 5, 2026
@glenn-jocher

Copy link
Copy Markdown
Member

Thanks @maxime2476! 🎉 Really clean fix — a 2D grayscale NumPy array now predicts correctly on color models (auto-expanded to 3 channels like PIL/file inputs), resolving the Given groups=1, ... expected input to have 3 channels, but got 1 crash. I reproduced the original bug against current main and confirmed your fix resolves it (verified for both uint8 and float64 2D arrays), tidied the inline comment to a single line, and added the version bump — all set. Appreciate the fix and the regression test to lock it in!

@glenn-jocher

glenn-jocher commented Jul 5, 2026

Copy link
Copy Markdown
Member

⚡ Actions Trigger

Made with ❤️ by Ultralytics Actions

GitHub Actions below triggered via workflow dispatch for this PR at 2026-07-05 18:14:07 UTC with @ultralytics/run-all command
(available commands are @ultralytics/run-all, @ultralytics/run-ci, and @ultralytics/run-docker):

@glenn-jocher
glenn-jocher merged commit c79f12a into ultralytics:main Jul 5, 2026
60 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working as intended in the official Ultralytics package. fixed Bug has been resolved python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

predict() on a 2D grayscale NumPy array crashes with a cryptic torch error (PIL/file grayscale works)

4 participants