Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for selecting/excluding group slices #5198

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading