Skip to content

Commit

Permalink
add support for selecting/excluding group slices
Browse files Browse the repository at this point in the history
  • Loading branch information
brimoor committed Dec 21, 2024
1 parent b5a1aab commit 9b8c693
Show file tree
Hide file tree
Showing 7 changed files with 576 additions and 70 deletions.
61 changes: 45 additions & 16 deletions docs/source/user_guide/groups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -815,8 +815,8 @@ Selecting slices

You can use
:meth:`select_group_slices() <fiftyone.core.collections.SampleCollection.select_group_slices>`
to create *non-grouped views* that contain one or more slices of data from a
grouped dataset.
to select one or more slices of data from a grouped dataset, either as a
grouped view or as a flattened *non-grouped* view.

For example, you can create an image view that contains only the left camera
images from the grouped dataset:
Expand All @@ -843,7 +843,7 @@ images from the grouped dataset:
View stages:
1. SelectGroupSlices(slices='left')
or you could create an image collection containing the left and right camera
or you can create an image collection containing the left and right camera
images:

.. code-block:: python
Expand Down Expand Up @@ -882,30 +882,59 @@ the fact that their data is sourced from a grouped dataset!
# Add fields/tags, run evaluation, export, etc
Also note that any filtering that you apply prior to a
.. note::

Any filtering that you apply prior to a
:meth:`select_group_slices() <fiftyone.core.collections.SampleCollection.select_group_slices>`
stage in a view is **not** automatically reflected by the output view, as
the stage looks up unfiltered slice data from the source collection:

.. code-block:: python
# Filter the active slice to locate groups of interest
match_view = dataset.filter_labels(...).match(...)
# Lookup all image slices for the matching groups
# This view contains *unfiltered* image slices
images_view = match_view.select_group_slices(media_type="image")
Instead, you can apply the same (or different) filtering *after* the
:meth:`select_group_slices() <fiftyone.core.collections.SampleCollection.select_group_slices>`
stage:

.. code-block:: python
# Now apply filters to the flattened collection
match_images_view = images_view.filter_labels(...).match(...)
Alternatively, you can pass `flat=False` to
:meth:`select_group_slices() <fiftyone.core.collections.SampleCollection.select_group_slices>`
stage in a view is **not** automatically reflected by the output view, as the
stage looks up unfiltered slice data from the source collection:
to create a grouped view that only contains certain group slices:

.. code-block:: python
:linenos:
# Filter the active slice to locate groups of interest
match_view = dataset.filter_labels(...).match(...)
no_center_view = dataset.select_group_slices(["left", "right"], flat=False)
# Lookup all image slices for the matching groups
# This view contains *unfiltered* image slices
images_view = match_view.select_group_slices(media_type="image")
assert no_center_view.media_type == "group"
assert no_center_view.group_slices == ["left", "right"]
Instead, you can apply the same (or different) filtering *after* the
:meth:`select_group_slices() <fiftyone.core.collections.SampleCollection.select_group_slices>`
stage:
.. _groups-excluding-slices:

Excluding slices
----------------

You can use
:meth:`exclude_group_slices() <fiftyone.core.collections.SampleCollection.exclude_group_slices>`
to create a grouped view that excludes certain slice(s) of a grouped dataset:

.. code-block:: python
:linenos:
# Now apply filters to the flattened collection
match_images_view = images_view.filter_labels(...).match(...)
no_center_view = dataset.exclude_group_slices("center")
assert no_center_view.media_type == "group"
assert no_center_view.group_slices == ["left", "right"]
.. _groups-aggregations:

Expand Down
1 change: 1 addition & 0 deletions fiftyone/__public__.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
ExcludeFields,
ExcludeFrames,
ExcludeGroups,
ExcludeGroupSlices,
ExcludeLabels,
Exists,
FilterField,
Expand Down
116 changes: 102 additions & 14 deletions fiftyone/core/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4618,6 +4618,80 @@ def exclude_groups(self, group_ids):
"""
return self._add_view_stage(fos.ExcludeGroups(group_ids))

@view_stage
def exclude_group_slices(self, slices=None, media_type=None):
"""Excludes the specified group slice(s) from the grouped collection.
Examples::
import fiftyone as fo
dataset = fo.Dataset()
dataset.add_group_field("group", default="ego")
group1 = fo.Group()
group2 = fo.Group()
dataset.add_samples(
[
fo.Sample(
filepath="/path/to/left-image1.jpg",
group=group1.element("left"),
),
fo.Sample(
filepath="/path/to/video1.mp4",
group=group1.element("ego"),
),
fo.Sample(
filepath="/path/to/right-image1.jpg",
group=group1.element("right"),
),
fo.Sample(
filepath="/path/to/left-image2.jpg",
group=group2.element("left"),
),
fo.Sample(
filepath="/path/to/video2.mp4",
group=group2.element("ego"),
),
fo.Sample(
filepath="/path/to/right-image2.jpg",
group=group2.element("right"),
),
]
)
#
# Exclude the samples from the "ego" group slice
#
view = dataset.exclude_group_slices("ego")
#
# Exclude the samples from the "left" or "right" group slices
#
view = dataset.exclude_group_slices(["left", "right"])
#
# Exclude all image slices
#
view = dataset.exclude_group_slices(media_type="image")
Args:
slices (None): a group slice or iterable of group slices to
exclude
media_type (None): a media type or iterable of media types whose
slice(s) to exclude
Returns:
a :class:`fiftyone.core.view.DatasetView`
"""
return self._add_view_stage(
fos.ExcludeGroupSlices(slices=slices, media_type=media_type)
)

@view_stage
def exclude_labels(
self, labels=None, ids=None, tags=None, fields=None, omit_empty=True
Expand Down Expand Up @@ -6594,19 +6668,24 @@ def select_group_slices(
self,
slices=None,
media_type=None,
flat=True,
_allow_mixed=False,
_force_mixed=False,
):
"""Selects the samples in the group collection from the given slice(s).
"""Selects the specified group slice(s) from the grouped collection.
When ``flat==True``, the returned view is a flattened non-grouped view
containing the samples from the slice(s) of interest.
The returned view is a flattened non-grouped view containing only the
slice(s) of interest.
When ``flat=False``, the returned view is a grouped collection
containing only the slice(s) of interest.
.. note::
This stage performs a ``$lookup`` that pulls the requested slice(s)
for each sample in the input collection from the source dataset.
As a result, this stage always emits *unfiltered samples*.
When ``flat=True``, this stage performs a ``$lookup`` that pulls
the requested slice(s) for each sample in the input collection from
the source dataset. As a result, the stage emits
*unfiltered samples*.
Examples::
Expand Down Expand Up @@ -6659,6 +6738,12 @@ def select_group_slices(
view = dataset.select_group_slices(["left", "right"])
#
# Select only the "left" and "right" group slices
#
view = dataset.select_group_slices(["left", "right"], flat=False)
#
# Retrieve all image samples
#
Expand All @@ -6669,7 +6754,10 @@ def select_group_slices(
slices (None): a group slice or iterable of group slices to select.
If neither argument is provided, a flattened list of all
samples is returned
media_type (None): a media type whose slice(s) to select
media_type (None): a media type or iterable of media types whose
slice(s) to select
flat (True): whether to return a flattened collection (True) or a
grouped collection (False)
Returns:
a :class:`fiftyone.core.view.DatasetView`
Expand All @@ -6678,6 +6766,7 @@ def select_group_slices(
fos.SelectGroupSlices(
slices=slices,
media_type=media_type,
flat=flat,
_allow_mixed=_allow_mixed,
_force_mixed=_force_mixed,
)
Expand Down Expand Up @@ -10542,24 +10631,23 @@ def _contains_media_type(self, media_type, any_slice=False):
return True

if self.media_type == fom.GROUP:
if self.group_media_types is None:
group_media_types = self.group_media_types
if group_media_types is None:
return self._dataset.media_type == media_type

if any_slice:
return any(
slice_media_type == media_type
for slice_media_type in self.group_media_types.values()
for slice_media_type in group_media_types.values()
)

return (
self.group_media_types.get(self.group_slice, None)
== media_type
)
return group_media_types.get(self.group_slice, None) == media_type

if self.media_type == fom.MIXED:
group_media_types = self._get_group_media_types()
return any(
slice_media_type == media_type
for slice_media_type in self._get_group_media_types().values()
for slice_media_type in group_media_types.values()
)

return False
Expand Down
11 changes: 6 additions & 5 deletions fiftyone/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -8484,14 +8484,16 @@ def _clone_collection(sample_collection, name, persistent):
slug = _validate_dataset_name(name)

contains_videos = sample_collection._contains_videos(any_slice=True)
contains_groups = sample_collection.media_type == fom.GROUP

if isinstance(sample_collection, fov.DatasetView):
dataset = sample_collection._dataset
view = sample_collection

if view.media_type == fom.MIXED:
raise ValueError("Cloning mixed views is not allowed")

if view._is_dynamic_groups:
raise ValueError("Cloning dynamic grouped views is not allowed")
else:
dataset = sample_collection
view = None
Expand Down Expand Up @@ -8525,10 +8527,9 @@ def _clone_collection(sample_collection, name, persistent):
dataset_doc.sample_collection_name = sample_collection_name
dataset_doc.frame_collection_name = frame_collection_name
dataset_doc.media_type = sample_collection.media_type
if not contains_groups:
dataset_doc.group_field = None
dataset_doc.group_media_types = {}
dataset_doc.default_group_slice = None
dataset_doc.group_field = sample_collection.group_field
dataset_doc.group_media_types = sample_collection.group_media_types
dataset_doc.default_group_slice = sample_collection.default_group_slice

for field in dataset_doc.sample_fields:
field._set_created_at(now)
Expand Down
Loading

0 comments on commit 9b8c693

Please sign in to comment.