From eb9ee8056d09e936b6d3e3e596a5df25a54a1e00 Mon Sep 17 00:00:00 2001 From: Stuart Wheaton Date: Tue, 9 Sep 2025 20:47:42 -0400 Subject: [PATCH 1/5] remove serverside JS usages --- fiftyone/core/expressions.py | 36 ++++++++++------------------- fiftyone/server/view.py | 45 +++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/fiftyone/core/expressions.py b/fiftyone/core/expressions.py index 3383231d1bb..6c7a040004d 100644 --- a/fiftyone/core/expressions.py +++ b/fiftyone/core/expressions.py @@ -15,6 +15,7 @@ import numpy as np import eta.core.utils as etau +import pymongo from fiftyone.core.odm.document import MongoEngineBaseDocument import fiftyone.core.utils as fou @@ -2723,33 +2724,20 @@ def sort(self, key=None, numeric=False, reverse=False): Returns: a :class:`ViewExpression` """ - if key is not None: - if numeric: - comp = "(a, b) => a.{key} - b.{key}" - else: - comp = "(a, b) => ('' + a.{key}).localeCompare(b.{key})" - - comp = comp.format(key=key) - elif numeric: - comp = "(a, b) => a - b" - else: - comp = "" + sort_order = pymongo.DESCENDING if reverse else pymongo.ASCENDING - if reverse: - rev = ".reverse()" + if key is not None: + sort_by = {key: sort_order} else: - rev = "" - - sort_fcn = """ - function(array) {{ - array.sort({comp}){rev}; - return array; - }} - """.format( - comp=comp, rev=rev - ) + sort_by = sort_order - return self._function(sort_fcn) + sort_stage = { + "$sortArray": { + "input": self, + "sortBy": sort_by, + } + } + return ViewExpression(sort_stage) def filter(self, expr): """Applies the given filter to the elements of this expression, which diff --git a/fiftyone/server/view.py b/fiftyone/server/view.py index 59534da8c67..5e2814b4050 100644 --- a/fiftyone/server/view.py +++ b/fiftyone/server/view.py @@ -947,19 +947,38 @@ def _add_label_tags(path, field, view): def _count_list_items(path, view): - function = ( - "function(items) {" - "let counts = {};" - "items && items.forEach((i) => {" - "counts[i] = 1 + (counts[i] || 0);" - "});" - "return counts;" - "}" - ) - - return view.set_field( - path, F(path)._function(function), _allow_missing=True - ) + expr = { + "$reduce": { + "input": F(path), # The field to be processed + "initialValue": {}, + "in": { + "$mergeObjects": [ + "$$value", + { + "$arrayToObject": [ + [ + { + "k": "$$this", + "v": { + "$add": [ + { + "$ifNull": [ + {"$getField": "$$this"}, + 0, + ] + }, + 1, + ] + }, + } + ] + ] + }, + ] + }, + } + } + return view.set_field(path, expr, _allow_missing=True) def _match_label_tags(view: foc.SampleCollection, label_tags): From 9119a27b1076e3a7278dcdc149ddb9e61f9fecc7 Mon Sep 17 00:00:00 2001 From: Stuart Wheaton Date: Tue, 9 Sep 2025 21:26:15 -0400 Subject: [PATCH 2/5] ViewExpression.sort has not worked in a long time, proposing deleting dead code --- fiftyone/core/expressions.py | 84 ------------------------------------ 1 file changed, 84 deletions(-) diff --git a/fiftyone/core/expressions.py b/fiftyone/core/expressions.py index 6c7a040004d..706682533c7 100644 --- a/fiftyone/core/expressions.py +++ b/fiftyone/core/expressions.py @@ -2663,82 +2663,6 @@ def reverse(self): """ return ViewExpression({"$reverseArray": self}) - def sort(self, key=None, numeric=False, reverse=False): - """Sorts this expression, which must resolve to an array. - - If no ``key`` is provided, this array must contain elements whose - BSON representation can be sorted by JavaScript's ``.sort()`` method. - - If a ``key`` is provided, the array must contain documents, which are - sorted by ``key``, which must be a field or embedded field. - - Examples:: - - # - # Sort the tags of each sample in a dataset - # - - import fiftyone as fo - from fiftyone import ViewField as F - - dataset = fo.Dataset() - dataset.add_samples( - [ - fo.Sample(filepath="im1.jpg", tags=["z", "f", "p", "a"]), - fo.Sample(filepath="im2.jpg", tags=["y", "q", "h", "d"]), - fo.Sample(filepath="im3.jpg", tags=["w", "c", "v", "l"]), - ] - ) - - # Sort the `tags` of each sample - view = dataset.set_field("tags", F("tags").sort()) - - print(view.first().tags) - - # - # Sort the predictions in each sample of a dataset by `confidence` - # - - import fiftyone as fo - import fiftyone.zoo as foz - from fiftyone import ViewField as F - - dataset = foz.load_zoo_dataset("quickstart") - - view = dataset.set_field( - "predictions.detections", - F("detections").sort(key="confidence", numeric=True, reverse=True) - ) - - sample = view.first() - print(sample.predictions.detections[0].confidence) - print(sample.predictions.detections[-1].confidence) - - Args: - key (None): an optional field or ``embedded.field.name`` to sort by - numeric (False): whether the array contains numeric values. By - default, the values will be sorted alphabetically by their - string representations - reverse (False): whether to sort in descending order - - Returns: - a :class:`ViewExpression` - """ - sort_order = pymongo.DESCENDING if reverse else pymongo.ASCENDING - - if key is not None: - sort_by = {key: sort_order} - else: - sort_by = sort_order - - sort_stage = { - "$sortArray": { - "input": self, - "sortBy": sort_by, - } - } - return ViewExpression(sort_stage) - def filter(self, expr): """Applies the given filter to the elements of this expression, which must resolve to an array. @@ -4563,14 +4487,6 @@ def zip(*args, use_longest=False, defaults=None): return ViewExpression({"$zip": zip_expr}) - # Experimental expressions ############################################### - - def _function(self, function): - function = " ".join(function.split()) - return ViewExpression( - {"$function": {"body": function, "args": [self], "lang": "js"}} - ) - class ViewField(ViewExpression): """A :class:`ViewExpression` that refers to a field or embedded field of a From 634a970c96c09f8ff09092ca2d9fac41161675cb Mon Sep 17 00:00:00 2001 From: Stuart Wheaton Date: Tue, 9 Sep 2025 22:43:17 -0400 Subject: [PATCH 3/5] fix --- fiftyone/server/view.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fiftyone/server/view.py b/fiftyone/server/view.py index 5e2814b4050..e5d26bd193c 100644 --- a/fiftyone/server/view.py +++ b/fiftyone/server/view.py @@ -963,7 +963,12 @@ def _count_list_items(path, view): "$add": [ { "$ifNull": [ - {"$getField": "$$this"}, + { + "$getField": { + "input": "$$value", + "field": "$$this", + } + }, 0, ] }, From a3743071dc6a4471b11d89cc081b657e67b9743c Mon Sep 17 00:00:00 2001 From: Stuart Wheaton Date: Tue, 9 Sep 2025 22:45:02 -0400 Subject: [PATCH 4/5] Revert "ViewExpression.sort has not worked in a long time, proposing deleting dead code" This reverts commit 9119a27b1076e3a7278dcdc149ddb9e61f9fecc7. --- fiftyone/core/expressions.py | 84 ++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/fiftyone/core/expressions.py b/fiftyone/core/expressions.py index 706682533c7..6c7a040004d 100644 --- a/fiftyone/core/expressions.py +++ b/fiftyone/core/expressions.py @@ -2663,6 +2663,82 @@ def reverse(self): """ return ViewExpression({"$reverseArray": self}) + def sort(self, key=None, numeric=False, reverse=False): + """Sorts this expression, which must resolve to an array. + + If no ``key`` is provided, this array must contain elements whose + BSON representation can be sorted by JavaScript's ``.sort()`` method. + + If a ``key`` is provided, the array must contain documents, which are + sorted by ``key``, which must be a field or embedded field. + + Examples:: + + # + # Sort the tags of each sample in a dataset + # + + import fiftyone as fo + from fiftyone import ViewField as F + + dataset = fo.Dataset() + dataset.add_samples( + [ + fo.Sample(filepath="im1.jpg", tags=["z", "f", "p", "a"]), + fo.Sample(filepath="im2.jpg", tags=["y", "q", "h", "d"]), + fo.Sample(filepath="im3.jpg", tags=["w", "c", "v", "l"]), + ] + ) + + # Sort the `tags` of each sample + view = dataset.set_field("tags", F("tags").sort()) + + print(view.first().tags) + + # + # Sort the predictions in each sample of a dataset by `confidence` + # + + import fiftyone as fo + import fiftyone.zoo as foz + from fiftyone import ViewField as F + + dataset = foz.load_zoo_dataset("quickstart") + + view = dataset.set_field( + "predictions.detections", + F("detections").sort(key="confidence", numeric=True, reverse=True) + ) + + sample = view.first() + print(sample.predictions.detections[0].confidence) + print(sample.predictions.detections[-1].confidence) + + Args: + key (None): an optional field or ``embedded.field.name`` to sort by + numeric (False): whether the array contains numeric values. By + default, the values will be sorted alphabetically by their + string representations + reverse (False): whether to sort in descending order + + Returns: + a :class:`ViewExpression` + """ + sort_order = pymongo.DESCENDING if reverse else pymongo.ASCENDING + + if key is not None: + sort_by = {key: sort_order} + else: + sort_by = sort_order + + sort_stage = { + "$sortArray": { + "input": self, + "sortBy": sort_by, + } + } + return ViewExpression(sort_stage) + def filter(self, expr): """Applies the given filter to the elements of this expression, which must resolve to an array. @@ -4487,6 +4563,14 @@ def zip(*args, use_longest=False, defaults=None): return ViewExpression({"$zip": zip_expr}) + # Experimental expressions ############################################### + + def _function(self, function): + function = " ".join(function.split()) + return ViewExpression( + {"$function": {"body": function, "args": [self], "lang": "js"}} + ) + class ViewField(ViewExpression): """A :class:`ViewExpression` that refers to a field or embedded field of a From 2c2a7bae69ecb7cd79fff1f847ba49f583b02e24 Mon Sep 17 00:00:00 2001 From: Stuart Wheaton Date: Wed, 10 Sep 2025 10:23:01 -0400 Subject: [PATCH 5/5] try new freq map expression that hopefully works in mongo6 --- fiftyone/server/view.py | 48 ++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/fiftyone/server/view.py b/fiftyone/server/view.py index e5d26bd193c..2cbdd9faabc 100644 --- a/fiftyone/server/view.py +++ b/fiftyone/server/view.py @@ -948,39 +948,23 @@ def _add_label_tags(path, field, view): def _count_list_items(path, view): expr = { - "$reduce": { - "input": F(path), # The field to be processed - "initialValue": {}, - "in": { - "$mergeObjects": [ - "$$value", - { - "$arrayToObject": [ - [ - { - "k": "$$this", - "v": { - "$add": [ - { - "$ifNull": [ - { - "$getField": { - "input": "$$value", - "field": "$$this", - } - }, - 0, - ] - }, - 1, - ] - }, - } - ] - ] + "$arrayToObject": { + "$map": { + "input": {"$setUnion": "$_label_tags"}, + "as": "tag", + "in": { + "k": "$$tag", + "v": { + "$size": { + "$filter": { + "input": "$_label_tags", + "as": "t", + "cond": {"$eq": ["$$t", "$$tag"]}, + } + } }, - ] - }, + }, + } } } return view.set_field(path, expr, _allow_missing=True)