Models
August 10, 2026 · View on GitHub
Detectors: Oriented R-CNN, Rotated Faster R-CNN, Rotated RetinaNet. Config: Configuration (model_type). User guide: Models.
RPN anchor angles (Python only)
Training JSON / ModelConfig does not expose anchor_angles (horizontal RPN priors are the default and match MMRotate-style setups). For experiments or legacy checkpoint matching, you may pass anchor_angles=[...] into RotatedFasterRCNN, OrientedRCNN, or RotatedRetinaNet constructors in code. Multi-angle RPN banks are usually not recommended for accuracy or speed versus the default.
Training vs export paths
| Mode | Code path | Notes |
|---|---|---|
Training (model.train()) | RotatedFasterRCNN.forward → self.roi_align → eager horizontal_roi_align (per-FPN loop) | Never calls faster_rcnn_inference.py. torch.onnx.is_in_onnx_export() is false. |
| Eval / deploy (Faster) | faster_rcnn_inference.faster_rcnn_inference() | Shared with PyTorch inference and export wrappers. |
| ONNX export (Faster) | Same as eval + horizontal_roi_align masked branch when is_in_onnx_export() | Fixed-shape RoIAlign for traceability; numerically equivalent to eager path (see tests/test_roi.py::test_horizontal_roi_align_eager_matches_onnx_export_path). |
| Eval (Oriented R-CNN) | Inline in OrientedRCNN.forward (midpoint RPN + OrientedROIAlign) | Set model._deterministic_rpn = True for export-parity comparisons. |
| ONNX export (Oriented) | oriented_rcnn_inference_pre_nms_padded + oriented_roi_align masked branch | Packed grid_sample (no feature Expand); always-on proposal cat pad; same Keras detect bundle as Faster. |
Regression guards: tests/test_roi.py (eager vs export RoIAlign), tests/test_models.py::TestRotatedFasterRCNN::test_full_training_forward_and_backward, export/tests/test_faster_rcnn_export_parity.py, export/tests/test_oriented_rcnn_export_parity.py.
Shared inference (faster_rcnn_inference.py / oriented_rcnn_inference.py)
RotatedFasterRCNN eval forwards through faster_rcnn_inference() (decode + rotated NMS). The same module powers ONNX export (export/wrappers.RotatedFasterRCNNPreNmsExportWrapper) with deterministic RPN top-k and padded proposals for traceable ROI align.
OrientedRCNN export uses oriented_rcnn_inference_pre_nms_padded (export/wrappers.OrientedRCNNPreNmsExportWrapper) with deterministic midpoint RPN and padded oriented proposals.
Rotated Faster R-CNN Proposal Filtering
RotatedFasterRCNN defaults (RPN/ROI IoU assign thresholds, ROI target_stds, post-RPN caps, final rotated NMS IoU) follow MMRotate’s DOTA le90 Faster R-CNN config unless overridden.
ROI training loss uses compute_horizontal_roi_loss (Rotated Faster R-CNN). Defaults match MMRotate via compute_horizontal_roi_loss_mmrotate (reg_norm='sampled_all'). Configurable via:
roi_box_reg_main_loss_type:smooth_l1(encoded primary, default) or decodedprobiou/riou/kfiouroi_box_reg_norm:sampled_all(MMDet avg_factor over pos+neg sample count) orpositives_only(per-dim mean over positives)roi_box_reg_iou_weight: decoded aux when main is Smooth L1 (optionalroi_box_reg_iou_schedule_*)roi_box_reg_smooth_l1_aux_weight: encoded Smooth L1 aux when main is decodedroi_box_reg_angle_weight(5th encoded dim; optionalroi_box_reg_angle_schedule_*),roi_match_low_quality,roi_min_pos_iouroi_proj_xy: encode/decode ROI dx/dy in the proposal local frame (truein DOTA base configs; no-op for axis-aligned xyxy RoIs, required for non-horizontal proposal angles)
Encoded Smooth L1 (main or aux) applies directly to all five encoded channels (MMRotate / MMDet L1Loss on bbox targets), including the angle channel after norm_factor and target_stds normalization. Optional roi_box_reg_angle_weight scales only the 5th channel.
Oriented R-CNN uses compute_oriented_roi_loss with the same encoded Smooth L1 and MMDet avg_factor normalization (roi_box_reg_norm: sampled_all by default).
Rotated RetinaNet uses compute_oriented_retinanet_loss. Set roi_box_reg_main_loss_type: probiou (or riou / kfiou) for a decoded primary loss; add encoded L1/Smooth L1 aux via roi_box_reg_smooth_l1_aux_weight (type from box_reg_loss_type, default l1 in DOTA recipes). Decoded regression randomly subsamples at most roi_batch_size_per_image (default 512) positive anchors per image across all FPN levels (not per level). loss_box_reg normalizes by the number of sampled positives used in regression. Classification still uses every matched anchor. When main is smooth_l1 (default), optional decoded aux uses roi_box_reg_iou_weight.
roi_inference_top_class_only (two-stage models)
RotatedFasterRCNN and OrientedRCNN use model.roi_inference_top_class_only only in eval / inference (after the ROI head), not during training loss:
false(default): keep every foreground class whose softmax probability is aboveinference_pre_nms_score_thresholdfor each proposal. This recovers more candidates when the classifier is still weak.true: take the argmax foreground class per proposal, then apply the same threshold to that score (MMRotate-style one detection hypothesis per RoI).
Recommended: leave false during early training (validation / snapshots where the head is immature). Switch to true for late fine-tuning and final inference when you want MMRotate-like behavior, fewer duplicate class hypotheses per RoI, and scores comparable to single-label decoding.
RotatedFasterRCNN applies no objectness score threshold when generating RPN proposals (training or inference): the RPN keeps top-k proposals by score only, like MMRotate. model.inference_pre_nms_score_threshold applies exclusively to ROI-head class scores before final rotated NMS (MMRotate test_cfg.rcnn.score_thr).
MMRotate parity notes (two-stage detectors)
- FPN levels: the RPN runs on all 5 levels (P2–P6, strides 4–64;
include_pool_level=Truekeeps torchvision's stride-64 max-pool level). Both two-stage detectors restrict ROI extraction to the first 4 FPN levels (strides 4–32):horizontal_roi_align(Rotated Faster R-CNN) andoriented_roi_align(Oriented R-CNN), matching MMRotateSingleRoIExtractor/RotatedSingleRoIExtractor. - RoIAlign:
horizontal_roi_alignusesaligned=True(half-pixel aligned), matching mmcv'sRoIAligndefault. - Backbone BN: frozen statistics (
FrozenBatchNorm2d) by default, matching MMRotatenorm_eval=True. Seebackbones/README.md. - Loss normalization: RPN and ROI SmoothL1 box-regression losses are summed over positives and divided by the total number of sampled anchors/RoIs (MMDet
avg_factor), including Oriented R-CNN midpoint RPN and oriented ROI stages. - Assignment IoU: RPN stages use HBB IoU when
use_hbb_for_matching: true(MMRotate horizontal RPN). Oriented R-CNN ROI matching uses rotated IoU by default (roi_use_hbb_for_matching: false). RetinaNet uses rotated IoU (use_hbb_for_matching: false).
RPN proposal pruning uses horizontal xyxy proposals and torchvision.ops.nms on GPU.
The RPN proposal geometry is horizontal; rotated geometry is introduced by the ROI
regression head.
This is intentional for MMRotate-style Rotated Faster R-CNN behavior. Final ROI classification/regression predicts oriented boxes and final detections use the rotated backend abstraction.
Rotated final NMS and rotated assignment should route through
oriented_det.ops.rotated_ops. The default backend is this repo's parallel GPU
sampling implementation (ORIENTED_DET_ROTATED_BACKEND=gpu_sample); CPU is for
debug/reference checks. We do not rely on MMCV. If profiling shows a large win,
add in-repo CUDA kernels behind the same abstraction after the first release.
Rotated RetinaNet classification (sigmoid focal loss)
RotatedRetinaNet follows MMRotate's FocalLoss(use_sigmoid=True) exactly:
- The head outputs
num_anchors * num_classesclassification channels (K independent binary classifiers per anchor, no background channel). Bias init is-log((1-π)/π)with π=0.01 so every class starts at sigmoid ≈ 0.01. - Training uses sigmoid focal loss (
sigmoid_focal_loss_sum): one-hot binary targets per anchor,alpha(default 0.25) weighting positive entries and1-alphaweighting negatives, summed over all anchors/levels and normalized by the total number of positive anchors in the batch (MMDetavg_factor). - Inference scores are
sigmoid(logits)per class; the best class per anchor is kept (labels stay 1-indexed downstream).
This replaced an earlier softmax background+K formulation whose background-bias init plus uniform-alpha focal loss starved the classification head of gradients (cls grad norm ~1000x smaller than bbox), producing 0 detections after 12 epochs.
Checkpoint break (v0.2+): RetinaNet now uses MMRotate-style separate cls/reg 4-conv towers with 3×3 prediction heads and P6/P7 convs on C5 (LastLevelP6P7). Pre-change checkpoints (head.convs, 1×1 conv_cls/conv_bbox, extra_fpn_conv) are incompatible.
Rotated RetinaNet MMRotate alignment
- Head: independent
cls_convs/reg_convs(default 4×3×3 each) + 3×3conv_cls/conv_bbox(MMRotateRetinaHead). - FPN P6/P7:
fpn_extra_level: trueattaches torchvisionLastLevelP6P7on C5 (add_extra_convs='on_input'), not max-pool P6 + manual P7 conv. - 5 FPN levels (P3–P7) with strides
[8, 16, 32, 64, 128]whenfpn_returned_layers: [2,3,4]. min_pos_iou=0in anchor assignment (MMRotateMaxIoUAssigner).- Regression loss: encoded L1/SmoothL1 summed over positives, normalized by batch positive count (MMDet
avg_factor). - Rotated IoU assignment (
use_hbb_for_matching: false). - le90 angle wrap in
edge_swapencoding (norm_angle_le90inencode_oriented_boxes).
final_nms_use_cpu (exact final NMS)
Set model.final_nms_use_cpu to true in JSON / ModelConfig so post-head final oriented NMS only uses the polygon IoU Python path on CPU (rotated_nms(..., force_cpu=True) for two-stage models; RetinaNet skips its GPU NMS branch). RPN NMS, anchor/ROI matching, and ORIENTED_DET_ROTATED_BACKEND elsewhere are unchanged—so training stays fast; validation/inference final dedup is slower but matches exact greedy NMS on true rotated IoU.
RPN anchor assignment also uses HBB overlap when use_hbb_for_matching is true. That path computes HBB IoU in large chunks and keeps only the best GT per anchor and best anchor per GT. This avoids thousands of tiny GPU launches and avoids materializing a full anchors x GT matrix for P2, where a single image can have millions of anchors.
First-batch timing probes are gated by code-only debug flags and are disabled by
default: TRACE_FIRST_TRAIN_FORWARD_TIMING in oriented_rcnn.py and
TRACE_RPN_LOSS_TIMING in oriented_rpn.py.