88For every detection and segmentation model variant, this module:
99
10101. Loads pretrained weights via the :class:`~rfdetr.detr.RFDETR` wrapper.
11- 2. Verifies :meth:`~rfdetr.detr.RFDETR.predict` returns valid
12- :class:`supervision.Detections` objects.
13- 3. Copies the same weights into a fresh :class:`~rfdetr.training.RFDETRModule`.
14- 4. Evaluates via ``Trainer.validate`` and asserts mAP thresholds.
11+ 2. Copies the weights into a fresh :class:`~rfdetr.training.RFDETRModule`.
12+ 3. Evaluates via ``Trainer.validate`` and asserts mAP thresholds.
13+
14+ API contract tests (return type of ``predict()``) live in
15+ ``tests/models/test_predict.py`` and do not require a COCO download.
1516
1617Test functions:
1718
18- - :func:`test_inference_detection_rfdetr_predict` — ``RFDETR.predict()``
19- returns valid detections for detection models (Nano/Small/Medium/Large).
20- - :func:`test_inference_segmentation_rfdetr_predict` — ``RFDETR.predict()``
21- returns detections with masks for segmentation models (Nano through 2XLarge).
19+ - :func:`test_inference_detection_rfdetr_predict` — asserts mAP@50 for detection
20+ models (Nano/Small/Medium/Large).
21+ - :func:`test_inference_segmentation_rfdetr_predict` — asserts mAP@50 for
22+ segmentation models (Nano through 2XLarge).
2223- :func:`test_inference_detection_ptl_predict` — ``trainer.predict()`` exercises
2324 the PTL predict loop (50 samples) then asserts mAP via ``Trainer.validate``.
2425- :func:`test_inference_segmentation_ptl_predict` — same for segmentation models.
2930from typing import Optional
3031
3132import pytest
32- import supervision as sv
3333import torch
3434from pytorch_lightning import LightningModule
3535
@@ -152,35 +152,34 @@ def _build_ptl_module(rfdetr_obj: RFDETR, train_config: TrainConfig) -> RFDETRMo
152152
153153@pytest .mark .gpu
154154@pytest .mark .parametrize (
155- ("model_cls" , "threshold_map" , "num_samples" , "batch_size" ),
155+ ("model_cls" , "threshold_map" , "threshold_f1" , " num_samples" , "batch_size" ),
156156 [
157- pytest .param (RFDETRNano , 0.67 , 2000 , 6 , id = "det-nano" ),
158- pytest .param (RFDETRSmall , 0.72 , 500 , 6 , id = "det-small" ),
159- pytest .param (RFDETRMedium , 0.73 , 500 , 4 , id = "det-medium" ),
160- pytest .param (RFDETRLarge , 0.74 , 500 , 2 , id = "det-large" ),
157+ pytest .param (RFDETRNano , 0.66 , 0.66 , 2000 , 6 , id = "det-nano" ),
158+ pytest .param (RFDETRSmall , 0.72 , 0.70 , 500 , 6 , id = "det-small" ),
159+ pytest .param (RFDETRMedium , 0.73 , 0.71 , 500 , 4 , id = "det-medium" ),
160+ pytest .param (RFDETRLarge , 0.74 , 0.72 , 500 , 2 , id = "det-large" ),
161161 ],
162162)
163163def test_inference_detection_rfdetr_predict (
164164 tmp_path : Path ,
165165 download_coco_val : tuple [Path , Path ],
166166 model_cls : type [RFDETR ],
167167 threshold_map : float ,
168+ threshold_f1 : float ,
168169 num_samples : int ,
169170 batch_size : int ,
170171) -> None :
171172 """``RFDETR.predict()`` returns valid ``sv.Detections`` for detection models.
172173
173174 Loads a pretrained detection model, runs ``predict()`` on a sample of COCO
174- val images, and asserts:
175-
176- - Return type is a list of :class:`supervision.Detections`.
177- - ``Trainer.validate`` on the same weights meets the mAP threshold.
175+ val images, and asserts ``Trainer.validate`` meets the mAP and F1 thresholds.
178176
179177 Args:
180178 tmp_path: Pytest-provided temporary directory.
181179 download_coco_val: Fixture providing ``(images_root, annotations_path)``.
182180 model_cls: Detection model class to instantiate with pretrained weights.
183181 threshold_map: Minimum ``val/mAP_50`` required.
182+ threshold_f1: Minimum ``val/F1`` (best macro-F1 across confidence sweep) required.
184183 num_samples: Number of val images used for ``Trainer.validate``.
185184 batch_size: DataLoader batch size for ``Trainer.validate``.
186185 """
@@ -190,56 +189,51 @@ def test_inference_detection_rfdetr_predict(
190189
191190 rfdetr = model_cls (device = device_str )
192191
193- # Verify RFDETR.predict() API returns sv.Detections.
194- # predict() accepts str paths or PIL Images, not pathlib.Path objects.
195- sample_images = [str (p ) for p in sorted (images_root .glob ("*.jpg" ))[:4 ]]
196- assert sample_images , "No COCO val images found."
197- detections = rfdetr .predict (sample_images , threshold = 0.3 )
198- assert isinstance (detections , list ), "predict() should return a list for multiple images"
199- assert all (isinstance (d , sv .Detections ) for d in detections ), "Each result must be sv.Detections"
200-
201- # Verify mAP via Trainer.validate on the same pretrained weights.
192+ # Verify mAP and F1 via Trainer.validate on the pretrained weights.
202193 tc = _build_train_config (coco_root , tmp_path , batch_size )
203194 dm = _build_datamodule (rfdetr .model_config , tc , num_samples = num_samples )
204195 module = _build_ptl_module (rfdetr , tc )
205196 accelerator = "auto" if torch .cuda .is_available () else "cpu"
206197 trainer = build_trainer (tc , rfdetr .model_config , accelerator = accelerator )
207- results = trainer .validate (module , datamodule = dm )
208- map_val = results [0 ]["val/mAP_50" ]
198+ (metrics ,) = trainer .validate (module , datamodule = dm )
199+ map_val = metrics ["val/mAP_50" ]
200+ f1_val = metrics ["val/F1" ]
209201 assert map_val >= threshold_map , f"mAP@50 { map_val :.4f} < { threshold_map } "
202+ assert f1_val >= threshold_f1 , f"F1 { f1_val :.4f} < { threshold_f1 } "
210203
211204
212205@pytest .mark .gpu
213206@pytest .mark .parametrize (
214- ("model_cls" , "threshold_map" , "num_samples" , "batch_size" ),
207+ ("model_cls" , "threshold_map" , "threshold_f1" , " num_samples" , "batch_size" ),
215208 [
216- pytest .param (RFDETRSegNano , 0.63 , 500 , 6 , id = "seg-nano" ),
217- pytest .param (RFDETRSegSmall , 0.66 , 100 , 6 , id = "seg-small" ),
218- pytest .param (RFDETRSegMedium , 0.68 , 100 , 4 , id = "seg-medium" ),
219- pytest .param (RFDETRSegLarge , 0.70 , 100 , 2 , id = "seg-large" ),
220- pytest .param (RFDETRSegXLarge , 0.72 , 100 , 2 , id = "seg-xlarge" ),
221- pytest .param (RFDETRSeg2XLarge , 0.73 , 100 , 2 , id = "seg-2xlarge" ),
209+ pytest .param (RFDETRSegNano , 0.63 , 0.64 , 500 , 6 , id = "seg-nano" ),
210+ pytest .param (RFDETRSegSmall , 0.66 , 0.67 , 100 , 6 , id = "seg-small" ),
211+ pytest .param (RFDETRSegMedium , 0.68 , 0.68 , 100 , 4 , id = "seg-medium" ),
212+ pytest .param (RFDETRSegLarge , 0.70 , 0.69 , 100 , 2 , id = "seg-large" ),
213+ pytest .param (RFDETRSegXLarge , 0.72 , 0.70 , 100 , 2 , id = "seg-xlarge" ),
214+ pytest .param (RFDETRSeg2XLarge , 0.73 , 0.71 , 100 , 2 , id = "seg-2xlarge" ),
222215 ],
223216)
224217def test_inference_segmentation_rfdetr_predict (
225218 tmp_path : Path ,
226219 download_coco_val : tuple [Path , Path ],
227220 model_cls : type [RFDETR ],
228221 threshold_map : float ,
222+ threshold_f1 : float ,
229223 num_samples : int ,
230224 batch_size : int ,
231225) -> None :
232- """``RFDETR.predict()`` returns valid ``sv.Detections`` with masks for segmentation models.
226+ """Asserts mAP and F1 thresholds for segmentation models via ``Trainer.validate`` .
233227
234228 Same structure as :func:`test_inference_detection_rfdetr_predict` but for
235- segmentation variants. Also asserts that the returned detections contain a
236- non-``None`` ``mask`` field.
229+ segmentation variants.
237230
238231 Args:
239232 tmp_path: Pytest-provided temporary directory.
240233 download_coco_val: Fixture providing ``(images_root, annotations_path)``.
241234 model_cls: Segmentation model class to instantiate with pretrained weights.
242235 threshold_map: Minimum ``val/mAP_50`` (bbox) required.
236+ threshold_f1: Minimum ``val/F1`` (best macro-F1 across confidence sweep) required.
243237 num_samples: Number of val images used for ``Trainer.validate``.
244238 batch_size: DataLoader batch size for ``Trainer.validate``.
245239 """
@@ -249,26 +243,17 @@ def test_inference_segmentation_rfdetr_predict(
249243
250244 rfdetr = model_cls (device = device_str )
251245
252- # Verify RFDETR.predict() returns sv.Detections with masks.
253- # predict() accepts str paths or PIL Images, not pathlib.Path objects.
254- sample_images = [str (p ) for p in sorted (images_root .glob ("*.jpg" ))[:4 ]]
255- assert sample_images , "No COCO val images found."
256- detections = rfdetr .predict (sample_images , threshold = 0.3 )
257- assert isinstance (detections , list ), "predict() should return a list for multiple images"
258- assert all (isinstance (d , sv .Detections ) for d in detections ), "Each result must be sv.Detections"
259- assert any (d .mask is not None for d in detections if len (d ) > 0 ), (
260- "Segmentation model predict() should return detections with masks"
261- )
262-
263- # Verify mAP via Trainer.validate on the same pretrained weights.
246+ # Verify mAP and F1 via Trainer.validate on the pretrained weights.
264247 tc = _build_train_config (coco_root , tmp_path , batch_size )
265248 dm = _build_datamodule (rfdetr .model_config , tc , num_samples = num_samples )
266249 module = _build_ptl_module (rfdetr , tc )
267250 accelerator = "auto" if torch .cuda .is_available () else "cpu"
268251 trainer = build_trainer (tc , rfdetr .model_config , accelerator = accelerator )
269- results = trainer .validate (module , datamodule = dm )
270- map_val = results [0 ]["val/mAP_50" ]
252+ (metrics ,) = trainer .validate (module , datamodule = dm )
253+ map_val = metrics ["val/mAP_50" ]
254+ f1_val = metrics ["val/F1" ]
271255 assert map_val >= threshold_map , f"mAP@50 { map_val :.4f} < { threshold_map } "
256+ assert f1_val >= threshold_f1 , f"F1 { f1_val :.4f} < { threshold_f1 } "
272257
273258
274259# ---------------------------------------------------------------------------
@@ -278,19 +263,20 @@ def test_inference_segmentation_rfdetr_predict(
278263
279264@pytest .mark .gpu
280265@pytest .mark .parametrize (
281- ("model_cls" , "threshold_map" , "num_samples" , "batch_size" ),
266+ ("model_cls" , "threshold_map" , "threshold_f1" , " num_samples" , "batch_size" ),
282267 [
283- pytest .param (RFDETRNano , 0.67 , 2000 , 6 , id = "det-nano" ),
284- pytest .param (RFDETRSmall , 0.72 , 500 , 6 , id = "det-small" ),
285- pytest .param (RFDETRMedium , 0.73 , 500 , 4 , id = "det-medium" ),
286- pytest .param (RFDETRLarge , 0.74 , 500 , 2 , id = "det-large" ),
268+ pytest .param (RFDETRNano , 0.66 , 0.66 , 2000 , 6 , id = "det-nano" ),
269+ pytest .param (RFDETRSmall , 0.72 , 0.70 , 500 , 6 , id = "det-small" ),
270+ pytest .param (RFDETRMedium , 0.73 , 0.71 , 500 , 4 , id = "det-medium" ),
271+ pytest .param (RFDETRLarge , 0.74 , 0.72 , 500 , 2 , id = "det-large" ),
287272 ],
288273)
289274def test_inference_detection_ptl_predict (
290275 tmp_path : Path ,
291276 download_coco_val : tuple [Path , Path ],
292277 model_cls : type [RFDETR ],
293278 threshold_map : float ,
279+ threshold_f1 : float ,
294280 num_samples : int ,
295281 batch_size : int ,
296282) -> None :
@@ -299,13 +285,14 @@ def test_inference_detection_ptl_predict(
299285 Loads a pretrained detection model, copies weights into a
300286 :class:`~rfdetr.training.RFDETRModule`, runs ``trainer.predict()`` on a
301287 small subset (50 samples) to exercise :meth:`~rfdetr.training.RFDETRModule.predict_step`,
302- then runs ``Trainer.validate`` on the full *num_samples* to assert mAP.
288+ then runs ``Trainer.validate`` on the full *num_samples* to assert mAP and F1 .
303289
304290 Args:
305291 tmp_path: Pytest-provided temporary directory.
306292 download_coco_val: Fixture providing ``(images_root, annotations_path)``.
307293 model_cls: Detection model class to instantiate with pretrained weights.
308294 threshold_map: Minimum ``val/mAP_50`` required.
295+ threshold_f1: Minimum ``val/F1`` (best macro-F1 across confidence sweep) required.
309296 num_samples: Number of val samples used for ``Trainer.validate``.
310297 batch_size: DataLoader batch size.
311298 """
@@ -325,30 +312,33 @@ def test_inference_detection_ptl_predict(
325312 assert predictions is not None , "trainer.predict() returned None"
326313 assert len (predictions ) > 0 , "trainer.predict() returned empty list"
327314
328- # Verify mAP via Trainer.validate on the full num_samples.
315+ # Verify mAP and F1 via Trainer.validate on the full num_samples.
329316 val_dm = _build_datamodule (rfdetr .model_config , tc , num_samples = num_samples )
330- results = trainer .validate (module , datamodule = val_dm )
331- map_val = results [0 ]["val/mAP_50" ]
317+ (metrics ,) = trainer .validate (module , datamodule = val_dm )
318+ map_val = metrics ["val/mAP_50" ]
319+ f1_val = metrics ["val/F1" ]
332320 assert map_val >= threshold_map , f"mAP@50 { map_val :.4f} < { threshold_map } "
321+ assert f1_val >= threshold_f1 , f"F1 { f1_val :.4f} < { threshold_f1 } "
333322
334323
335324@pytest .mark .gpu
336325@pytest .mark .parametrize (
337- ("model_cls" , "threshold_map" , "num_samples" , "batch_size" ),
326+ ("model_cls" , "threshold_map" , "threshold_f1" , " num_samples" , "batch_size" ),
338327 [
339- pytest .param (RFDETRSegNano , 0.63 , 500 , 6 , id = "seg-nano" ),
340- pytest .param (RFDETRSegSmall , 0.66 , 100 , 6 , id = "seg-small" ),
341- pytest .param (RFDETRSegMedium , 0.68 , 100 , 4 , id = "seg-medium" ),
342- pytest .param (RFDETRSegLarge , 0.70 , 100 , 2 , id = "seg-large" ),
343- pytest .param (RFDETRSegXLarge , 0.72 , 100 , 2 , id = "seg-xlarge" ),
344- pytest .param (RFDETRSeg2XLarge , 0.73 , 100 , 2 , id = "seg-2xlarge" ),
328+ pytest .param (RFDETRSegNano , 0.63 , 0.64 , 500 , 6 , id = "seg-nano" ),
329+ pytest .param (RFDETRSegSmall , 0.66 , 0.67 , 100 , 6 , id = "seg-small" ),
330+ pytest .param (RFDETRSegMedium , 0.68 , 0.68 , 100 , 4 , id = "seg-medium" ),
331+ pytest .param (RFDETRSegLarge , 0.70 , 0.69 , 100 , 2 , id = "seg-large" ),
332+ pytest .param (RFDETRSegXLarge , 0.72 , 0.70 , 100 , 2 , id = "seg-xlarge" ),
333+ pytest .param (RFDETRSeg2XLarge , 0.73 , 0.71 , 100 , 2 , id = "seg-2xlarge" ),
345334 ],
346335)
347336def test_inference_segmentation_ptl_predict (
348337 tmp_path : Path ,
349338 download_coco_val : tuple [Path , Path ],
350339 model_cls : type [RFDETR ],
351340 threshold_map : float ,
341+ threshold_f1 : float ,
352342 num_samples : int ,
353343 batch_size : int ,
354344) -> None :
@@ -362,6 +352,7 @@ def test_inference_segmentation_ptl_predict(
362352 download_coco_val: Fixture providing ``(images_root, annotations_path)``.
363353 model_cls: Segmentation model class to instantiate with pretrained weights.
364354 threshold_map: Minimum ``val/mAP_50`` (bbox) required.
355+ threshold_f1: Minimum ``val/F1`` (best macro-F1 across confidence sweep) required.
365356 num_samples: Number of val samples used for ``Trainer.validate``.
366357 batch_size: DataLoader batch size.
367358 """
@@ -381,8 +372,10 @@ def test_inference_segmentation_ptl_predict(
381372 assert predictions is not None , "trainer.predict() returned None"
382373 assert len (predictions ) > 0 , "trainer.predict() returned empty list"
383374
384- # Verify mAP via Trainer.validate on the full num_samples.
375+ # Verify mAP and F1 via Trainer.validate on the full num_samples.
385376 val_dm = _build_datamodule (rfdetr .model_config , tc , num_samples = num_samples )
386- results = trainer .validate (module , datamodule = val_dm )
387- map_val = results [0 ]["val/mAP_50" ]
377+ (metrics ,) = trainer .validate (module , datamodule = val_dm )
378+ map_val = metrics ["val/mAP_50" ]
379+ f1_val = metrics ["val/F1" ]
388380 assert map_val >= threshold_map , f"mAP@50 { map_val :.4f} < { threshold_map } "
381+ assert f1_val >= threshold_f1 , f"F1 { f1_val :.4f} < { threshold_f1 } "
0 commit comments