We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Show currently selected samples in the App, and allow selection/exclusion
We should replace `Dataset: coco-2017-validation` in the App screenshot below with `Selected # samples`, to indicate how many samples are currently selected:

Also, we should add an icon next to `Selected # samples` that the user can click on. This icon would open a small window that lets the user pick between two actions to take:
- show only these samples: this would add a `Select([list, of, selected, samples])` stage to the current view
- exclude these samples: this would add an `Exclude([list, of, selected, samples])` stage to the current view

<img width="1323" alt="Screen Shot 2020-08-13 at 3 25 08 PM" src="https://user-images.githubusercontent.com/25985824/90178699-45803480-dd7a-11ea-9b21-3e1adc413e23.png">


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `fiftyone/server/main.py`
Content:
```
1 """
2 FiftyOne Flask server.
3 
4 | Copyright 2017-2020, Voxel51, Inc.
5 | `voxel51.com <https://voxel51.com/>`_
6 |
7 """
8 import argparse
9 import json
10 import logging
11 import os
12 import uuid
13 
14 from bson import json_util
15 from flask import Flask, jsonify, request, send_file
16 from flask_cors import CORS
17 from flask_socketio import emit, Namespace, SocketIO
18 
19 import eta.core.utils as etau
20 
21 os.environ["FIFTYONE_SERVER"] = "1"
22 import fiftyone.constants as foc
23 import fiftyone.core.fields as fof
24 import fiftyone.core.odm as foo
25 from fiftyone.core.service import DatabaseService
26 from fiftyone.core.stages import _STAGES
27 import fiftyone.core.state as fos
28 
29 from util import get_image_size
30 from pipelines import DISTRIBUTION_PIPELINES, LABELS, SCALARS
31 
32 
33 logger = logging.getLogger(__name__)
34 
35 # connect to the existing DB service to initialize global port information
36 db = DatabaseService()
37 db.start()
38 
39 app = Flask(__name__)
40 CORS(app)
41 
42 app.config["SECRET_KEY"] = "fiftyone"
43 
44 socketio = SocketIO(app, async_mode="eventlet", cors_allowed_origins="*")
45 
46 
47 def get_user_id():
48     uid_path = os.path.join(foc.FIFTYONE_CONFIG_DIR, "var", "uid")
49 
50     def read():
51         try:
52             with open(uid_path) as f:
53                 return next(f).strip()
54         except (IOError, StopIteration):
55             return None
56 
57     if not read():
58         os.makedirs(os.path.dirname(uid_path), exist_ok=True)
59         with open(uid_path, "w") as f:
60             f.write(str(uuid.uuid4()))
61     return read()
62 
63 
64 @app.route("/")
65 def get_sample_media():
66     """Gets the sample media.
67 
68     Returns:
69         bytes
70     """
71     path = request.args.get("path")
72     return send_file(path)
73 
74 
75 @app.route("/fiftyone")
76 def get_fiftyone_info():
77     return jsonify({"version": foc.VERSION})
78 
79 
80 @app.route("/stages")
81 def get_stages():
82     """Gets ViewStage descriptions"""
83     return {
84         "stages": [
85             {"name": stage.__name__, "params": stage._params()}
86             for stage in _STAGES
87         ]
88     }
89 
90 
91 def _load_state(func):
92     def wrapper(self, *args, **kwargs):
93         state = fos.StateDescriptionWithDerivables.from_dict(self.state)
94         state = func(self, state, *args, **kwargs)
95         self.state = state.serialize()
96         emit("update", self.state, broadcast=True, include_self=False)
97         return self.state
98 
99     return wrapper
100 
101 
102 class StateController(Namespace):
103     """State controller.
104 
105     Attributes:
106         state: a :class:`fiftyone.core.state.StateDescriptionWithDerivables`
107                instance
108 
109     Args:
110         **args: positional arguments for ``flask_socketio.Namespace``
111         **kwargs: keyword arguments for ``flask_socketio.Namespace``
112     """
113 
114     def __init__(self, *args, **kwargs):
115         self.state = fos.StateDescriptionWithDerivables().serialize()
116         super().__init__(*args, **kwargs)
117 
118     def on_connect(self):
119         """Handles connection to the server."""
120         pass
121 
122     def on_disconnect(self):
123         """Handles disconnection from the server."""
124         pass
125 
126     def on_update(self, data):
127         """Updates the state.
128 
129         Args:
130             state_dict: a serialized
131                 :class:`fiftyone.core.state.StateDescription`
132         """
133         self.state = fos.StateDescriptionWithDerivables.from_dict(
134             data["data"]
135         ).serialize()
136         emit(
137             "update",
138             self.state,
139             broadcast=True,
140             include_self=data["include_self"],
141         )
142 
143     def on_get_fiftyone_info(self):
144         """Retrieves information about the FiftyOne installation."""
145         return {
146             "version": foc.VERSION,
147             "user_id": get_user_id(),
148         }
149 
150     def on_get_current_state(self, _):
151         """Gets the current state.
152 
153         Returns:
154             a :class:`fiftyone.core.state.StateDescriptionWithDerivables`
155         """
156         return self.state
157 
158     @_load_state
159     def on_add_selection(self, state, _id):
160         """Adds a sample to the selected samples list.
161 
162         Args:
163             state: the current
164                 :class:`fiftyone.core.state.StateDescriptionWithDerivables`
165             _id: the sample ID
166 
167         Returns:
168             the updated
169                 :class:`fiftyone.core.state.StateDescriptionWithDerivables`
170         """
171         selected = set(state.selected)
172         selected.add(_id)
173         state.selected = list(selected)
174         return state
175 
176     @_load_state
177     def on_remove_selection(self, state, _id):
178         """Remove a sample from the selected samples list
179 
180         Args:
181             state: the current
182                 :class:`fiftyone.core.state.StateDescriptionWithDerivables`
183             _id: the sample ID
184 
185         Returns:
186             the updated
187                 :class:`fiftyone.core.state.StateDescriptionWithDerivables`
188         """
189         selected = set(state.selected)
190         selected.remove(_id)
191         state.selected = list(selected)
192         return state
193 
194     def on_page(self, page, page_length=20):
195         """Gets the requested page of samples.
196 
197         Args:
198             page: the page number
199             page_length: the page length
200 
201         Returns:
202             the list of sample dicts for the page
203         """
204         state = fos.StateDescriptionWithDerivables.from_dict(self.state)
205         if state.view is not None:
206             view = state.view
207         elif state.dataset is not None:
208             view = state.dataset.view()
209         else:
210             return []
211 
212         view = view.skip((page - 1) * page_length).limit(page_length + 1)
213         samples = [
214             json.loads(
215                 json_util.dumps(s.to_mongo_dict()), parse_constant=lambda c: c
216             )
217             for s in view
218         ]
219         more = False
220         if len(samples) > page_length:
221             samples = samples[:page_length]
222             more = page + 1
223 
224         results = [{"sample": s} for s in samples]
225         for r in results:
226             w, h = get_image_size(r["sample"]["filepath"])
227             r["width"] = w
228             r["height"] = h
229 
230         return {"results": results, "more": more}
231 
232     def on_get_distributions(self, group):
233         """Gets the distributions for the current state with respect to a
234         group.
235 
236         Args:
237             group: one of "labels", "tags", or "scalars"
238 
239         Returns:
240             a list of distributions
241         """
242         state = fos.StateDescriptionWithDerivables.from_dict(self.state)
243         if state.view is not None:
244             view = state.view
245         elif state.dataset is not None:
246             view = state.dataset.view()
247         else:
248             return []
249 
250         return _get_distributions(view, group)
251 
252 
253 def _get_distributions(view, group):
254     pipeline = DISTRIBUTION_PIPELINES[group]
255 
256     # we add a sub-pipeline for each numeric as it looks like multiple
257     # buckets in a single pipeline is not supported
258     if group == SCALARS:
259         _numeric_distribution_pipelines(view, pipeline)
260 
261     result = list(view.aggregate(pipeline))
262 
263     if group in {LABELS, SCALARS}:
264         new_result = []
265         for f in result[0].values():
266             new_result += f
267         result = new_result
268 
269     if group != SCALARS:
270         for idx, dist in enumerate(result):
271             result[idx]["data"] = sorted(
272                 result[idx]["data"], key=lambda c: c["count"], reverse=True
273             )
274 
275     return sorted(result, key=lambda d: d["name"])
276 
277 
278 def _numeric_bounds(view, numerics):
279     bounds_pipeline = [{"$facet": {}}]
280     for idx, (k, v) in enumerate(numerics.items()):
281         bounds_pipeline[0]["$facet"]["numeric-%d" % idx] = [
282             {
283                 "$group": {
284                     "_id": k,
285                     "min": {"$min": "$%s" % k},
286                     "max": {"$max": "$%s" % k},
287                 },
288             }
289         ]
290 
291     return list(view.aggregate(bounds_pipeline))[0] if len(numerics) else {}
292 
293 
294 def _numeric_distribution_pipelines(view, pipeline, buckets=50):
295     numerics = view._dataset.get_field_schema(ftype=fof.IntField)
296     numerics.update(view._dataset.get_field_schema(ftype=fof.FloatField))
297 
298     # here we query the min and max for each numeric field
299     # unfortunately, it looks like this has to be a separate query
300     bounds = _numeric_bounds(view, numerics)
301 
302     # for each numeric field, build the boundaries array with the
303     # min/max results when adding the field's sub-pipeline
304     for idx, (k, v) in enumerate(numerics.items()):
305         sub_pipeline = "numeric-%d" % idx
306         field_bounds = bounds[sub_pipeline][0]
307         mn = field_bounds["min"]
308         mx = field_bounds["max"]
309 
310         # if min and max are equal, we artifically create a boundary
311         # @todo alternative approach to scalar fields with only one value
312         if mn == mx:
313             if mx > 0:
314                 mn = 0
315             else:
316                 mx = 0
317 
318         step = (mx - mn) / buckets
319         boundaries = [mn + step * s for s in range(0, buckets)]
320 
321         pipeline[0]["$facet"][sub_pipeline] = [
322             {
323                 "$bucket": {
324                     "groupBy": "$%s" % k,
325                     "boundaries": boundaries,
326                     "default": "null",
327                     "output": {"count": {"$sum": 1}},
328                 }
329             },
330             {
331                 "$group": {
332                     "_id": k,
333                     "data": {
334                         "$push": {
335                             "key": {
336                                 "$cond": [
337                                     {"$ne": ["$_id", "null"]},
338                                     {"$add": ["$_id", step / 2]},
339                                     "null",
340                                 ]
341                             },
342                             "count": "$count",
343                         }
344                     },
345                 }
346             },
347             {
348                 "$project": {
349                     "name": k,
350                     "type": v.__class__.__name__[
351                         : -len("Field")  # grab field type from the class
352                     ].lower(),
353                     "data": "$data",
354                 }
355             },
356         ]
357 
358 
359 socketio.on_namespace(StateController("/state"))
360 
361 
362 if __name__ == "__main__":
363     log_path = os.path.join(
364         foc.FIFTYONE_CONFIG_DIR, "var", "log", "server.log"
365     )
366     etau.ensure_basedir(log_path)
367     # pylint: disable=no-member
368     app.logger.addHandler(logging.FileHandler(log_path, mode="w"))
369 
370     parser = argparse.ArgumentParser()
371     parser.add_argument("--port", type=int, default=5151)
372     args = parser.parse_args()
373 
374     socketio.run(app, port=args.port, debug=foc.DEV_INSTALL)
375
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py
--- a/fiftyone/server/main.py
+++ b/fiftyone/server/main.py
@@ -88,15 +88,23 @@
     }
 
 
-def _load_state(func):
-    def wrapper(self, *args, **kwargs):
-        state = fos.StateDescriptionWithDerivables.from_dict(self.state)
-        state = func(self, state, *args, **kwargs)
-        self.state = state.serialize()
-        emit("update", self.state, broadcast=True, include_self=False)
-        return self.state
+def _load_state(trigger_update=False):
+    def decorator(func):
+        def wrapper(self, *args, **kwargs):
+            state = fos.StateDescriptionWithDerivables.from_dict(self.state)
+            state = func(self, state, *args, **kwargs)
+            self.state = state.serialize()
+            emit(
+                "update",
+                self.state,
+                broadcast=True,
+                include_self=trigger_update,
+            )
+            return self.state
+
+        return wrapper
 
-    return wrapper
+    return decorator
 
 
 class StateController(Namespace):
@@ -155,7 +163,7 @@
         """
         return self.state
 
-    @_load_state
+    @_load_state()
     def on_add_selection(self, state, _id):
         """Adds a sample to the selected samples list.
 
@@ -173,7 +181,7 @@
         state.selected = list(selected)
         return state
 
-    @_load_state
+    @_load_state()
     def on_remove_selection(self, state, _id):
         """Remove a sample from the selected samples list
 
@@ -191,6 +199,21 @@
         state.selected = list(selected)
         return state
 
+    @_load_state()
+    def on_clear_selection(self, state):
+        """Remove all samples from the selected samples list
+
+        Args:
+            state: the current
+                :class:`fiftyone.core.state.StateDescriptionWithDerivables`
+
+        Returns:
+            the updated
+                :class:`fiftyone.core.state.StateDescriptionWithDerivables`
+        """
+        state.selected = []
+        return state
+
     def on_page(self, page, page_length=20):
         """Gets the requested page of samples.

{"golden_diff": "diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py\n--- a/fiftyone/server/main.py\n+++ b/fiftyone/server/main.py\n@@ -88,15 +88,23 @@\n     }\n \n \n-def _load_state(func):\n-    def wrapper(self, *args, **kwargs):\n-        state = fos.StateDescriptionWithDerivables.from_dict(self.state)\n-        state = func(self, state, *args, **kwargs)\n-        self.state = state.serialize()\n-        emit(\"update\", self.state, broadcast=True, include_self=False)\n-        return self.state\n+def _load_state(trigger_update=False):\n+    def decorator(func):\n+        def wrapper(self, *args, **kwargs):\n+            state = fos.StateDescriptionWithDerivables.from_dict(self.state)\n+            state = func(self, state, *args, **kwargs)\n+            self.state = state.serialize()\n+            emit(\n+                \"update\",\n+                self.state,\n+                broadcast=True,\n+                include_self=trigger_update,\n+            )\n+            return self.state\n+\n+        return wrapper\n \n-    return wrapper\n+    return decorator\n \n \n class StateController(Namespace):\n@@ -155,7 +163,7 @@\n         \"\"\"\n         return self.state\n \n-    @_load_state\n+    @_load_state()\n     def on_add_selection(self, state, _id):\n         \"\"\"Adds a sample to the selected samples list.\n \n@@ -173,7 +181,7 @@\n         state.selected = list(selected)\n         return state\n \n-    @_load_state\n+    @_load_state()\n     def on_remove_selection(self, state, _id):\n         \"\"\"Remove a sample from the selected samples list\n \n@@ -191,6 +199,21 @@\n         state.selected = list(selected)\n         return state\n \n+    @_load_state()\n+    def on_clear_selection(self, state):\n+        \"\"\"Remove all samples from the selected samples list\n+\n+        Args:\n+            state: the current\n+                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n+\n+        Returns:\n+            the updated\n+                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n+        \"\"\"\n+        state.selected = []\n+        return state\n+\n     def on_page(self, page, page_length=20):\n         \"\"\"Gets the requested page of samples.\n", "issue": "Show currently selected samples in the App, and allow selection/exclusion\nWe should replace `Dataset: coco-2017-validation` in the App screenshot below with `Selected # samples`, to indicate how many samples are currently selected:\r\n\r\nAlso, we should add an icon next to `Selected # samples` that the user can click on. This icon would open a small window that lets the user pick between two actions to take:\r\n- show only these samples: this would add a `Select([list, of, selected, samples])` stage to the current view\r\n- exclude these samples: this would add an `Exclude([list, of, selected, samples])` stage to the current view\r\n\r\n<img width=\"1323\" alt=\"Screen Shot 2020-08-13 at 3 25 08 PM\" src=\"https://user-images.githubusercontent.com/25985824/90178699-45803480-dd7a-11ea-9b21-3e1adc413e23.png\">\r\n\n", "before_files": [{"content": "\"\"\"\nFiftyOne Flask server.\n\n| Copyright 2017-2020, Voxel51, Inc.\n| `voxel51.com <https://voxel51.com/>`_\n|\n\"\"\"\nimport argparse\nimport json\nimport logging\nimport os\nimport uuid\n\nfrom bson import json_util\nfrom flask import Flask, jsonify, request, send_file\nfrom flask_cors import CORS\nfrom flask_socketio import emit, Namespace, SocketIO\n\nimport eta.core.utils as etau\n\nos.environ[\"FIFTYONE_SERVER\"] = \"1\"\nimport fiftyone.constants as foc\nimport fiftyone.core.fields as fof\nimport fiftyone.core.odm as foo\nfrom fiftyone.core.service import DatabaseService\nfrom fiftyone.core.stages import _STAGES\nimport fiftyone.core.state as fos\n\nfrom util import get_image_size\nfrom pipelines import DISTRIBUTION_PIPELINES, LABELS, SCALARS\n\n\nlogger = logging.getLogger(__name__)\n\n# connect to the existing DB service to initialize global port information\ndb = DatabaseService()\ndb.start()\n\napp = Flask(__name__)\nCORS(app)\n\napp.config[\"SECRET_KEY\"] = \"fiftyone\"\n\nsocketio = SocketIO(app, async_mode=\"eventlet\", cors_allowed_origins=\"*\")\n\n\ndef get_user_id():\n    uid_path = os.path.join(foc.FIFTYONE_CONFIG_DIR, \"var\", \"uid\")\n\n    def read():\n        try:\n            with open(uid_path) as f:\n                return next(f).strip()\n        except (IOError, StopIteration):\n            return None\n\n    if not read():\n        os.makedirs(os.path.dirname(uid_path), exist_ok=True)\n        with open(uid_path, \"w\") as f:\n            f.write(str(uuid.uuid4()))\n    return read()\n\n\n@app.route(\"/\")\ndef get_sample_media():\n    \"\"\"Gets the sample media.\n\n    Returns:\n        bytes\n    \"\"\"\n    path = request.args.get(\"path\")\n    return send_file(path)\n\n\n@app.route(\"/fiftyone\")\ndef get_fiftyone_info():\n    return jsonify({\"version\": foc.VERSION})\n\n\n@app.route(\"/stages\")\ndef get_stages():\n    \"\"\"Gets ViewStage descriptions\"\"\"\n    return {\n        \"stages\": [\n            {\"name\": stage.__name__, \"params\": stage._params()}\n            for stage in _STAGES\n        ]\n    }\n\n\ndef _load_state(func):\n    def wrapper(self, *args, **kwargs):\n        state = fos.StateDescriptionWithDerivables.from_dict(self.state)\n        state = func(self, state, *args, **kwargs)\n        self.state = state.serialize()\n        emit(\"update\", self.state, broadcast=True, include_self=False)\n        return self.state\n\n    return wrapper\n\n\nclass StateController(Namespace):\n    \"\"\"State controller.\n\n    Attributes:\n        state: a :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n               instance\n\n    Args:\n        **args: positional arguments for ``flask_socketio.Namespace``\n        **kwargs: keyword arguments for ``flask_socketio.Namespace``\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        self.state = fos.StateDescriptionWithDerivables().serialize()\n        super().__init__(*args, **kwargs)\n\n    def on_connect(self):\n        \"\"\"Handles connection to the server.\"\"\"\n        pass\n\n    def on_disconnect(self):\n        \"\"\"Handles disconnection from the server.\"\"\"\n        pass\n\n    def on_update(self, data):\n        \"\"\"Updates the state.\n\n        Args:\n            state_dict: a serialized\n                :class:`fiftyone.core.state.StateDescription`\n        \"\"\"\n        self.state = fos.StateDescriptionWithDerivables.from_dict(\n            data[\"data\"]\n        ).serialize()\n        emit(\n            \"update\",\n            self.state,\n            broadcast=True,\n            include_self=data[\"include_self\"],\n        )\n\n    def on_get_fiftyone_info(self):\n        \"\"\"Retrieves information about the FiftyOne installation.\"\"\"\n        return {\n            \"version\": foc.VERSION,\n            \"user_id\": get_user_id(),\n        }\n\n    def on_get_current_state(self, _):\n        \"\"\"Gets the current state.\n\n        Returns:\n            a :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n        \"\"\"\n        return self.state\n\n    @_load_state\n    def on_add_selection(self, state, _id):\n        \"\"\"Adds a sample to the selected samples list.\n\n        Args:\n            state: the current\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n            _id: the sample ID\n\n        Returns:\n            the updated\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n        \"\"\"\n        selected = set(state.selected)\n        selected.add(_id)\n        state.selected = list(selected)\n        return state\n\n    @_load_state\n    def on_remove_selection(self, state, _id):\n        \"\"\"Remove a sample from the selected samples list\n\n        Args:\n            state: the current\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n            _id: the sample ID\n\n        Returns:\n            the updated\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n        \"\"\"\n        selected = set(state.selected)\n        selected.remove(_id)\n        state.selected = list(selected)\n        return state\n\n    def on_page(self, page, page_length=20):\n        \"\"\"Gets the requested page of samples.\n\n        Args:\n            page: the page number\n            page_length: the page length\n\n        Returns:\n            the list of sample dicts for the page\n        \"\"\"\n        state = fos.StateDescriptionWithDerivables.from_dict(self.state)\n        if state.view is not None:\n            view = state.view\n        elif state.dataset is not None:\n            view = state.dataset.view()\n        else:\n            return []\n\n        view = view.skip((page - 1) * page_length).limit(page_length + 1)\n        samples = [\n            json.loads(\n                json_util.dumps(s.to_mongo_dict()), parse_constant=lambda c: c\n            )\n            for s in view\n        ]\n        more = False\n        if len(samples) > page_length:\n            samples = samples[:page_length]\n            more = page + 1\n\n        results = [{\"sample\": s} for s in samples]\n        for r in results:\n            w, h = get_image_size(r[\"sample\"][\"filepath\"])\n            r[\"width\"] = w\n            r[\"height\"] = h\n\n        return {\"results\": results, \"more\": more}\n\n    def on_get_distributions(self, group):\n        \"\"\"Gets the distributions for the current state with respect to a\n        group.\n\n        Args:\n            group: one of \"labels\", \"tags\", or \"scalars\"\n\n        Returns:\n            a list of distributions\n        \"\"\"\n        state = fos.StateDescriptionWithDerivables.from_dict(self.state)\n        if state.view is not None:\n            view = state.view\n        elif state.dataset is not None:\n            view = state.dataset.view()\n        else:\n            return []\n\n        return _get_distributions(view, group)\n\n\ndef _get_distributions(view, group):\n    pipeline = DISTRIBUTION_PIPELINES[group]\n\n    # we add a sub-pipeline for each numeric as it looks like multiple\n    # buckets in a single pipeline is not supported\n    if group == SCALARS:\n        _numeric_distribution_pipelines(view, pipeline)\n\n    result = list(view.aggregate(pipeline))\n\n    if group in {LABELS, SCALARS}:\n        new_result = []\n        for f in result[0].values():\n            new_result += f\n        result = new_result\n\n    if group != SCALARS:\n        for idx, dist in enumerate(result):\n            result[idx][\"data\"] = sorted(\n                result[idx][\"data\"], key=lambda c: c[\"count\"], reverse=True\n            )\n\n    return sorted(result, key=lambda d: d[\"name\"])\n\n\ndef _numeric_bounds(view, numerics):\n    bounds_pipeline = [{\"$facet\": {}}]\n    for idx, (k, v) in enumerate(numerics.items()):\n        bounds_pipeline[0][\"$facet\"][\"numeric-%d\" % idx] = [\n            {\n                \"$group\": {\n                    \"_id\": k,\n                    \"min\": {\"$min\": \"$%s\" % k},\n                    \"max\": {\"$max\": \"$%s\" % k},\n                },\n            }\n        ]\n\n    return list(view.aggregate(bounds_pipeline))[0] if len(numerics) else {}\n\n\ndef _numeric_distribution_pipelines(view, pipeline, buckets=50):\n    numerics = view._dataset.get_field_schema(ftype=fof.IntField)\n    numerics.update(view._dataset.get_field_schema(ftype=fof.FloatField))\n\n    # here we query the min and max for each numeric field\n    # unfortunately, it looks like this has to be a separate query\n    bounds = _numeric_bounds(view, numerics)\n\n    # for each numeric field, build the boundaries array with the\n    # min/max results when adding the field's sub-pipeline\n    for idx, (k, v) in enumerate(numerics.items()):\n        sub_pipeline = \"numeric-%d\" % idx\n        field_bounds = bounds[sub_pipeline][0]\n        mn = field_bounds[\"min\"]\n        mx = field_bounds[\"max\"]\n\n        # if min and max are equal, we artifically create a boundary\n        # @todo alternative approach to scalar fields with only one value\n        if mn == mx:\n            if mx > 0:\n                mn = 0\n            else:\n                mx = 0\n\n        step = (mx - mn) / buckets\n        boundaries = [mn + step * s for s in range(0, buckets)]\n\n        pipeline[0][\"$facet\"][sub_pipeline] = [\n            {\n                \"$bucket\": {\n                    \"groupBy\": \"$%s\" % k,\n                    \"boundaries\": boundaries,\n                    \"default\": \"null\",\n                    \"output\": {\"count\": {\"$sum\": 1}},\n                }\n            },\n            {\n                \"$group\": {\n                    \"_id\": k,\n                    \"data\": {\n                        \"$push\": {\n                            \"key\": {\n                                \"$cond\": [\n                                    {\"$ne\": [\"$_id\", \"null\"]},\n                                    {\"$add\": [\"$_id\", step / 2]},\n                                    \"null\",\n                                ]\n                            },\n                            \"count\": \"$count\",\n                        }\n                    },\n                }\n            },\n            {\n                \"$project\": {\n                    \"name\": k,\n                    \"type\": v.__class__.__name__[\n                        : -len(\"Field\")  # grab field type from the class\n                    ].lower(),\n                    \"data\": \"$data\",\n                }\n            },\n        ]\n\n\nsocketio.on_namespace(StateController(\"/state\"))\n\n\nif __name__ == \"__main__\":\n    log_path = os.path.join(\n        foc.FIFTYONE_CONFIG_DIR, \"var\", \"log\", \"server.log\"\n    )\n    etau.ensure_basedir(log_path)\n    # pylint: disable=no-member\n    app.logger.addHandler(logging.FileHandler(log_path, mode=\"w\"))\n\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--port\", type=int, default=5151)\n    args = parser.parse_args()\n\n    socketio.run(app, port=args.port, debug=foc.DEV_INSTALL)\n", "path": "fiftyone/server/main.py"}], "after_files": [{"content": "\"\"\"\nFiftyOne Flask server.\n\n| Copyright 2017-2020, Voxel51, Inc.\n| `voxel51.com <https://voxel51.com/>`_\n|\n\"\"\"\nimport argparse\nimport json\nimport logging\nimport os\nimport uuid\n\nfrom bson import json_util\nfrom flask import Flask, jsonify, request, send_file\nfrom flask_cors import CORS\nfrom flask_socketio import emit, Namespace, SocketIO\n\nimport eta.core.utils as etau\n\nos.environ[\"FIFTYONE_SERVER\"] = \"1\"\nimport fiftyone.constants as foc\nimport fiftyone.core.fields as fof\nimport fiftyone.core.odm as foo\nfrom fiftyone.core.service import DatabaseService\nfrom fiftyone.core.stages import _STAGES\nimport fiftyone.core.state as fos\n\nfrom util import get_image_size\nfrom pipelines import DISTRIBUTION_PIPELINES, LABELS, SCALARS\n\n\nlogger = logging.getLogger(__name__)\n\n# connect to the existing DB service to initialize global port information\ndb = DatabaseService()\ndb.start()\n\napp = Flask(__name__)\nCORS(app)\n\napp.config[\"SECRET_KEY\"] = \"fiftyone\"\n\nsocketio = SocketIO(app, async_mode=\"eventlet\", cors_allowed_origins=\"*\")\n\n\ndef get_user_id():\n    uid_path = os.path.join(foc.FIFTYONE_CONFIG_DIR, \"var\", \"uid\")\n\n    def read():\n        try:\n            with open(uid_path) as f:\n                return next(f).strip()\n        except (IOError, StopIteration):\n            return None\n\n    if not read():\n        os.makedirs(os.path.dirname(uid_path), exist_ok=True)\n        with open(uid_path, \"w\") as f:\n            f.write(str(uuid.uuid4()))\n    return read()\n\n\n@app.route(\"/\")\ndef get_sample_media():\n    \"\"\"Gets the sample media.\n\n    Returns:\n        bytes\n    \"\"\"\n    path = request.args.get(\"path\")\n    return send_file(path)\n\n\n@app.route(\"/fiftyone\")\ndef get_fiftyone_info():\n    return jsonify({\"version\": foc.VERSION})\n\n\n@app.route(\"/stages\")\ndef get_stages():\n    \"\"\"Gets ViewStage descriptions\"\"\"\n    return {\n        \"stages\": [\n            {\"name\": stage.__name__, \"params\": stage._params()}\n            for stage in _STAGES\n        ]\n    }\n\n\ndef _load_state(trigger_update=False):\n    def decorator(func):\n        def wrapper(self, *args, **kwargs):\n            state = fos.StateDescriptionWithDerivables.from_dict(self.state)\n            state = func(self, state, *args, **kwargs)\n            self.state = state.serialize()\n            emit(\n                \"update\",\n                self.state,\n                broadcast=True,\n                include_self=trigger_update,\n            )\n            return self.state\n\n        return wrapper\n\n    return decorator\n\n\nclass StateController(Namespace):\n    \"\"\"State controller.\n\n    Attributes:\n        state: a :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n               instance\n\n    Args:\n        **args: positional arguments for ``flask_socketio.Namespace``\n        **kwargs: keyword arguments for ``flask_socketio.Namespace``\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        self.state = fos.StateDescriptionWithDerivables().serialize()\n        super().__init__(*args, **kwargs)\n\n    def on_connect(self):\n        \"\"\"Handles connection to the server.\"\"\"\n        pass\n\n    def on_disconnect(self):\n        \"\"\"Handles disconnection from the server.\"\"\"\n        pass\n\n    def on_update(self, data):\n        \"\"\"Updates the state.\n\n        Args:\n            state_dict: a serialized\n                :class:`fiftyone.core.state.StateDescription`\n        \"\"\"\n        self.state = fos.StateDescriptionWithDerivables.from_dict(\n            data[\"data\"]\n        ).serialize()\n        emit(\n            \"update\",\n            self.state,\n            broadcast=True,\n            include_self=data[\"include_self\"],\n        )\n\n    def on_get_fiftyone_info(self):\n        \"\"\"Retrieves information about the FiftyOne installation.\"\"\"\n        return {\n            \"version\": foc.VERSION,\n            \"user_id\": get_user_id(),\n        }\n\n    def on_get_current_state(self, _):\n        \"\"\"Gets the current state.\n\n        Returns:\n            a :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n        \"\"\"\n        return self.state\n\n    @_load_state()\n    def on_add_selection(self, state, _id):\n        \"\"\"Adds a sample to the selected samples list.\n\n        Args:\n            state: the current\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n            _id: the sample ID\n\n        Returns:\n            the updated\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n        \"\"\"\n        selected = set(state.selected)\n        selected.add(_id)\n        state.selected = list(selected)\n        return state\n\n    @_load_state()\n    def on_remove_selection(self, state, _id):\n        \"\"\"Remove a sample from the selected samples list\n\n        Args:\n            state: the current\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n            _id: the sample ID\n\n        Returns:\n            the updated\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n        \"\"\"\n        selected = set(state.selected)\n        selected.remove(_id)\n        state.selected = list(selected)\n        return state\n\n    @_load_state()\n    def on_clear_selection(self, state):\n        \"\"\"Remove all samples from the selected samples list\n\n        Args:\n            state: the current\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n\n        Returns:\n            the updated\n                :class:`fiftyone.core.state.StateDescriptionWithDerivables`\n        \"\"\"\n        state.selected = []\n        return state\n\n    def on_page(self, page, page_length=20):\n        \"\"\"Gets the requested page of samples.\n\n        Args:\n            page: the page number\n            page_length: the page length\n\n        Returns:\n            the list of sample dicts for the page\n        \"\"\"\n        state = fos.StateDescriptionWithDerivables.from_dict(self.state)\n        if state.view is not None:\n            view = state.view\n        elif state.dataset is not None:\n            view = state.dataset.view()\n        else:\n            return []\n\n        view = view.skip((page - 1) * page_length).limit(page_length + 1)\n        samples = [\n            json.loads(\n                json_util.dumps(s.to_mongo_dict()), parse_constant=lambda c: c\n            )\n            for s in view\n        ]\n        more = False\n        if len(samples) > page_length:\n            samples = samples[:page_length]\n            more = page + 1\n\n        results = [{\"sample\": s} for s in samples]\n        for r in results:\n            w, h = get_image_size(r[\"sample\"][\"filepath\"])\n            r[\"width\"] = w\n            r[\"height\"] = h\n\n        return {\"results\": results, \"more\": more}\n\n    def on_get_distributions(self, group):\n        \"\"\"Gets the distributions for the current state with respect to a\n        group.\n\n        Args:\n            group: one of \"labels\", \"tags\", or \"scalars\"\n\n        Returns:\n            a list of distributions\n        \"\"\"\n        state = fos.StateDescriptionWithDerivables.from_dict(self.state)\n        if state.view is not None:\n            view = state.view\n        elif state.dataset is not None:\n            view = state.dataset.view()\n        else:\n            return []\n\n        return _get_distributions(view, group)\n\n\ndef _get_distributions(view, group):\n    pipeline = DISTRIBUTION_PIPELINES[group]\n\n    # we add a sub-pipeline for each numeric as it looks like multiple\n    # buckets in a single pipeline is not supported\n    if group == SCALARS:\n        _numeric_distribution_pipelines(view, pipeline)\n\n    result = list(view.aggregate(pipeline))\n\n    if group in {LABELS, SCALARS}:\n        new_result = []\n        for f in result[0].values():\n            new_result += f\n        result = new_result\n\n    if group != SCALARS:\n        for idx, dist in enumerate(result):\n            result[idx][\"data\"] = sorted(\n                result[idx][\"data\"], key=lambda c: c[\"count\"], reverse=True\n            )\n\n    return sorted(result, key=lambda d: d[\"name\"])\n\n\ndef _numeric_bounds(view, numerics):\n    bounds_pipeline = [{\"$facet\": {}}]\n    for idx, (k, v) in enumerate(numerics.items()):\n        bounds_pipeline[0][\"$facet\"][\"numeric-%d\" % idx] = [\n            {\n                \"$group\": {\n                    \"_id\": k,\n                    \"min\": {\"$min\": \"$%s\" % k},\n                    \"max\": {\"$max\": \"$%s\" % k},\n                },\n            }\n        ]\n\n    return list(view.aggregate(bounds_pipeline))[0] if len(numerics) else {}\n\n\ndef _numeric_distribution_pipelines(view, pipeline, buckets=50):\n    numerics = view._dataset.get_field_schema(ftype=fof.IntField)\n    numerics.update(view._dataset.get_field_schema(ftype=fof.FloatField))\n\n    # here we query the min and max for each numeric field\n    # unfortunately, it looks like this has to be a separate query\n    bounds = _numeric_bounds(view, numerics)\n\n    # for each numeric field, build the boundaries array with the\n    # min/max results when adding the field's sub-pipeline\n    for idx, (k, v) in enumerate(numerics.items()):\n        sub_pipeline = \"numeric-%d\" % idx\n        field_bounds = bounds[sub_pipeline][0]\n        mn = field_bounds[\"min\"]\n        mx = field_bounds[\"max\"]\n\n        # if min and max are equal, we artifically create a boundary\n        # @todo alternative approach to scalar fields with only one value\n        if mn == mx:\n            if mx > 0:\n                mn = 0\n            else:\n                mx = 0\n\n        step = (mx - mn) / buckets\n        boundaries = [mn + step * s for s in range(0, buckets)]\n\n        pipeline[0][\"$facet\"][sub_pipeline] = [\n            {\n                \"$bucket\": {\n                    \"groupBy\": \"$%s\" % k,\n                    \"boundaries\": boundaries,\n                    \"default\": \"null\",\n                    \"output\": {\"count\": {\"$sum\": 1}},\n                }\n            },\n            {\n                \"$group\": {\n                    \"_id\": k,\n                    \"data\": {\n                        \"$push\": {\n                            \"key\": {\n                                \"$cond\": [\n                                    {\"$ne\": [\"$_id\", \"null\"]},\n                                    {\"$add\": [\"$_id\", step / 2]},\n                                    \"null\",\n                                ]\n                            },\n                            \"count\": \"$count\",\n                        }\n                    },\n                }\n            },\n            {\n                \"$project\": {\n                    \"name\": k,\n                    \"type\": v.__class__.__name__[\n                        : -len(\"Field\")  # grab field type from the class\n                    ].lower(),\n                    \"data\": \"$data\",\n                }\n            },\n        ]\n\n\nsocketio.on_namespace(StateController(\"/state\"))\n\n\nif __name__ == \"__main__\":\n    log_path = os.path.join(\n        foc.FIFTYONE_CONFIG_DIR, \"var\", \"log\", \"server.log\"\n    )\n    etau.ensure_basedir(log_path)\n    # pylint: disable=no-member\n    app.logger.addHandler(logging.FileHandler(log_path, mode=\"w\"))\n\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--port\", type=int, default=5151)\n    args = parser.parse_args()\n\n    socketio.run(app, port=args.port, debug=foc.DEV_INSTALL)\n", "path": "fiftyone/server/main.py"}]}