16031aae96
CPU tests Workflow / Testing (ubuntu-latest, 3.12) (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.13) (push) Failing after 0s
Mypy Type Check / Type Check (push) Failing after 0s
Docs/Test WorkFlow / Test docs build (push) Failing after 1s
PR Conflict Labeler / labeling (push) Failing after 1s
Dependency resolution / Resolve [tflite] extra — Python 3.12 (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.10) (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.13) (push) Failing after 1s
CPU tests Workflow / build-pkg (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.10) (push) Failing after 0s
CPU tests Workflow / Testing (ubuntu-latest, 3.11) (push) Failing after 0s
Smoke Tests / try-all-models (macos-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (macos-latest, 3.13) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / testing-guardian (push) Has been cancelled
GPU tests Workflow / Testing (push) Has been cancelled
157 lines
7.2 KiB
Python
157 lines
7.2 KiB
Python
# ------------------------------------------------------------------------
|
|
# RF-DETR
|
|
# Copyright (c) 2025 Roboflow. All Rights Reserved.
|
|
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
|
# ------------------------------------------------------------------------
|
|
"""Unit tests for _namespace_from_configs — the canonical config-to-Namespace mapping."""
|
|
|
|
import pytest
|
|
|
|
from rfdetr._namespace import _namespace_from_configs
|
|
|
|
|
|
class TestNamespaceFromConfigs:
|
|
"""_namespace_from_configs maps ModelConfig + TrainConfig to a Namespace."""
|
|
|
|
def test_forwards_model_config_fields(self, base_model_config, base_train_config):
|
|
"""All key ModelConfig fields are faithfully mapped."""
|
|
mc = base_model_config(num_classes=7)
|
|
args = _namespace_from_configs(mc, base_train_config())
|
|
|
|
assert args.encoder == mc.encoder
|
|
assert args.num_classes == 7
|
|
assert args.hidden_dim == mc.hidden_dim
|
|
assert args.resolution == mc.resolution
|
|
assert args.patch_size == mc.patch_size
|
|
assert args.num_windows == mc.num_windows
|
|
assert args.segmentation_head == mc.segmentation_head
|
|
assert args.positional_encoding_size == mc.positional_encoding_size
|
|
|
|
def test_forwards_train_config_fields(self, base_model_config, base_train_config):
|
|
"""All key TrainConfig fields are faithfully mapped."""
|
|
tc = base_train_config(
|
|
lr=3e-4,
|
|
epochs=20,
|
|
weight_decay=5e-5,
|
|
batch_size=4,
|
|
num_workers=0,
|
|
eval_interval=3,
|
|
log_per_class_metrics=False,
|
|
train_log_sync_dist=True,
|
|
train_log_on_step=True,
|
|
compute_val_loss=False,
|
|
compute_test_loss=False,
|
|
ema_update_interval=2,
|
|
prefetch_factor=4,
|
|
)
|
|
args = _namespace_from_configs(base_model_config(), tc)
|
|
|
|
assert args.lr == pytest.approx(3e-4)
|
|
assert args.epochs == 20
|
|
assert args.weight_decay == pytest.approx(5e-5)
|
|
assert args.batch_size == 4
|
|
assert args.num_workers == 0
|
|
assert args.eval_interval == 3
|
|
assert args.log_per_class_metrics is False
|
|
assert args.train_log_sync_dist is True
|
|
assert args.train_log_on_step is True
|
|
assert args.compute_val_loss is False
|
|
assert args.compute_test_loss is False
|
|
assert args.ema_update_interval == 2
|
|
assert args.prefetch_factor == 4
|
|
|
|
def test_forwards_promoted_train_fields(self, base_model_config, base_train_config):
|
|
"""Promoted TrainConfig fields are forwarded to the namespace."""
|
|
tc = base_train_config(clip_max_norm=0.35, seed=123, sync_bn=True, fp16_eval=True)
|
|
args = _namespace_from_configs(base_model_config(), tc)
|
|
|
|
assert args.clip_max_norm == pytest.approx(0.35)
|
|
assert args.seed == 123
|
|
assert args.sync_bn is True
|
|
assert args.fp16_eval is True
|
|
|
|
def test_seed_falls_back_to_legacy_default_when_unset(self, base_model_config, base_train_config):
|
|
"""Seed defaults to 42 in the namespace when TrainConfig.seed is None."""
|
|
tc = base_train_config(seed=None)
|
|
args = _namespace_from_configs(base_model_config(), tc)
|
|
assert args.seed == 42
|
|
|
|
def test_forwards_dataset_fields(self, base_model_config, base_train_config):
|
|
"""Dataset-routing fields are forwarded to the Namespace."""
|
|
tc = base_train_config(multi_scale=True, expanded_scales=True, dataset_file="coco")
|
|
args = _namespace_from_configs(base_model_config(), tc)
|
|
|
|
assert args.multi_scale is True
|
|
assert args.expanded_scales is True
|
|
assert args.dataset_file == "coco"
|
|
|
|
def test_num_queries_from_subclass_config(self, base_model_config, base_train_config):
|
|
"""num_queries is read from subclass config attributes."""
|
|
mc = base_model_config() # RFDETRBaseConfig has num_queries=300
|
|
args = _namespace_from_configs(mc, base_train_config())
|
|
assert args.num_queries == 300
|
|
|
|
def test_resume_none_becomes_empty_string(self, base_model_config, base_train_config):
|
|
"""Resume=None (the default) is converted to '' for the Namespace."""
|
|
tc = base_train_config()
|
|
assert tc.resume is None
|
|
args = _namespace_from_configs(base_model_config(), tc)
|
|
assert args.resume == ""
|
|
|
|
def test_segmentation_extras_forwarded_from_seg_config(self, base_model_config, seg_train_config):
|
|
"""SegmentationTrainConfig mask loss coefficients are forwarded."""
|
|
mc = base_model_config(segmentation_head=True)
|
|
tc = seg_train_config()
|
|
args = _namespace_from_configs(mc, tc)
|
|
|
|
assert args.mask_ce_loss_coef == pytest.approx(5.0)
|
|
assert args.mask_dice_loss_coef == pytest.approx(5.0)
|
|
|
|
def test_segmentation_cls_loss_default_matches_pre_1_7_effective_weight(self, base_model_config, seg_train_config):
|
|
"""Default segmentation classification loss weight must stay at the pre-1.7 effective value."""
|
|
mc = base_model_config(segmentation_head=True)
|
|
tc = seg_train_config()
|
|
args = _namespace_from_configs(mc, tc)
|
|
|
|
assert args.cls_loss_coef == pytest.approx(1.0)
|
|
|
|
def test_segmentation_cls_loss_explicit_override_is_forwarded(self, base_model_config, seg_train_config):
|
|
"""Explicit segmentation classification loss weight overrides are preserved."""
|
|
mc = base_model_config(segmentation_head=True)
|
|
tc = seg_train_config(cls_loss_coef=5.0)
|
|
args = _namespace_from_configs(mc, tc)
|
|
|
|
assert args.cls_loss_coef == pytest.approx(5.0)
|
|
|
|
def test_segmentation_num_select_none_falls_back_to_model_config(self, base_model_config, seg_train_config) -> None:
|
|
"""SegmentationTrainConfig(num_select=None) must not overwrite ModelConfig.num_select."""
|
|
mc = base_model_config(segmentation_head=True, num_select=200)
|
|
# Explicitly passing num_select=None triggers the deprecation warning (Item #3).
|
|
with pytest.warns(DeprecationWarning, match="TrainConfig.num_select is deprecated"):
|
|
tc = seg_train_config(num_select=None)
|
|
|
|
args = _namespace_from_configs(mc, tc)
|
|
|
|
assert args.num_select == 200
|
|
|
|
def test_segmentation_extras_default_for_plain_config(self, base_model_config, base_train_config):
|
|
"""mask_* attributes default to 5.0 for a plain TrainConfig (not segmentation)."""
|
|
args = _namespace_from_configs(base_model_config(), base_train_config())
|
|
assert args.mask_ce_loss_coef == pytest.approx(5.0)
|
|
assert args.mask_dice_loss_coef == pytest.approx(5.0)
|
|
|
|
def test_segmentation_head_flag_forwarded(self, base_model_config, base_train_config):
|
|
"""segmentation_head=True from ModelConfig reaches the Namespace."""
|
|
mc = base_model_config(segmentation_head=True)
|
|
args = _namespace_from_configs(mc, base_train_config())
|
|
assert args.segmentation_head is True
|
|
|
|
def test_build_namespace_emits_deprecation_warning(
|
|
self, base_model_config, base_train_config, reset_build_namespace_warning_state
|
|
):
|
|
"""build_namespace() must emit a DeprecationWarning on every call."""
|
|
from rfdetr._namespace import build_namespace
|
|
|
|
with pytest.warns(FutureWarning, match="build_namespace"):
|
|
build_namespace(base_model_config(), base_train_config())
|