Problem
The grayscale path uses image.squeeze() before passing an image to Pillow. This removes height and width when either equals one, instead of removing only the channel dimension.
Reproduction
import numpy as np
from diffusers.utils.pil_utils import numpy_to_pil
x = np.zeros((1, 7, 1), dtype=np.float32)
y = numpy_to_pil(x)[0]
print(y.size) # expected (7, 1), not (1, 7)
# A (1, 1, 1) image also loses both spatial dimensions.
Proposed scope
Use image.squeeze(-1) for the HWC grayscale branch. The same Image.fromarray(image.squeeze(), mode="L") pattern occurs once in src/diffusers/utils/pil_utils.py and twice in src/diffusers/image_processor.py; a fix should cover all three paths with common singleton-height/width regressions. Two DreamBooth examples, train_dreambooth_lora_flux2_img2img.py and train_dreambooth_lora_flux2_klein_img2img.py, also use unrestricted squeeze for CHW grayscale tensors; those need a channel-axis-specific review rather than blindly applying axis -1.
Validation and limits
Current pil_utils blob: 72d4704fa945f0cc4edfa8e6a15955cd75fd18f1. Current image_processor blob: 4f6f4bd52b9c2c6efd4a35fa50706a8642bc6c75. The isolated pil_utils helper harness ran 9 cases: 4 failed/5 passed before, 9 passed after. It checks dimensions and pixel values for singleton spatial axes, batches, and ordinary grayscale/RGB/RGBA. Python 3.12.14, NumPy 2.3.5, Pillow 12.3.0. This is not a full pipeline or repository suite; the repeated image_processor paths and examples have been inspected but not integration-tested.
Opening an issue first for maintainer agreement on scope. I checked related grayscale work, including #488; that added grayscale support, whereas this concerns preserving singleton spatial axes.
Problem
The grayscale path uses
image.squeeze()before passing an image to Pillow. This removes height and width when either equals one, instead of removing only the channel dimension.Reproduction
Proposed scope
Use
image.squeeze(-1)for the HWC grayscale branch. The sameImage.fromarray(image.squeeze(), mode="L")pattern occurs once insrc/diffusers/utils/pil_utils.pyand twice insrc/diffusers/image_processor.py; a fix should cover all three paths with common singleton-height/width regressions. Two DreamBooth examples,train_dreambooth_lora_flux2_img2img.pyandtrain_dreambooth_lora_flux2_klein_img2img.py, also use unrestricted squeeze for CHW grayscale tensors; those need a channel-axis-specific review rather than blindly applying axis -1.Validation and limits
Current pil_utils blob:
72d4704fa945f0cc4edfa8e6a15955cd75fd18f1. Current image_processor blob:4f6f4bd52b9c2c6efd4a35fa50706a8642bc6c75. The isolated pil_utils helper harness ran 9 cases: 4 failed/5 passed before, 9 passed after. It checks dimensions and pixel values for singleton spatial axes, batches, and ordinary grayscale/RGB/RGBA. Python 3.12.14, NumPy 2.3.5, Pillow 12.3.0. This is not a full pipeline or repository suite; the repeated image_processor paths and examples have been inspected but not integration-tested.Opening an issue first for maintainer agreement on scope. I checked related grayscale work, including #488; that added grayscale support, whereas this concerns preserving singleton spatial axes.