Repository: Lightning-Universe/lightning-flash

Issue:
`TypeError` with Plant Pathology
## 🐛 Bug

### To Reproduce

https://www.kaggle.com/c/plant-pathology-2021-fgvc8

See below:

#### Code sample


```python
import flash
from flash.image import ImageClassificationData
import pandas as pd
```


```python
data = pd.read_csv("train.csv")
```


```python
data["filepath"] = "train_images/" + data["image"]
```


```python
dm = ImageClassificationData.from_files(train_files=data["filepath"], train_targets=data.labels)
```


```python
batch = next(iter(dm.train_dataloader()))
```


    ---------------------------------------------------------------------------

    TypeError                                 Traceback (most recent call last)

    /tmp/ipykernel_326/1146354640.py in <module>
    ----> 1 batch = next(iter(dm.train_dataloader()))
    

    ~/conda/lib/python3.8/site-packages/torch/utils/data/dataloader.py in __next__(self)
        519             if self._sampler_iter is None:
        520                 self._reset()
    --> 521             data = self._next_data()
        522             self._num_yielded += 1
        523             if self._dataset_kind == _DatasetKind.Iterable and \


    ~/conda/lib/python3.8/site-packages/torch/utils/data/dataloader.py in _next_data(self)
       1201             else:
       1202                 del self._task_info[idx]
    -> 1203                 return self._process_data(data)
       1204 
       1205     def _try_put_index(self):


    ~/conda/lib/python3.8/site-packages/torch/utils/data/dataloader.py in _process_data(self, data)
       1227         self._try_put_index()
       1228         if isinstance(data, ExceptionWrapper):
    -> 1229             data.reraise()
       1230         return data
       1231 


    ~/conda/lib/python3.8/site-packages/torch/_utils.py in reraise(self)
        423             # have message field
        424             raise self.exc_type(message=msg)
    --> 425         raise self.exc_type(msg)
        426 
        427 


    TypeError: Caught TypeError in DataLoader worker process 0.
    Original Traceback (most recent call last):
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/utils/data/_utils/worker.py", line 287, in _worker_loop
        data = fetcher.fetch(index)
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/utils/data/_utils/fetch.py", line 47, in fetch
        return self.collate_fn(data)
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1051, in _call_impl
        return forward_call(*input, **kwargs)
      File "/home/jovyan/conda/lib/python3.8/site-packages/flash/core/data/batch.py", line 221, in forward
        sample = self.per_sample_transform(sample)
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1051, in _call_impl
        return forward_call(*input, **kwargs)
      File "/home/jovyan/conda/lib/python3.8/site-packages/flash/core/data/batch.py", line 76, in forward
        sample = self.to_tensor_transform(sample)
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1051, in _call_impl
        return forward_call(*input, **kwargs)
      File "/home/jovyan/conda/lib/python3.8/site-packages/flash/core/data/utils.py", line 178, in forward
        return self.func(*args, **kwargs)
      File "/home/jovyan/conda/lib/python3.8/site-packages/flash/core/data/process.py", line 351, in to_tensor_transform
        return self.current_transform(sample)
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1051, in _call_impl
        return forward_call(*input, **kwargs)
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/nn/modules/container.py", line 139, in forward
        input = module(input)
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1051, in _call_impl
        return forward_call(*input, **kwargs)
      File "/home/jovyan/conda/lib/python3.8/site-packages/flash/core/data/transforms.py", line 47, in forward
        result[keys[0]] = super().forward(inputs[0])
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/nn/modules/container.py", line 139, in forward
        input = module(input)
      File "/home/jovyan/conda/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1051, in _call_impl
        return forward_call(*input, **kwargs)
      File "/home/jovyan/conda/lib/python3.8/site-packages/flash/core/data/utils.py", line 178, in forward
        return self.func(*args, **kwargs)
    TypeError: new(): invalid data type 'str'

### Expected behavior

TypeError

### Environment

 - PyTorch Version (e.g., 1.0):
 - OS (e.g., Linux):
 - How you installed PyTorch (`conda`, `pip`, source):
 - Build command you used (if compiling from source):
 - Python version:
 - CUDA/cuDNN version:
 - GPU models and configuration:
 - Any other relevant information:

### Additional context

<!-- Add any other context about the problem here. -->



Discussion:
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Resolving patch:
diff --git a/flash/core/data/utilities/classification.py b/flash/core/data/utilities/classification.py
--- a/flash/core/data/utilities/classification.py
+++ b/flash/core/data/utilities/classification.py
@@ -36,12 +36,17 @@ def _as_list(x: Union[List, torch.Tensor, np.ndarray]) -> List:
     return x
 
 
+def _strip(x: str) -> str:
+    return x.strip(", ")
+
+
 class TargetMode(Enum):
     """The ``TargetMode`` Enum describes the different supported formats for targets in Flash."""
 
     MULTI_TOKEN = auto()
     MULTI_NUMERIC = auto()
     MUTLI_COMMA_DELIMITED = auto()
+    MUTLI_SPACE_DELIMITED = auto()
     MULTI_BINARY = auto()
 
     SINGLE_TOKEN = auto()
@@ -67,9 +72,12 @@ def from_target(cls, target: Any) -> "TargetMode":
             target: A target that is one of: a single target, a list of targets, a comma delimited string.
         """
         if isinstance(target, str):
-            # TODO: This could be a dangerous assumption if people happen to have a label that contains a comma
+            target = _strip(target)
+            # TODO: This could be a dangerous assumption if people happen to have a label that contains a comma or space
             if "," in target:
                 return TargetMode.MUTLI_COMMA_DELIMITED
+            elif " " in target:
+                return TargetMode.MUTLI_SPACE_DELIMITED
             else:
                 return TargetMode.SINGLE_TOKEN
         elif _is_list_like(target):
@@ -88,6 +96,7 @@ def multi_label(self) -> bool:
         return any(
             [
                 self is TargetMode.MUTLI_COMMA_DELIMITED,
+                self is TargetMode.MUTLI_SPACE_DELIMITED,
                 self is TargetMode.MULTI_NUMERIC,
                 self is TargetMode.MULTI_TOKEN,
                 self is TargetMode.MULTI_BINARY,
@@ -116,7 +125,7 @@ def binary(self) -> bool:
 _RESOLUTION_MAPPING = {
     TargetMode.MULTI_BINARY: [TargetMode.MULTI_NUMERIC],
     TargetMode.SINGLE_BINARY: [TargetMode.MULTI_BINARY, TargetMode.MULTI_NUMERIC],
-    TargetMode.SINGLE_TOKEN: [TargetMode.MUTLI_COMMA_DELIMITED],
+    TargetMode.SINGLE_TOKEN: [TargetMode.MUTLI_COMMA_DELIMITED, TargetMode.MUTLI_SPACE_DELIMITED],
     TargetMode.SINGLE_NUMERIC: [TargetMode.MULTI_NUMERIC],
 }
 
@@ -179,7 +188,7 @@ def __init__(self, labels: List[Any]):
         self.label_to_idx = {label: idx for idx, label in enumerate(labels)}
 
     def format(self, target: Any) -> Any:
-        return self.label_to_idx[(target[0] if not isinstance(target, str) else target).strip()]
+        return self.label_to_idx[_strip(target[0] if not isinstance(target, str) else target)]
 
 
 class MultiLabelTargetFormatter(SingleLabelTargetFormatter):
@@ -201,6 +210,11 @@ def format(self, target: Any) -> Any:
         return super().format(target.split(","))
 
 
+class SpaceDelimitedTargetFormatter(MultiLabelTargetFormatter):
+    def format(self, target: Any) -> Any:
+        return super().format(target.split(" "))
+
+
 class MultiNumericTargetFormatter(TargetFormatter):
     def __init__(self, num_classes: int):
         self.num_classes = num_classes
@@ -245,6 +259,8 @@ def get_target_formatter(
         return SingleLabelTargetFormatter(labels)
     elif target_mode is TargetMode.MUTLI_COMMA_DELIMITED:
         return CommaDelimitedTargetFormatter(labels)
+    elif target_mode is TargetMode.MUTLI_SPACE_DELIMITED:
+        return SpaceDelimitedTargetFormatter(labels)
     return MultiLabelTargetFormatter(labels)
 
 
@@ -289,13 +305,16 @@ def get_target_details(targets: List[Any], target_mode: TargetMode) -> Tuple[Opt
         if target_mode is TargetMode.MUTLI_COMMA_DELIMITED:
             for target in targets:
                 tokens.extend(target.split(","))
+        elif target_mode is TargetMode.MUTLI_SPACE_DELIMITED:
+            for target in targets:
+                tokens.extend(target.split(" "))
         elif target_mode is TargetMode.MULTI_TOKEN:
             for target in targets:
                 tokens.extend(target)
         else:
             tokens = targets
 
-        tokens = [token.strip() for token in tokens]
+        tokens = [_strip(token) for token in tokens]
         labels = list(sorted_alphanumeric(set(tokens)))
         num_classes = len(labels)
     return labels, num_classes
diff --git a/flash/image/detection/data.py b/flash/image/detection/data.py
--- a/flash/image/detection/data.py
+++ b/flash/image/detection/data.py
@@ -151,9 +151,10 @@ def from_voc(
         transform_kwargs: Optional[Dict] = None,
         **data_module_kwargs: Any,
     ) -> "ObjectDetectionData":
-        """Creates a :class:`~flash.image.detection.data.ObjectDetectionData` object from the given data folders
-        and annotation files in the `PASCAL VOC (Visual Obect Challenge)
-        <http://host.robots.ox.ac.uk/pascal/VOC/>`_ XML format.
+        """.. _PASCAL: http://host.robots.ox.ac.uk/pascal/VOC/
+
+        Creates a :class:`~flash.image.detection.data.ObjectDetectionData` object from the given data folders
+        and annotation files in the `PASCAL VOC (Visual Object Challenge) XML format <PASCAL>`_.
 
         Args:
             train_folder: The folder containing the train data.


Test patch:
diff --git a/tests/core/data/utilities/test_classification.py b/tests/core/data/utilities/test_classification.py
--- a/tests/core/data/utilities/test_classification.py
+++ b/tests/core/data/utilities/test_classification.py
@@ -50,6 +50,13 @@
         ["blue", "green", "red"],
         3,
     ),
+    Case(
+        ["blue green", "green red", "red blue"],
+        [[1, 1, 0], [0, 1, 1], [1, 0, 1]],
+        TargetMode.MUTLI_SPACE_DELIMITED,
+        ["blue", "green", "red"],
+        3,
+    ),
     # Ambiguous
     Case([[0], [1, 2], [2, 0]], [[1, 0, 0], [0, 1, 1], [1, 0, 1]], TargetMode.MULTI_NUMERIC, None, 3),
     Case([[1, 0, 0], [0, 1, 1], [1, 0, 1]], [[1, 0, 0], [0, 1, 1], [1, 0, 1]], TargetMode.MULTI_BINARY, None, 3),
@@ -67,6 +74,13 @@
         ["blue", "green", "red"],
         3,
     ),
+    Case(
+        ["blue", "green red", "red blue"],
+        [[1, 0, 0], [0, 1, 1], [1, 0, 1]],
+        TargetMode.MUTLI_SPACE_DELIMITED,
+        ["blue", "green", "red"],
+        3,
+    ),
     # Special cases
     Case(["blue ", " green", "red"], [0, 1, 2], TargetMode.SINGLE_TOKEN, ["blue", "green", "red"], 3),
     Case(
@@ -76,6 +90,13 @@
         ["blue", "green", "red"],
         3,
     ),
+    Case(
+        ["blue", "green ,red", "red ,blue"],
+        [[1, 0, 0], [0, 1, 1], [1, 0, 1]],
+        TargetMode.MUTLI_COMMA_DELIMITED,
+        ["blue", "green", "red"],
+        3,
+    ),
     Case(
         [f"class_{i}" for i in range(10000)],
         list(range(10000)),
@@ -115,16 +136,16 @@ def test_speed(case):
     else:
         targets = case.target * repeats
 
-    start = time.time()
+    start = time.perf_counter()
     target_mode = get_target_mode(targets)
     labels, num_classes = get_target_details(targets, target_mode)
     formatter = get_target_formatter(target_mode, labels, num_classes)
-    end = time.time()
+    end = time.perf_counter()
 
     assert (end - start) / len(targets) < 1e-5  # 0.01ms per target
 
-    start = time.time()
+    start = time.perf_counter()
     _ = [formatter(t) for t in targets]
-    end = time.time()
+    end = time.perf_counter()
 
     assert (end - start) / len(targets) < 1e-5  # 0.01ms per target