tests.workflow_zero_shot_od_test

  1import logging
  2
  3import fiftyone as fo
  4import pytest
  5from fiftyone.utils.huggingface import load_from_hub
  6
  7import config.config
  8from main import (
  9    configure_logging,
 10    workflow_ensemble_selection,
 11    workflow_zero_shot_object_detection,
 12)
 13
 14
 15@pytest.fixture(autouse=True)
 16def deactivate_wandb_sync():
 17    config.config.WANDB_ACTIVE = False
 18
 19
 20@pytest.fixture(autouse=True)
 21def setup_logging():
 22    configure_logging()
 23
 24
 25@pytest.fixture
 26def dataset_v51():
 27    """Fixture to load a FiftyOne dataset from the hub."""
 28    dataset_name_hub = "Voxel51/fisheye8k"
 29    dataset_name = "fisheye8k_zero_shot_test"
 30    try:
 31        dataset = load_from_hub(
 32            repo_id=dataset_name_hub, max_samples=2, name=dataset_name
 33        )
 34    except:
 35        dataset = fo.load_dataset(dataset_name)
 36    dataset.persistent = True
 37    return dataset
 38
 39
 40def test_zero_shot_inference(dataset_v51):
 41    """Tests zero-shot object detection workflow by running inference on a dataset and validating detection outputs."""
 42
 43    config = {
 44        "n_post_processing_worker_per_inference_worker": 1,
 45        "n_worker_dataloader": 1,
 46        "prefetch_factor_dataloader": 1,
 47        "hf_models_zeroshot_objectdetection": {
 48            "omlab/omdet-turbo-swin-tiny-hf": {
 49                "batch_size": 2,  # Test 2
 50                "n_dataset_chunks": 1,
 51            },
 52            "IDEA-Research/grounding-dino-tiny": {
 53                "batch_size": 1,
 54                "n_dataset_chunks": 1,
 55            },
 56            "google/owlvit-large-patch14": {
 57                "batch_size": 1,
 58                "n_dataset_chunks": 1,
 59            },
 60            "google/owlv2-base-patch16-finetuned": {
 61                "batch_size": 1,
 62                "n_dataset_chunks": 2,  # Test 2
 63            },
 64            "google/owlv2-large-patch14-ensemble": {
 65                "batch_size": 1,
 66                "n_dataset_chunks": 1,
 67            },
 68        },
 69        "detection_threshold": 0.2,
 70        "object_classes": [
 71            "skater",
 72            "child",
 73            "bicycle",
 74            "bicyclist",
 75            "cyclist",
 76            "bike",
 77            "rider",
 78            "motorcycle",
 79            "motorcyclist",
 80            "pedestrian",
 81            "person",
 82            "walker",
 83            "jogger",
 84            "runner",
 85            "skateboarder",
 86            "scooter",
 87            "vehicle",
 88            "car",
 89            "bus",
 90            "truck",
 91            "taxi",
 92            "van",
 93            "pickup truck",
 94            "trailer",
 95            "emergency vehicle",
 96            "delivery driver",
 97        ],
 98    }
 99
100    dataset_info = {
101        "name": "fisheye8k_zero_shot_test",
102        "v51_type": "FiftyOneDataset",
103        "splits": [],
104    }
105
106    fields_of_interest_include = "pred_zsod_"
107    dataset_fields = dataset_v51.get_field_schema()
108    # Cleanup fields prior to run
109    for field in dataset_fields:
110        if fields_of_interest_include in field:
111            dataset_v51.delete_sample_field(field)
112            logging.info(f"Removed dataset field {field} prior to test.")
113
114    workflow_zero_shot_object_detection(dataset_v51, dataset_info, config)
115
116    # Get fields of dataset after run
117    dataset_fields = dataset_v51.get_field_schema()
118    logging.info(f"Dataset fields after run: {dataset_fields}")
119    fields_of_interest = [
120        field for field in dataset_fields if fields_of_interest_include in field
121    ]
122
123    logging.info(f"Zero Shot fields found: {fields_of_interest}")
124    assert (
125        len(fields_of_interest) > 0
126    ), f"No fields that include '{fields_of_interest_include}' found"
127
128    n_detections_found = dict.fromkeys(fields_of_interest, 0)
129
130    for sample in dataset_v51.iter_samples(progress=True, autosave=False):
131        for field in fields_of_interest:
132            detections = sample[field].detections
133            logging.info(detections)
134            n_detections_found[field] += len(detections)
135            for detection in detections:
136                # Test if detection label is in allowed classes
137                assert detection.label in config["object_classes"], (
138                    f"Detection label '{detection.label}' not in configured object classes: "
139                    f"{config['object_classes']}"
140                )
141
142                # Test bbox coordinates (x, y, width, height)
143                bbox = detection.bounding_box
144                assert (
145                    0 <= bbox[0] <= 1 and 0 <= bbox[1] <= 1
146                ), f"Bounding box coordinates (x={bbox[0]}, y={bbox[1]}) must be between 0 and 1"
147                assert (
148                    0 <= bbox[2] <= 1 and 0 <= bbox[3] <= 1
149                ), f"Bounding box dimensions (w={bbox[2]}, h={bbox[3]}) must be between 0 and 1"
150                assert (
151                    bbox[0] + bbox[2] <= 1
152                ), f"Bounding box extends beyond right image border: x({bbox[0]}) + width({bbox[2]}) > 1"
153                assert (
154                    bbox[1] + bbox[3] <= 1
155                ), f"Bounding box extends beyond bottom image border: y({bbox[1]}) + height({bbox[3]}) > 1"
156
157                # Test bbox area is reasonable (not too small or large)
158                bbox_area = bbox[2] * bbox[3]
159                assert (
160                    0 <= bbox_area <= 1
161                ), f"Bounding box area ({bbox_area}) is outside of range [0, 1]"
162
163    # Check if all models detected something at all
164    for field, n_detections in n_detections_found.items():
165        assert n_detections > 0, (
166            f"No detections found for model {field}. "
167            f"Expected at least 1 detection, got {n_detections}"
168        )
169        logging.info(f"Found {n_detections} detections for model {field}")
170
171
172def test_ensemble_selection(dataset_v51):
173    """Integration test for the workflow_ensemble_selection function that verifies detection tagging and unique instance counting."""
174
175    dataset_info = {
176        "name": "fisheye8k_zero_shot_test",
177        "v51_type": "FiftyOneDataset",
178        "splits": [],
179    }
180
181    run_config = {
182        "field_includes": "pred_zsod_",  # V51 field used for detections, "pred_zsod_" default for zero-shot object detection models
183        "agreement_threshold": 3,  # Threshold for n models necessary for agreement between models
184        "iou_threshold": 0.5,  # Threshold for IoU between bboxes to consider them as overlapping
185        "max_bbox_size": 0.01,  # Value between [0,1] for the max size of considered bboxes
186        "positive_classes": [  # Classes to consider, must be subset of available classes in the detections. Example for Vulnerable Road Users.
187            "skater",
188            "child",
189            "bicycle",
190            "bicyclist",
191            "cyclist",
192            "bike",
193            "rider",
194            "motorcycle",
195            "motorcyclist",
196            "pedestrian",
197            "person",
198            "walker",
199            "jogger",
200            "runner",
201            "skateboarder",
202            "scooter",
203            "delivery driver",
204        ],
205    }
206
207    workflow_ensemble_selection(
208        dataset_v51, dataset_info, run_config, wandb_activate=False
209    )
210
211    fields_of_interest_include = "pred_zsod_"
212    dataset_fields = dataset_v51.get_field_schema()
213    fields_of_interest = [
214        field for field in dataset_fields if fields_of_interest_include in field
215    ]
216
217    n_unique_field = "n_unique_ensemble_selection"
218    for sample in dataset_v51.iter_samples(progress=True, autosave=False):
219        # Check if field was populated with unique instances
220        n_unique = sample[n_unique_field]
221        assert n_unique > 0, f"No unique instances found in sample {sample}"
222        n_samples_with_tags = 0
223        # Check if detections were tagged
224        for field in fields_of_interest:
225            detections = sample[field].detections
226            for detection in detections:
227                tags = detection.tags
228                if len(tags) > 0:
229                    n_samples_with_tags += 1
230        assert n_samples_with_tags > 0, "No samples with tags detected"
@pytest.fixture(autouse=True)
def deactivate_wandb_sync():
16@pytest.fixture(autouse=True)
17def deactivate_wandb_sync():
18    config.config.WANDB_ACTIVE = False
@pytest.fixture(autouse=True)
def setup_logging():
21@pytest.fixture(autouse=True)
22def setup_logging():
23    configure_logging()
@pytest.fixture
def dataset_v51():
26@pytest.fixture
27def dataset_v51():
28    """Fixture to load a FiftyOne dataset from the hub."""
29    dataset_name_hub = "Voxel51/fisheye8k"
30    dataset_name = "fisheye8k_zero_shot_test"
31    try:
32        dataset = load_from_hub(
33            repo_id=dataset_name_hub, max_samples=2, name=dataset_name
34        )
35    except:
36        dataset = fo.load_dataset(dataset_name)
37    dataset.persistent = True
38    return dataset

Fixture to load a FiftyOne dataset from the hub.

def test_zero_shot_inference(dataset_v51):
 41def test_zero_shot_inference(dataset_v51):
 42    """Tests zero-shot object detection workflow by running inference on a dataset and validating detection outputs."""
 43
 44    config = {
 45        "n_post_processing_worker_per_inference_worker": 1,
 46        "n_worker_dataloader": 1,
 47        "prefetch_factor_dataloader": 1,
 48        "hf_models_zeroshot_objectdetection": {
 49            "omlab/omdet-turbo-swin-tiny-hf": {
 50                "batch_size": 2,  # Test 2
 51                "n_dataset_chunks": 1,
 52            },
 53            "IDEA-Research/grounding-dino-tiny": {
 54                "batch_size": 1,
 55                "n_dataset_chunks": 1,
 56            },
 57            "google/owlvit-large-patch14": {
 58                "batch_size": 1,
 59                "n_dataset_chunks": 1,
 60            },
 61            "google/owlv2-base-patch16-finetuned": {
 62                "batch_size": 1,
 63                "n_dataset_chunks": 2,  # Test 2
 64            },
 65            "google/owlv2-large-patch14-ensemble": {
 66                "batch_size": 1,
 67                "n_dataset_chunks": 1,
 68            },
 69        },
 70        "detection_threshold": 0.2,
 71        "object_classes": [
 72            "skater",
 73            "child",
 74            "bicycle",
 75            "bicyclist",
 76            "cyclist",
 77            "bike",
 78            "rider",
 79            "motorcycle",
 80            "motorcyclist",
 81            "pedestrian",
 82            "person",
 83            "walker",
 84            "jogger",
 85            "runner",
 86            "skateboarder",
 87            "scooter",
 88            "vehicle",
 89            "car",
 90            "bus",
 91            "truck",
 92            "taxi",
 93            "van",
 94            "pickup truck",
 95            "trailer",
 96            "emergency vehicle",
 97            "delivery driver",
 98        ],
 99    }
100
101    dataset_info = {
102        "name": "fisheye8k_zero_shot_test",
103        "v51_type": "FiftyOneDataset",
104        "splits": [],
105    }
106
107    fields_of_interest_include = "pred_zsod_"
108    dataset_fields = dataset_v51.get_field_schema()
109    # Cleanup fields prior to run
110    for field in dataset_fields:
111        if fields_of_interest_include in field:
112            dataset_v51.delete_sample_field(field)
113            logging.info(f"Removed dataset field {field} prior to test.")
114
115    workflow_zero_shot_object_detection(dataset_v51, dataset_info, config)
116
117    # Get fields of dataset after run
118    dataset_fields = dataset_v51.get_field_schema()
119    logging.info(f"Dataset fields after run: {dataset_fields}")
120    fields_of_interest = [
121        field for field in dataset_fields if fields_of_interest_include in field
122    ]
123
124    logging.info(f"Zero Shot fields found: {fields_of_interest}")
125    assert (
126        len(fields_of_interest) > 0
127    ), f"No fields that include '{fields_of_interest_include}' found"
128
129    n_detections_found = dict.fromkeys(fields_of_interest, 0)
130
131    for sample in dataset_v51.iter_samples(progress=True, autosave=False):
132        for field in fields_of_interest:
133            detections = sample[field].detections
134            logging.info(detections)
135            n_detections_found[field] += len(detections)
136            for detection in detections:
137                # Test if detection label is in allowed classes
138                assert detection.label in config["object_classes"], (
139                    f"Detection label '{detection.label}' not in configured object classes: "
140                    f"{config['object_classes']}"
141                )
142
143                # Test bbox coordinates (x, y, width, height)
144                bbox = detection.bounding_box
145                assert (
146                    0 <= bbox[0] <= 1 and 0 <= bbox[1] <= 1
147                ), f"Bounding box coordinates (x={bbox[0]}, y={bbox[1]}) must be between 0 and 1"
148                assert (
149                    0 <= bbox[2] <= 1 and 0 <= bbox[3] <= 1
150                ), f"Bounding box dimensions (w={bbox[2]}, h={bbox[3]}) must be between 0 and 1"
151                assert (
152                    bbox[0] + bbox[2] <= 1
153                ), f"Bounding box extends beyond right image border: x({bbox[0]}) + width({bbox[2]}) > 1"
154                assert (
155                    bbox[1] + bbox[3] <= 1
156                ), f"Bounding box extends beyond bottom image border: y({bbox[1]}) + height({bbox[3]}) > 1"
157
158                # Test bbox area is reasonable (not too small or large)
159                bbox_area = bbox[2] * bbox[3]
160                assert (
161                    0 <= bbox_area <= 1
162                ), f"Bounding box area ({bbox_area}) is outside of range [0, 1]"
163
164    # Check if all models detected something at all
165    for field, n_detections in n_detections_found.items():
166        assert n_detections > 0, (
167            f"No detections found for model {field}. "
168            f"Expected at least 1 detection, got {n_detections}"
169        )
170        logging.info(f"Found {n_detections} detections for model {field}")

Tests zero-shot object detection workflow by running inference on a dataset and validating detection outputs.

def test_ensemble_selection(dataset_v51):
173def test_ensemble_selection(dataset_v51):
174    """Integration test for the workflow_ensemble_selection function that verifies detection tagging and unique instance counting."""
175
176    dataset_info = {
177        "name": "fisheye8k_zero_shot_test",
178        "v51_type": "FiftyOneDataset",
179        "splits": [],
180    }
181
182    run_config = {
183        "field_includes": "pred_zsod_",  # V51 field used for detections, "pred_zsod_" default for zero-shot object detection models
184        "agreement_threshold": 3,  # Threshold for n models necessary for agreement between models
185        "iou_threshold": 0.5,  # Threshold for IoU between bboxes to consider them as overlapping
186        "max_bbox_size": 0.01,  # Value between [0,1] for the max size of considered bboxes
187        "positive_classes": [  # Classes to consider, must be subset of available classes in the detections. Example for Vulnerable Road Users.
188            "skater",
189            "child",
190            "bicycle",
191            "bicyclist",
192            "cyclist",
193            "bike",
194            "rider",
195            "motorcycle",
196            "motorcyclist",
197            "pedestrian",
198            "person",
199            "walker",
200            "jogger",
201            "runner",
202            "skateboarder",
203            "scooter",
204            "delivery driver",
205        ],
206    }
207
208    workflow_ensemble_selection(
209        dataset_v51, dataset_info, run_config, wandb_activate=False
210    )
211
212    fields_of_interest_include = "pred_zsod_"
213    dataset_fields = dataset_v51.get_field_schema()
214    fields_of_interest = [
215        field for field in dataset_fields if fields_of_interest_include in field
216    ]
217
218    n_unique_field = "n_unique_ensemble_selection"
219    for sample in dataset_v51.iter_samples(progress=True, autosave=False):
220        # Check if field was populated with unique instances
221        n_unique = sample[n_unique_field]
222        assert n_unique > 0, f"No unique instances found in sample {sample}"
223        n_samples_with_tags = 0
224        # Check if detections were tagged
225        for field in fields_of_interest:
226            detections = sample[field].detections
227            for detection in detections:
228                tags = detection.tags
229                if len(tags) > 0:
230                    n_samples_with_tags += 1
231        assert n_samples_with_tags > 0, "No samples with tags detected"

Integration test for the workflow_ensemble_selection function that verifies detection tagging and unique instance counting.