huggingface / huggingface/datasets
`Image.cast_storage` warns about downcasting int64 to uint8 for storage that is already uint8
- Dominant language
- Python
- Stars
- 22k
- Forks
- 3.4k
- Avg merge
- 5d 7h
- Merged PRs (30d)
- 17
Description
### Describe the bug
Casting a nested-list column whose Arrow value type is already `uint8` to `Image()` emits a spurious warning:
```
UserWarning: Downcasting array dtype int64 to uint8 to be compatible with 'Pillow'
```
No downcast is actually needed. The Arrow storage type is `list>>`, so the dtype information is present and correct; the `int64` is introduced by `cast_storage` itself.
`Image.cast_storage`, list branch (`src/datasets/features/image.py#L264-L268`):
```python
elif pa.types.is_list(storage.type):
bytes_array = pa.array(
[encode_np_array(np.array(arr))["bytes"] if arr is not None else None for arr in storage.to_pylist()],
type=pa.binary(),
)
```
`storage.to_pylist()` materialises the values as plain Python `int`s, discarding the Arrow value type, and `np.array(arr)` on nested lists of Python `int`s then defaults to `int64`. `encode_np_array` (`src/datasets/features/image.py#L389-L397`) compares that against `|u1` and warns:
```python
# Multi-channel array case (only np.dtype("|u1") is allowed)
if array.shape[2:]:
...
dest_dtype = np.dtype("|u1")
if dtype != dest_dtype:
warnings.warn(f"Downcasting array dtype {dtype} to {dest_dtype} to be compatible with 'Pillow'")
```
So the values round-trip `uint8` → Python `int` → `int64` → `uint8`, and the user is warned about a downcast their data never needed. The encoded image is correct; the problem is the misleading message.
### Steps to reproduce the bug
```python
import warnings
import numpy as np
from datasets import Dataset
from datasets.features import Image
ds = Dataset.from_list(
[
{
"prompt": [{"role": "user", "content": "x"}],
"image": np.random.uniform(low=0.0, high=255.0, size=(64, 64, 3)).astype(np.uint8),
}
for _ in range(4)
]
)
print("inferred features:", ds.features)
print("arrow schema:", ds.data.schema)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
ds2 = ds.cast_column("image", Image())
_ = ds2[0]
print("WARNINGS:", [(x.category.__name__, str(x.message)) for x in w])
```
Output:
```
inferred features: {'prompt': List({'role': Value('string'), 'content': Value('string')}), 'image': List(List(List(Value('uint8'))))}
arrow schema: prompt: list>
child 0, item: struct
child 0, role: string
child 1, content: string
image: list>>
child 0, item: list>
child 0, item: list
child 0, item: uint8
-- schema metadata --
huggingface: '{"info": {"features": {"prompt": {"feature": {"role": {"dty' + 241
WARNINGS: [('UserWarning', "Downcasting array dtype int64 to uint8 to be compatible with 'Pillow'"), ('UserWarning', "Downcasting array dtype int64 to uint8 to be compatible with 'Pillow'"), ('UserWarning', "Downcasting array dtype int64 to uint8 to be compatible with 'Pillow'"), ('UserWarning', "Downcasting array dtype int64 to uint8 to be compatible with 'Pillow'")]
```
Note the contradiction between the schema (`uint8`) and the warning (`int64`). The `prompt` column is irrelevant to the issue; it is only there because this is the shape the reproduction was found in.
Both `5.0.1` and current `main` are affected — `cast_storage` is identical in the two.
### Expected behavior
No warning: the Arrow storage type already is a valid image array dtype, so nothing is being lost.
`cast_storage` knows the value type from `storage.type`, so it could build the array with it rather than letting `numpy` guess from Python `int`s — unwrapping the nested `list<...>` types down to the innermost value type and passing it as `dtype` to `np.array`. Genuine downcasts (e.g. list storage whose value type really is `int64`) would keep warning as they do today.
I'm happy to send a PR if this sounds right to you.
### Environment info
- `datasets` 5.0.2.dev0 (editable install of `main` @ 836b82e0544cabf6474b25ade131b4d21e570373); also reproduced with `datasets` 5.0.1 in CI
- Python 3.12.9, Linux
- `numpy` 2.4.4, `pyarrow` 24.0.0, `Pillow` 12.1.1
- CI occurrence with `datasets==5.0.1`, `numpy==2.5.2`, `pyarrow==25.0.1`, `pillow==12.3.0`, Python 3.12: https://github.com/huggingface/trl/actions/runs/32695065358/job/97335526967 (tracked in https://github.com/huggingface/trl/issues/6888)
Contributor guide
Research direction
Start in src/datasets/features/image.py at Image.cast_storage, especially the list branch around lines 264-268, then read encode_np_array around lines 389-397. Reproduce the nested uint8 list case from the issue and verify that casting to Image no longer emits a warning, while genuine int64-to-uint8 downcasts still do.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- numpy, python
- Domain
- computer-vision, data
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100