internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: voxel51/fiftyone

Issue:
[BUG] Can't view video datasets with `metadata` in App
When trying to load a video dataset with `fiftyone.core.labels.VideoMetadata` values in its `metadata` field, I get the following error:

```
Traceback (most recent call last):
  File "main.py", line 108, in wrapper
    return func(self, *args, **kwargs)
  File "main.py", line 316, in on_get_frame_labels
    field_labels = _make_image_labels(k, v, frame_number)
  File "main.py", line 156, in _make_image_labels
    fol.ImageLabel.from_dict(label).to_image_labels(name=name),
AttributeError: 'VideoMetadata' object has no attribute 'to_image_labels'
```

After setting the `metadata` to `None`, I can then visualize the dataset.


Discussion:
~~Update: this happens when working with the attached dataset from a remote session, but not from a local session...~~

This happens in both local and remote sessions

[pip-175k-sample.zip](https://github.com/voxel51/fiftyone/files/5425828/pip-175k-sample.zip)

Here's what I did:

```py
import fiftyone as fo

dataset = fo.Dataset.from_dir(
    "/path/to/pip-175k-sample",
    fo.types.FiftyOneDataset
)

dataset.compute_metadata()

session = fo.launch_app(dataset)
```

> Update: this happens when working with the attached dataset from a remote session, but not from a local session...

~~This is a likely duplicate of #208, in that case.~~

I got this confused with #627. Still a *possible* duplicate of #208, though.
Update to the update: this just happened in a local session too

Resolving patch:
diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py
--- a/fiftyone/server/main.py
+++ b/fiftyone/server/main.py
@@ -375,17 +375,27 @@ def on_get_video_data(self, sample_d):
 
             labels.add_frame(frame_labels)
 
+        sample_schema = state.dataset.get_field_schema()
         for frame_number in range(
             1, etav.get_frame_count(sample["filepath"]) + 1
         ):
             frame_labels = etav.VideoFrameLabels(frame_number=frame_number)
             for k, v in sample.items():
-                if isinstance(v, dict) and k != "frames" and "_cls" in v:
-                    field_labels = _make_frame_labels(k, v, frame_number)
-                    for obj in field_labels.objects:
-                        obj.frame_number = frame_number
+                if k not in sample_schema:
+                    continue
 
-                    frame_labels.merge_labels(field_labels)
+                field = sample_schema[k]
+                if not isinstance(field, fof.EmbeddedDocumentField):
+                    continue
+
+                if not issubclass(field.document_type, fol.Label):
+                    continue
+
+                field_labels = _make_frame_labels(k, v, frame_number)
+                for obj in field_labels.objects:
+                    obj.frame_number = frame_number
+
+                frame_labels.merge_labels(field_labels)
 
             labels.add_frame(frame_labels, overwrite=False)
 


Test patch:
Repository: voxel51/fiftyone

Issue:
[BUG] Field stats don't load on empty views
On `develop`, field label stats for empty views are stuck in a loading state rather than showing 0.

```py
import fiftyone as fo
import fiftyone.zoo as foz
from fiftyone import ViewField as F

dataset = foz.load_zoo_dataset(
    "cifar10",
    split="test",
    max_samples=50,
    shuffle=True,
)

session = fo.launch_app(dataset)

# Field label stats don't load properly
session.view = dataset.match(F("ground_truth.label") == "non_existent")
session.view = dataset.limit(0)
```

<img width="1277" alt="Screen Shot 2021-01-04 at 7 36 41 PM" src="https://user-images.githubusercontent.com/25985824/103593679-777a6a80-4ec4-11eb-995b-69888b24842f.png">



Discussion:

Resolving patch:
diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py
--- a/fiftyone/server/main.py
+++ b/fiftyone/server/main.py
@@ -221,14 +221,16 @@ async def post(self):
         elif event == "deactivate":
             self.write_message({"type": "deactivate"})
 
-        elif event == "statistics":
-            state = fos.StateDescription.from_dict(StateHandler.state)
-            view = state.view or state.dataset
+        state = fos.StateDescription.from_dict(StateHandler.state)
+        if state.view is not None:
+            view = state.view
+        else:
+            view = state.dataset
+
+        if event == "statistics":
             await StateHandler.send_statistics(view, only=self)
 
         elif event == "extended_statistics":
-            state = fos.StateDescription.from_dict(StateHandler.state)
-            view = state.view or state.dataset
             await StateHandler.send_statistics(
                 view, only=self, filters=state.filters
             )
@@ -425,7 +427,10 @@ async def on_filters_update(self, filters):
         """
         state = fos.StateDescription.from_dict(StateHandler.state)
         state.filters = filters
-        view = state.view or state.dataset
+        if state.view is not None:
+            view = state.view
+        else:
+            view = state.dataset
         StateHandler.state = state.serialize()
         for clients in PollingHandler.clients.values():
             clients.update({"extended_statistics"})
@@ -650,7 +655,10 @@ def get_statistics_awaitables(cls, only=None):
         if StateHandler.state["dataset"] is None:
             return []
         state = fos.StateDescription.from_dict(StateHandler.state)
-        view = state.view or state.dataset
+        if state.view is not None:
+            view = state.view
+        else:
+            view = state.dataset
         awaitables = [cls.send_statistics(view, only=only)]
 
         awaitables.append(


Test patch:
Repository: voxel51/fiftyone

Issue:
[FR] Limit integer label filter sliders to integers
Here's a dataset with some scalar values on its samples that have type `IntField`:

```
Name:           quickstart
Media type      image
Num samples:    200
Persistent:     False
Info:           {}
Tags:           ['validation']
Sample fields:
    media_type:   fiftyone.core.fields.StringField
    filepath:     fiftyone.core.fields.StringField
    tags:         fiftyone.core.fields.ListField(fiftyone.core.fields.StringField)
    metadata:     fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.metadata.Metadata)
    ground_truth: fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.labels.Detections)
    uniqueness:   fiftyone.core.fields.FloatField
    predictions:  fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.labels.Detections)
    tp_iou_0_75:  fiftyone.core.fields.IntField
    fp_iou_0_75:  fiftyone.core.fields.IntField
    fn_iou_0_75:  fiftyone.core.fields.IntField
```

It would be desirable if the label filter in the App respected the int data type and locked the slider into int values, rather than allowing/showing `[0.00    [34.92, 81.48]     97.00]` in the App

<img width="267" alt="Screen Shot 2020-10-07 at 2 01 12 PM" src="https://user-images.githubusercontent.com/25985824/95369815-f0f2c480-08a5-11eb-9b16-29c59c7abd7b.png">



Discussion:

Resolving patch:
diff --git a/fiftyone/core/config.py b/fiftyone/core/config.py
--- a/fiftyone/core/config.py
+++ b/fiftyone/core/config.py
@@ -211,6 +211,12 @@ def __init__(self, d=None):
             env_var="FIFTYONE_APP_COLOR_POOL",
             default=foc.DEFAULT_APP_COLOR_POOL,
         )
+        self.default_grid_zoom = self.parse_int(
+            d,
+            "default_grid_zoom",
+            env_var="FIFTYONE_APP_GRID_ZOOM",
+            default=5,
+        )
         self.notebook_height = self.parse_int(
             d,
             "notebook_height",
@@ -236,6 +242,23 @@ def __init__(self, d=None):
             default=True,
         )
 
+        self._validate()
+
+    def _validate(self):
+        if self.default_grid_zoom < 0 or self.default_grid_zoom > 10:
+            raise AppConfigError(
+                "`default_grid_zoom` must be in [0, 10]; found %d"
+                % self.default_grid_zoom
+            )
+
+
+class AppConfigError(etac.EnvConfigError):
+    """Exception raised when an invalid :class:`AppConfig` instance is
+    encountered.
+    """
+
+    pass
+
 
 def locate_config():
     """Returns the path to the :class:`FiftyOneConfig` on disk.


Test patch:
Repository: voxel51/fiftyone

Issue:
[BUG] Expression values for SortBy stage are not correctly rendered in the App
This issue may have arisen along with the new `json` type introduced in https://github.com/voxel51/fiftyone/pull/964.

The type of the `field_or_expr` parameter of `SortBy` is currently declared as `field|str|json`, which causes `sort_by("uniqueness")` to be properly rendered as `uniqueness` in the App, but `sort_by(F("ground_truth.detections").length())` is rendered as `Object [object]`, while it should be rendered as editable JSON. 

As a side note, if I reverse the order of the parameter type specification to `json|field|str`, then `sort_by(F("ground_truth.detections").length())` is correctly rendered as a JSON dict, but  `sort_by("uniqueness")` is treated as JSON and thus rendered as `"uniqueness"`, whereas it would be more user-friendly and consistent with other stage parameters if it were rendered as `uniqueness`.

I believe this is how the parsing engine currently works, but I'd say that composite types like `A|B|C` should be treated as ordered from left-to-right and the first valid type should be used to render the parameter. So the `field_or_expr` parameter should remain implemented as `field|str|json` so that the value will be treated as a `field` whenever possible.

So, ultimately, I believe the issue here is just that `Object [object]` should have been detected as `json` type and rendered appropriately.

```py
import fiftyone as fo
import fiftyone.zoo as foz
from fiftyone import ViewField as F

dataset = foz.load_zoo_dataset("quickstart")

session = fo.launch_app(dataset)

# looks good
session.view = dataset.sort_by("uniqueness")

# produces the screenshot below
session.view = dataset.sort_by(F("ground_truth.detections").length())
```

<img width="958" alt="Screen Shot 2021-04-15 at 11 04 52 PM" src="https://user-images.githubusercontent.com/25985824/114966065-d0508c00-9e3f-11eb-90b6-58a3bef8faa8.png">



Discussion:

Resolving patch:
diff --git a/fiftyone/core/stages.py b/fiftyone/core/stages.py
--- a/fiftyone/core/stages.py
+++ b/fiftyone/core/stages.py
@@ -3603,7 +3603,11 @@ def _kwargs(self):
     @classmethod
     def _params(cls):
         return [
-            {"name": "field_or_expr", "type": "field|str|json"},
+            {
+                "name": "field_or_expr",
+                "type": "field|str|json",
+                "placeholder": "field or expression",
+            },
             {
                 "name": "reverse",
                 "type": "bool",


Test patch:
Repository: voxel51/fiftyone

Issue:
[FR] Switching to patches view should respect the current extended view
In the example below, switching to patches view (objects or evaluations) should first "bookmark" the extended view and then create the patches stage. This achieves the desired effect that the user likely has: "I'd like to see patches for the contents of the current sample grid"

<img width="1275" alt="Screen Shot 2021-05-07 at 8 35 39 PM" src="https://user-images.githubusercontent.com/25985824/117519943-d1bf3100-af73-11eb-93d3-98490854b6a5.png">




Discussion:

Resolving patch:
diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py
--- a/fiftyone/server/main.py
+++ b/fiftyone/server/main.py
@@ -718,14 +718,28 @@ async def on_all_tags(caller):
         )
 
     @staticmethod
-    async def on_save_filters(caller):
+    async def on_save_filters(caller, add_stages=[], with_selected=False):
         state = fos.StateDescription.from_dict(StateHandler.state)
         if state.view is not None:
             view = state.view
         else:
             view = state.dataset
 
-        state.view = get_extended_view(view, state.filters)
+        view = get_extended_view(view, state.filters)
+
+        if with_selected:
+            if state.selected:
+                view = view.select(state.selected)
+            elif state.selected_labels:
+                view = view.select_labels(state.selected_labels)
+
+        for d in add_stages:
+            stage = fosg.ViewStage._from_dict(d)
+            view = view.add_stage(stage)
+
+        state.selected = []
+        state.selected_labels = []
+        state.view = view
         state.filters = {}
 
         await StateHandler.on_update(caller, state.serialize())


Test patch:
Repository: voxel51/fiftyone

Issue:
[BUG] Loading videos in the App in Google Colab
An error is raised when a video dataset is loaded in the App in Google Colab. This does not seem to prevent the videos from playing though.
```shell
Server Error

An exception has been raised by the server. Your session has been reverted to its previous state.

Traceback (most recent call last):
  File "main.py", line 180, in wrapper
    result = await func(self, *args, **kwargs)
  File "main.py", line 285, in post
    await handle(caller, **message)
  File "main.py", line 652, in on_get_video_data
    self.sample_collection(), state, view, [_id]
AttributeError: 'PollingHandler' object has no attribute 'sample_collection'
```

Code to replicate:
```python
import fiftyone as fo
import fiftyone.zoo as foz

dataset = foz.load_zoo_dataset("quickstart-video")
session = fo.launch_app(dataset)
```

http://localhost:5151/?fiftyoneColab=true


Discussion:

Resolving patch:
diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py
--- a/fiftyone/server/main.py
+++ b/fiftyone/server/main.py
@@ -649,9 +649,12 @@ async def on_get_video_data(self, _id):
             view = state.dataset
 
         result = await _get_video_data(
-            self.sample_collection(), state, view, [_id]
+            StateHandler.sample_collection(), state, view, [_id]
         )
         sample, frames, labels = result[0]
+        convert([labels])
+        convert([sample])
+        convert(frames)
 
         fps = etav.get_frame_rate(sample["filepath"])
         _write_message(
@@ -1312,9 +1315,6 @@ async def _get_video_data(col, state, view, _ids):
         else:
             sample["frames"] = None
 
-        convert([sample])
-        convert(frames)
-
         labels = _make_video_labels(state, view, sample, frames)
         results.append((sample, frames, labels))
 


Test patch:
Repository: voxel51/fiftyone

Issue:
[BUG] Labelbox Import Uses Incorrect Labelbox ID?
### System information

-   **OS Platform and Distribution**: Linux Ubuntu 20.04
-   **FiftyOne installed from (pip or source)**: pip
-   **FiftyOne version (run `fiftyone --version`)**:  v0.9.1
-   **Python version**: 3.6.9

### Commands to reproduce

We used upload_media_to_labelbox() to upload images from Voxel51 to Labelbox and then performed bounding box labeling. We then exported a JSON file from Labelbox. We ran the import_from_labelbox() function and encountered this error message for each labeled sample:

```
Skipping labels for unknown Labelbox ID 'ckou3botb2y0d0yw47ftmggdr'; provide a `download_dir` if you wish to download media and create samples for new media
```

We were able to overcome this problem by using the "DataRow ID" field instead of the "ID" field from the exported JSON file.

We believe this problem results from the upload_media function, which uses the Labelbox SDK create_data_row() function, which creates a data row object and then the ID of the data row is saved into Voxel51. The ID in the exported JSON file refers to the ID of the label, not the data row.
https://github.com/voxel51/fiftyone/blob/496e402db4eb2974acb12d26f4ffe926ced2d7b0/fiftyone/utils/labelbox.py#L393-L396

### Recommended Fix

We used the following fix and it worked for us.

We changed the "ID" field below:

https://github.com/voxel51/fiftyone/blob/496e402db4eb2974acb12d26f4ffe926ced2d7b0/fiftyone/utils/labelbox.py#L131

to "DataRow ID"



Discussion:
@luke-iqt contributed to this bug report equally!

Resolving patch:
diff --git a/fiftyone/utils/labelbox.py b/fiftyone/utils/labelbox.py
--- a/fiftyone/utils/labelbox.py
+++ b/fiftyone/utils/labelbox.py
@@ -61,7 +61,7 @@ def import_from_labelbox(
 
         [
             {
-                "ID": <labelbox-id>,
+                "DataRow ID": <labelbox-id>,
                 "Labeled Data": <url-or-None>,
                 "Label": {...}
             }
@@ -128,7 +128,7 @@ def import_from_labelbox(
     # ref: https://github.com/Labelbox/labelbox/blob/7c79b76310fa867dd38077e83a0852a259564da1/exporters/coco-exporter/coco_exporter.py#L33
     with fou.ProgressBar() as pb:
         for d in pb(d_list):
-            labelbox_id = d["ID"]
+            labelbox_id = d["DataRow ID"]
 
             if labelbox_id in id_map:
                 # Get existing sample
@@ -482,7 +482,7 @@ def convert_labelbox_export_to_import(inpath, outpath=None, video_outdir=None):
             )
 
             dout_map[uuid] = {
-                "ID": uuid,
+                "DataRow ID": uuid,
                 "Labeled Data": None,
                 "Label": {"frames": frames_outpath},
             }
@@ -490,7 +490,7 @@ def convert_labelbox_export_to_import(inpath, outpath=None, video_outdir=None):
 
         if uuid not in dout_map:
             dout_map[uuid] = {
-                "ID": uuid,
+                "DataRow ID": uuid,
                 "Labeled Data": None,
                 "Label": {"objects": [], "classifications": []},
             }


Test patch:
Repository: voxel51/fiftyone

Issue:
[BUG] Refreshing browser tab exits session.wait()
Refreshing the App window in the browser causes `session.wait()` to exit, but the intention is that `session.wait()` should only exit when the App window is actually closed.

This is worth addressing because refreshing the App window via ctrl + R is a common practice for App users.

```py
import fiftyone as fo 

session = fo.launch_app()

# should block until App is closed, but ctrl + R also breaks
session.wait()  
```



Discussion:

Resolving patch:
diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py
--- a/fiftyone/server/main.py
+++ b/fiftyone/server/main.py
@@ -206,6 +206,7 @@ async def wrapper(self, *args, **kwargs):
 
 _notebook_clients = {}
 _deactivated_clients = set()
+_DISCONNECT_TIMEOUT = 1  # seconds
 
 
 class PollingHandler(tornado.web.RequestHandler):
@@ -421,8 +422,13 @@ def on_close(self):
         """
         StateHandler.clients.remove(self)
         StateHandler.app_clients.discard(self)
-        if not StateHandler.app_clients:
-            _write_message({"type": "close"}, session=True)
+
+        async def close_wait():
+            await asyncio.sleep(_DISCONNECT_TIMEOUT)
+            if not StateHandler.app_clients:
+                _write_message({"type": "close"}, session=True)
+
+        tornado.ioloop.IOLoop.current().add_callback(close_wait)
 
     @_catch_errors
     async def on_message(self, message):


Test patch:
Repository: voxel51/fiftyone

Issue:
[BUG] Cannot open desktop App with fiftyone==0.12.0 source install
On a source install of `fiftyone==0.12.0`, the desktop App cannot be opened:

```shell
fiftyone app launch --desktop
```

```
Usage Error: Couldn't find a script named "start-app"
```

The `start-app` script in the last release was
https://github.com/voxel51/fiftyone/blob/d383bfb0fd88a04a3352f06ddaa48599d0e8ce2a/app/package.json#L11)

I tried updating
https://github.com/voxel51/fiftyone/blob/da5d83a3e5b1578ba162f2453f26a1139991d370/fiftyone/core/service.py#L456

to both `yarn dev` and `yarn start` per the now-available scripts
https://github.com/voxel51/fiftyone/blob/da5d83a3e5b1578ba162f2453f26a1139991d370/app/package.json#L9-L10

but neither caused the App window to appear for me (but no error either, just hung).


Discussion:

Resolving patch:
diff --git a/fiftyone/core/service.py b/fiftyone/core/service.py
--- a/fiftyone/core/service.py
+++ b/fiftyone/core/service.py
@@ -453,7 +453,7 @@ def command(self):
 
     def find_app(self):
         if foc.DEV_INSTALL:
-            return ["yarn", "start-app"]
+            return ["yarn", "start-desktop"]
 
         for path in etau.list_files("./"):
             if path.endswith(".tar.gz"):


Test patch:
Repository: voxel51/fiftyone

Issue:
[BUG] VS Code Jupyter notebook creates duplicate screenshot
VS Code's Jupyter notebook environment renders a duplicate screenshot when freezing App windows. My investigations lead me to believe that this a bug in VS code, as I have verified that FiftyOne `Session` instances only capture and render the screenshot once.

![Screenshot from 2021-07-13 17-12-42](https://user-images.githubusercontent.com/19821840/125537069-bff77a98-de49-470d-9b56-42c3e3635467.png)

Note that this is with the Jupyter extension. It seems that the Python extension has native Jupyter support coming soon.



Discussion:

Resolving patch:
diff --git a/fiftyone/core/session.py b/fiftyone/core/session.py
--- a/fiftyone/core/session.py
+++ b/fiftyone/core/session.py
@@ -978,8 +978,8 @@ def _show(self, height=None):
 
         import IPython.display
 
-        handle = IPython.display.display(display_id=True)
         uuid = str(uuid4())
+        handle = IPython.display.DisplayHandle(display_id=uuid)
 
         # @todo isn't it bad to set this here? The first time this is called
         # is before `self.state` has been initialized


Test patch: