Repository: voxel51/fiftyone

Issue:
[BUG] IndexError when use compute_patch_embeddings() with polylines label type, fiftyone 0.14.2
### System information

-   **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: colab
-   **FiftyOne installed from (pip or source)**: pip
-   **FiftyOne version (run `fiftyone --version`)**: 0.14.2
-   **Python version**: 3.7.12

### Commands to reproduce



```
pip3 install fiftyone==0.14.2
```

### Describe the problem

after updating fiftyone to 0.14.2 i have an issue with `compute_patch_embeddings()` when label type is polylines, i got none values and error `IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed`, but when downgrade fiftyone to 0.14.0 it works fine 

### Code to reproduce issue
```

import fiftyone as fo
import fiftyone.brain as fob
import fiftyone.zoo as foz
from fiftyone import ViewField as F

dataset = foz.load_zoo_dataset("quickstart")
points = [(0.7416666666666667, 0.28055555555555556), (0.6984375, 0.325), (0.6619791666666667, 0.2759259259259259), (0.7057291666666666, 0.2324074074074074)]
for s in dataset:
  polygon =[]
  polyline = fo.Polyline(label = 'fill',
                        points = [points],
                        closed=True,
                        filled=True,
                        )
  polygon.append(polyline)
  s['test_emb'] = fo.Polylines(polylines=polygon)
  s.save()
  embs = dataset.compute_patch_embeddings(model, patches_field="test_emb", skip_failures=False)
  print(embs)

```
### Other info / logs

IndexError                                Traceback (most recent call last)
<ipython-input-21-e7b8385cc759> in <module>()
     16   s['test_emb'] = fo.Polylines(polylines=polygon)
     17   s.save()
---> 18   embs = dataset.compute_patch_embeddings(model, patches_field="test_emb", skip_failures=False)
     19   print(embs)

7 frames
/usr/local/lib/python3.7/dist-packages/torch/_utils.py in reraise(self)
    432             # instantiate since we don't know how to
    433             raise RuntimeError(msg) from None
--> 434         raise exception
    435 
    436 

IndexError: Caught IndexError in DataLoader worker process 0.
Original Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/torch/utils/data/_utils/worker.py", line 287, in _worker_loop
    data = fetcher.fetch(index)
  File "/usr/local/lib/python3.7/dist-packages/torch/utils/data/_utils/fetch.py", line 49, in fetch
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/usr/local/lib/python3.7/dist-packages/torch/utils/data/_utils/fetch.py", line 49, in <listcomp>
    data = [self.dataset[idx] for idx in possibly_batched_index]
  File "/usr/local/lib/python3.7/dist-packages/fiftyone/utils/torch.py", line 1317, in __getitem__
    raise e
  File "/usr/local/lib/python3.7/dist-packages/fiftyone/utils/torch.py", line 1313, in __getitem__
    patches = self._patches[first:last, :]
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed



Discussion:

Resolving patch:
diff --git a/fiftyone/utils/torch.py b/fiftyone/utils/torch.py
--- a/fiftyone/utils/torch.py
+++ b/fiftyone/utils/torch.py
@@ -1052,6 +1052,11 @@ def _parse_inputs(
         sample_ids=None,
         include_ids=False,
     ):
+        if image_paths is None and samples is None:
+            raise ValueError(
+                "Either `image_paths` or `samples` must be provided"
+            )
+
         if image_paths is None:
             image_paths = samples.values("filepath")
 
@@ -1168,6 +1173,11 @@ def _parse_inputs(
         sample_ids=None,
         include_ids=False,
     ):
+        if image_paths is None and samples is None:
+            raise ValueError(
+                "Either `image_paths` or `samples` must be provided"
+            )
+
         if image_paths is None:
             image_paths = samples.values("filepath")
 
@@ -1197,7 +1207,7 @@ class TorchImagePatchesDataset(Dataset):
     """A :class:`torch:torch.utils.data.Dataset` of image patch tensors
     extracted from a list of images.
 
-    Provide either ``image_paths`` and ``detections`` or ``samples`` and
+    Provide either ``image_paths`` and ``patches`` or ``samples`` and
     ``patches_field`` in order to use this dataset.
 
     Instances of this dataset emit image patches for each sample, or
@@ -1217,9 +1227,13 @@ class TorchImagePatchesDataset(Dataset):
 
     Args:
         image_paths (None): an iterable of image paths
-        detections (None): a list of :class:`fiftyone.core.labels.Detections`
-            instances specifying the image patch(es) to extract from each
-            image. Elements can be ``None`` if an image has no patches
+        patches (None): a list of labels of type
+            :class:`fiftyone.core.labels.Detection`,
+            :class:`fiftyone.core.labels.Detections`,
+            :class:`fiftyone.core.labels.Polyline`, or
+            :class:`fiftyone.core.labels.Polylines` specifying the image
+            patch(es) to extract from each image. Elements can be ``None`` if
+            an image has no patches
         samples (None): a :class:`fiftyone.core.collections.SampleCollection`
             from which to extract patches
         patches_field (None): the name of the field defining the image patches
@@ -1261,7 +1275,7 @@ class TorchImagePatchesDataset(Dataset):
     def __init__(
         self,
         image_paths=None,
-        detections=None,
+        patches=None,
         samples=None,
         patches_field=None,
         handle_missing="skip",
@@ -1277,7 +1291,7 @@ def __init__(
     ):
         image_paths, sample_ids, patch_edges, patches = self._parse_inputs(
             image_paths=image_paths,
-            detections=detections,
+            patches=patches,
             samples=samples,
             patches_field=patches_field,
             handle_missing=handle_missing,
@@ -1362,13 +1376,18 @@ def _extract_patches(self, image_path, patches):
     def _parse_inputs(
         self,
         image_paths=None,
-        detections=None,
+        patches=None,
         samples=None,
         patches_field=None,
         handle_missing="skip",
         sample_ids=None,
         include_ids=False,
     ):
+        if image_paths is None and samples is None:
+            raise ValueError(
+                "Either `image_paths` or `samples` must be provided"
+            )
+
         if image_paths is None:
             image_paths = samples.values("filepath")
 
@@ -1380,22 +1399,56 @@ def _parse_inputs(
         if sample_ids is not None:
             sample_ids = _to_bytes_array(sample_ids)
 
-        if detections is not None:
+        if patches is not None:
             bboxes = []
-            for dets in detections:
-                if dets is not None:
-                    boxes = [d.bounding_box for d in dets.detections]
-                else:
+
+            for p in patches:
+                if p is None:
                     boxes = None
+                elif isinstance(p, fol.Detection):
+                    boxes = [p.bounding_box]
+                elif isinstance(p, fol.Detections):
+                    boxes = [d.bounding_box for d in p.detections]
+                elif isinstance(p, fol.Polyline):
+                    boxes = [p.to_detection().bounding_box]
+                elif isinstance(p, fol.Polylines):
+                    boxes = [
+                        _p.to_detection().bounding_box for _p in p.polylines
+                    ]
+                else:
+                    raise ValueError("Unsupported patches type %s" % type(p))
 
                 bboxes.append(boxes)
+        elif patches_field is not None:
+            label_type = samples._get_label_field_type(patches_field)
+
+            if issubclass(label_type, (fol.Detection, fol.Detections)):
+                _, bbox_path = samples._get_label_field_path(
+                    patches_field, "bounding_box"
+                )
+                bboxes = samples.values(bbox_path)
+            elif issubclass(label_type, (fol.Polyline, fol.Polylines)):
+                _, points_path = samples._get_label_field_path(
+                    patches_field, "points"
+                )
+                points = samples.values(points_path)
+
+                if issubclass(label_type, fol.Polyline):
+                    bboxes = [_polyline_to_bbox(p) for p in points]
+                else:
+                    bboxes = [_polylines_to_bboxes(p) for p in points]
+            else:
+                raise ValueError(
+                    "Patches field '%s' has unsupported type %s"
+                    % (patches_field, label_type)
+                )
+
+            if not issubclass(label_type, fol._LABEL_LIST_FIELDS):
+                bboxes = [[b] if b is not None else None for b in bboxes]
         else:
-            label_type, bbox_path = samples._get_label_field_path(
-                patches_field, "bounding_box"
+            raise ValueError(
+                "Either `patches` or `patches_field` must be provided"
             )
-            bboxes = samples.values(bbox_path)
-            if not issubclass(label_type, fol._LABEL_LIST_FIELDS):
-                bboxes = [[b] if b is not None else b for b in bboxes]
 
         num_patches = 0
         patch_edges = [0]
@@ -1425,6 +1478,25 @@ def _to_eta_bbox(bounding_box):
     return etag.BoundingBox.from_coords(tlx, tly, tlx + w, tly + h)
 
 
+def _polylines_to_bboxes(points):
+    if points is None:
+        return None
+
+    return [_polyline_to_bbox(p) for p in points]
+
+
+def _polyline_to_bbox(points):
+    if points is None:
+        return None
+
+    x, y = zip(*list(itertools.chain(*points)))
+    xtl = min(x)
+    ytl = min(y)
+    xbr = max(x)
+    ybr = max(y)
+    return [xtl, ytl, (xbr - xtl), (ybr - ytl)]
+
+
 def from_image_classification_dir_tree(dataset_dir):
     """Creates a :class:`torch:torch.utils.data.Dataset` for the given image
     classification dataset directory tree.


Test patch:
diff --git a/tests/misc/torch_tests.py b/tests/misc/torch_tests.py
--- a/tests/misc/torch_tests.py
+++ b/tests/misc/torch_tests.py
@@ -12,9 +12,6 @@
 import torch
 import torchvision
 
-import numpy as np
-from PIL import Image
-
 import fiftyone as fo
 import fiftyone.utils.torch as fout
 
@@ -100,7 +97,8 @@ def test_torch_image_patches_dataset():
     dataset.add_sample(sample)
 
     image_paths = [sample.filepath]
-    detections = [sample.detections]
+    patches = [sample.detections]
+
     transform = torchvision.transforms.Compose(
         [
             torchvision.transforms.Resize(
@@ -111,7 +109,7 @@ def test_torch_image_patches_dataset():
     )
 
     torch_dataset = fout.TorchImagePatchesDataset(
-        image_paths=image_paths, detections=detections, transform=transform
+        image_paths=image_paths, patches=patches, transform=transform
     )
 
     data_loader = torch.utils.data.DataLoader(torch_dataset, batch_size=1)