Skip to content

FiftyOne Computer Vision Tips and Tricks – March 15, 2024

Welcome to our weekly FiftyOne tips and tricks blog where we recap interesting questions and answers that have recently popped up on Slack, GitHub, Stack Overflow, and Reddit.

As an open source community, the FiftyOne community is open to all. This means everyone is welcome to ask questions, and everyone is welcome to answer them. Continue reading to see the latest questions asked and answers provided!

Wait, what’s FiftyOne?

FiftyOne is an open source machine learning toolset that enables data science teams to improve the performance of their computer vision models by helping them curate high quality datasets, evaluate models, find mistakes, visualize embeddings, and get to production faster.

Ok, let’s dive into this week’s tips and tricks!

Running inference only on unprocessed samples

Community Slack member Patrick asked:

I am writing a script to run inference of an object detection model over a dataset that I am continuously adding samples to. For the sake of efficiency, I would like to only run inference on samples which have not been processed already. To do this, I would like to create a view of the dataset containing samples without pedicition_model_name. I have found an example of doing the inverse in the documentation.

# The subset of samples where predictions have been computed
predictions_view = dataset.exists("prediction_model_name")

Is anyone aware of a similarly clean method to achieve this?

Following your example, you could use dataset.exists("prediction_model_name", bool=False) to invert the filter.

Deleting selected annotations

Community Slack member george asked:

Has anyone created a plugin to delete the selected annotations?

delete_selected_labels is available as a builtin operator as of the 0.23.3 release. For those unfamiliar with operators, they allow you to define custom operations that accept parameters via input properties, execute some actions based on them, and optionally return outputs. They can be executed by users in the App or triggered internally by other operators.

Learn more about developing operators in the Docs.

Running multiple annotation jobs in CVAT

Community Slack member Yohal asked:

I’m trying to create multiple annotation jobs in CVAT. Ideally, I would like to have a single project with a single task and multiple jobs within it. I’m using the following code, but it gets a single project with multiple tasks sharing the same name, each having a single job. What am I missing?

    for some_id in sorted(ids):
        annotation_key = f"annotation_key_{some_id}"
        view = dataset.match(F("my_id").re_match(some_id))
        view.annotate(
            annotation_key,
            label_schema=get_lines_labeling_schema(),
            project_name=DEFAULT_CVAT_PROJECT_NAME,
            task_name=DEFAULT_CVAT_TASK_NAME,
        )

The current CVAT integration doesn’t support multiple annotation runs uploading jobs to the same taskbut there is a possible work around. In order to have multiple jobs in a single task, you can create a view in FiftyOne that contains all of the samples that you want to upload, and then call view.annotate(...) once on that view. You can then specify the segment_size to determine the number of samples in each job.

Importing FiftyOne without binding to a port

Community Slack member Anssi asked:

Is it possible to import Fiftyone without binding to a port? The system I am using doesn’t allow any binding to ports.

This is only possible with FiftyOne Teams, which gives you a more SaaS-like experience. Learn more about FiftyOne Teams’ collaboration, dataset versioning and security features.

Filtering and matching object classes

Community Slack member Tyler asked:

I have a dataset with detections representing three object classes. I want a filter/match to keep all objects of class one and two, then for class three, I want all of those objects that contain a field “keep_field”. Is there a convenient way to do this using the filter/match methods?

Here’s an example of how to achieve it.

TLDR

match =  (
    F("label").is_in(("class1", "class2"))| 
    (F("label").is_in("class3") & F("keep_field") == True))

matching_view = dataset.filter_labels("ground_truth",
   match
)

Full example:

import fiftyone as fo
import fiftyone.zoo as foz
from fiftyone import ViewField as F

dataset = foz.load_zoo_dataset("quickstart")

#match class 1 and class 2 OR match class 3 if it has keep_field

match =  (
    F("label").is_in(("cat", "dog"))| 
    (F("label").is_in("bird") & F("keep_field") == True))

matching_view = dataset.filter_labels("ground_truth",
   match
)

print(matching_view)

#To test class 3 in this example

#should go through
sample = dataset.first()
sample.ground_truth.detections[0]["keep_field"] = True
sample.save()

#should not go through
sample = dataset.last()
sample.ground_truth.detections[0]["keep_field"] = True
sample.save()

#rerun the filter
matching_view = dataset.filter_labels("ground_truth",
   match
)
print(matching_view)

#visualize the new filter
session = fo.launch_app(matching_view)

For additional filtering tips and tricks, check out the “Filtering Cheat Sheet” in the Docs.