Repository: huggingface/datasets
Issue: ArrowInvalid: Could not convert <PIL.Image.Image image mode=RGB when adding image to Dataset

## Describe the bug

When adding a Pillow image to an existing Dataset on the hub, `add_item` fails due to the Pillow image not being automatically converted into the Image feature.

## Steps to reproduce the bug
```python
from datasets import load_dataset
from PIL import Image

dataset = load_dataset("hf-internal-testing/example-documents")

# load any random Pillow image
image = Image.open("/content/cord_example.png").convert("RGB")

new_image = {'image': image}
dataset['test'] = dataset['test'].add_item(new_image)
```

## Expected results
The image should be automatically casted to the Image feature when using `add_item`. For now, this can be fixed by using `encode_example`:

```
import datasets

feature = datasets.Image(decode=False)
new_image = {'image': feature.encode_example(image)}
dataset['test'] = dataset['test'].add_item(new_image)
```

## Actual results

```
ArrowInvalid: Could not convert <PIL.Image.Image image mode=RGB size=576x864 at 0x7F7CCC4589D0> with type Image: did not recognize Python value type when inferring an Arrow data type
```


Changed files:
src/datasets/arrow_dataset.py
tests/test_arrow_dataset.py

diff --git a/src/datasets/arrow_dataset.py b/src/datasets/arrow_dataset.py
index cd299c55f..caf414211 100644
--- a/src/datasets/arrow_dataset.py
+++ b/src/datasets/arrow_dataset.py
@@ -5339,14 +5339,15 @@ class Dataset(DatasetInfoMixin, IndexableMixin, TensorflowDatasetMixin):
         })
         ```
         """
-        column_table = InMemoryTable.from_pydict({name: column})
+        ts = OptimizedTypedSequence(column, type=None, col=name)
+        column_table = InMemoryTable.from_pydict({name: ts})
         _check_column_names(self._data.column_names + column_table.column_names)
         dataset = self.flatten_indices() if self._indices is not None else self
         # Concatenate tables horizontally
         table = concat_tables([dataset._data, column_table], axis=1)
         # Update features
         info = dataset.info.copy()
-        info.features.update(Features.from_arrow_schema(column_table.schema))
+        info.features.update(Features({name: ts.get_inferred_type()}))
         table = update_metadata_with_features(table, info.features)
         return Dataset(table, info=info, split=self.split, indices_table=None, fingerprint=new_fingerprint)
 
@@ -5582,9 +5583,12 @@ class Dataset(DatasetInfoMixin, IndexableMixin, TensorflowDatasetMixin):
         {'label': 0, 'text': 'this movie is the absolute worst thing I have ever seen'}
         ```
         """
-        item_table = InMemoryTable.from_pydict({k: [v] for k, v in item.items()})
+        item = {k: OptimizedTypedSequence([v], type=None, col=k) for k, v in item.items()}
+        item_table = InMemoryTable.from_pydict(item)
         # We don't call _check_if_features_can_be_aligned here so this cast is "unsafe"
-        dset_features, item_features = _align_features([self.features, Features.from_arrow_schema(item_table.schema)])
+        dset_features, item_features = _align_features(
+            [self.features, Features({k: v.get_inferred_type() for k, v in item.items()})]
+        )
         # Cast to align the schemas of the tables and concatenate the tables
         table = concat_tables(
             [
diff --git a/tests/test_arrow_dataset.py b/tests/test_arrow_dataset.py
index c13ea46e4..2222c337d 100644
--- a/tests/test_arrow_dataset.py
+++ b/tests/test_arrow_dataset.py
@@ -3214,6 +3214,18 @@ def test_dataset_add_column(column, expected_dtype, in_memory, transform, datase
     assert_arrow_metadata_are_synced_with_dataset_features(dataset)
 
 
+@require_pil
+def test_dataset_add_column_complex_features(image_file):
+    import PIL.Image
+
+    pil_image = PIL.Image.open(image_file)
+    dataset = Dataset.from_dict({"col_1": ["a", "b"]})
+    dataset = dataset.add_column("col_2", [pil_image, None])
+    assert dataset.data.shape == (2, 2)
+    assert dataset.features == Features({"col_1": Value("string"), "col_2": Image()})
+    assert dataset[:] == {"col_1": ["a", "b"], "col_2": [pil_image, None]}
+
+
 @pytest.mark.parametrize(
     "transform",
     [None, ("shuffle", (42,), {}), ("with_format", ("pandas",), {}), ("class_encode_column", ("col_2",), {})],
@@ -3280,6 +3292,18 @@ def test_dataset_add_item_introduce_feature_type():
     assert dataset[:] == {"col_1": [None, None, None, "a"]}
 
 
+@require_pil
+def test_dataset_add_item_complex_features(image_file):
+    import PIL.Image
+
+    pil_image = PIL.Image.open(image_file)
+    dataset = Dataset.from_dict({"col_1": [pil_image, None]})
+    dataset = dataset.add_item({"col_1": pil_image})
+    assert dataset.data.shape == (3, 1)
+    assert dataset.features == Features({"col_1": Image()})
+    assert dataset[:] == {"col_1": [pil_image, None, pil_image]}
+
+
 def test_dataset_filter_batched_indices():
     ds = Dataset.from_dict({"num": [0, 1, 2, 3]})
     ds = ds.filter(lambda num: num % 2 == 0, input_columns="num", batch_size=2)