Loss of cast `Image` feature on certain dataset method
State: closed | Author: davanstrien (MEMBER)
Labels: name: bug

## Describe the bug

When an a column is cast to an `Image` feature, the cast type appears to be lost during certain operations. I first noticed this when using the `push_to_hub` method on a dataset that contained urls pointing to images which had been cast to an `image`. This also happens when using select on a dataset which has had a column cast to an `Image`.

I suspect this might be related to https://github.com/huggingface/datasets/pull/3556 but I don't believe that pull request fixes this issue.  

## Steps to reproduce the bug

An example of casting a url to an image followed by using the `select` method:

```python
from datasets import Dataset
from datasets import features
url = "https://cf.ltkcdn.net/cats/images/std-lg/246866-1200x816-grey-white-kitten.webp"
data_dict = {"url": [url]*2}
dataset = Dataset.from_dict(data_dict)
dataset =  dataset.cast_column('url',features.Image())
sample = dataset.select([1])
```

[example notebook](https://gist.github.com/davanstrien/06e53f4383c28ae77ce1b30d0eaf0d70#file-potential_casting_bug-ipynb)

## Expected results
The cast value is maintained when further methods are applied to the dataset. 

## Actual results
```python
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-12-47f393bc2d0d> in <module>()
----> 1 sample = dataset.select([1])

4 frames

/usr/local/lib/python3.7/dist-packages/datasets/arrow_dataset.py in wrapper(*args, **kwargs)
    487         }
    488         # apply actual function
--> 489         out: Union["Dataset", "DatasetDict"] = func(self, *args, **kwargs)
    490         datasets: List["Dataset"] = list(out.values()) if isinstance(out, dict) else [out]
    491         # re-apply format to the output

/usr/local/lib/python3.7/dist-packages/datasets/fingerprint.py in wrapper(*args, **kwargs)
    409             # Call actual function
    410 
--> 411             out = func(self, *args, **kwargs)
    412 
    413             # Update fingerprint of in-place transforms + update in-place history of transforms

/usr/local/lib/python3.7/dist-packages/datasets/arrow_dataset.py in select(self, indices, keep_in_memory, indices_cache_file_name, writer_batch_size, new_fingerprint)
   2772             )
   2773         else:
-> 2774             return self._new_dataset_with_indices(indices_buffer=buf_writer.getvalue(), fingerprint=new_fingerprint)
   2775 
   2776     @transmit_format

/usr/local/lib/python3.7/dist-packages/datasets/arrow_dataset.py in _new_dataset_with_indices(self, indices_cache_file_name, indices_buffer, fingerprint)
   2688             split=self.split,
   2689             indices_table=indices_table,
-> 2690             fingerprint=fingerprint,
   2691         )
   2692 

/usr/local/lib/python3.7/dist-packages/datasets/arrow_dataset.py in __init__(self, arrow_table, info, split, indices_table, fingerprint)
    664         if self.info.features.type != inferred_features.type:
    665             raise ValueError(
--> 666                 f"External features info don't match the dataset:\nGot\n{self.info.features}\nwith type\n{self.info.features.type}\n\nbut expected something like\n{inferred_features}\nwith type\n{inferred_features.type}"
    667             )
    668 

ValueError: External features info don't match the dataset:
Got
{'url': Image(id=None)}
with type
struct<url: extension<arrow.py_extension_type<ImageExtensionType>>>

but expected something like
{'url': Value(dtype='string', id=None)}
with type
struct<url: string>
```

## Environment info
<!-- You can run the command `datasets-cli env` and copy-and-paste its output below. -->
- `datasets` version: 1.17.1.dev0
- Platform: Linux-5.4.144+-x86_64-with-Ubuntu-18.04-bionic
- Python version: 3.7.12
- PyArrow version: 3.0.0

Comments: Hi! Thanks for reporting! The issue with `cast_column` should be fixed by #3575 and after we merge that PR I'll start working on the `push_to_hub` support for the `Image`/`Audio` feature.

> Hi! Thanks for reporting! The issue with `cast_column` should be fixed by #3575 and after we merge that PR I'll start working on the `push_to_hub` support for the `Image`/`Audio` feature.

Thanks, I'll keep an eye out for #3575 getting merged. I managed to use `push_to_hub` sucesfully with images when they were loaded via `map` - something like `ds.map(lambda example: {"img": load_image_function(example['fname']})`, this only pushed  the images to the hub if the `load_image_function` return a PIL Image without the filename attribute though. I guess this might often be the prefered behaviour though. 


Hi ! We merged the PR and did a release of `datasets` that includes the changes. Can you try updating `datasets` and try again ?

> Hi ! We merged the PR and did a release of `datasets` that includes the changes. Can you try updating `datasets` and try again ?

Thanks for checking. There is no longer an error when calling `select` but it appears the cast value isn't preserved. Before `select`

```python
dataset.features
{'url': Image(id=None)}
```

after select:
```
{'url': Value(dtype='string', id=None)}
```

Updated Colab example [here](https://colab.research.google.com/gist/davanstrien/4e88f55a3675c279b5c2f64299ae5c6f/potential_casting_bug.ipynb) 

Hmmm, if I re-run your google colab I'm getting the right type at the end:
```
sample.features
# {'url': Image(id=None)}
```

Appolgies - I've just run again and also got this output. I have also sucesfully used the `push_to_hub` method. I think this is fixed now so will close this issue. 

Fixed in #3575