huggingface / huggingface/datasets

`Image.cast_storage` warns about downcasting int64 to uint8 for storage that is already uint8

Open Beginner friendly
#8,511 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.