Skip to main content

vision_rs/models/yolo/loss/
anchor.rs

1/*
2 * Copyright 2026 Teenygrad
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18//! Anchor grid generation for YOLO detection heads.
19//!
20//! YOLO26 uses three FPN levels with strides [8, 16, 32].  For each level,
21//! anchor centres are placed at the centre of every grid cell.
22
23/// Flattened anchor descriptors for all FPN levels.
24///
25/// Anchors are ordered level-by-level (stride 8 first, then 16, then 32),
26/// and within each level in row-major order (y, x).
27pub struct AnchorGrid {
28    /// Anchor centre x in input-image pixel coordinates. Shape: `[A]`.
29    pub cx: Vec<f32>,
30    /// Anchor centre y in input-image pixel coordinates. Shape: `[A]`.
31    pub cy: Vec<f32>,
32    /// Stride (scale factor from feature to input). Shape: `[A]`.
33    pub strides: Vec<f32>,
34    /// Total number of anchors A.
35    pub n_anchors: usize,
36}
37
38impl AnchorGrid {
39    /// Build the anchor grid for YOLO26 given the input image dimensions.
40    ///
41    /// `img_h` and `img_w` must be divisible by 32.
42    pub fn yolo26(img_h: usize, img_w: usize) -> Self {
43        Self::new(img_h, img_w, &[8, 16, 32])
44    }
45
46    /// Build the anchor grid for arbitrary strides.
47    pub fn new(img_h: usize, img_w: usize, strides: &[usize]) -> Self {
48        let total: usize = strides.iter().map(|&s| (img_h / s) * (img_w / s)).sum();
49        let mut cx      = Vec::with_capacity(total);
50        let mut cy      = Vec::with_capacity(total);
51        let mut stride_v = Vec::with_capacity(total);
52
53        for &s in strides {
54            let fh = img_h / s;
55            let fw = img_w / s;
56            for gy in 0..fh {
57                for gx in 0..fw {
58                    cx.push((gx as f32 + 0.5) * s as f32);
59                    cy.push((gy as f32 + 0.5) * s as f32);
60                    stride_v.push(s as f32);
61                }
62            }
63        }
64
65        AnchorGrid { n_anchors: total, cx, cy, strides: stride_v }
66    }
67
68    /// Decode raw LTRB predictions into absolute XYWH boxes.
69    ///
70    /// Input `ltrb`: `[4, A]` channels-first — (l, t, r, b) distances.
71    /// Output: `[4, A]` channels-first — (cx, cy, w, h) in pixel coords.
72    pub fn decode_ltrb_to_xywh(&self, ltrb: &[f32]) -> Vec<f32> {
73        let a = self.n_anchors;
74        assert_eq!(ltrb.len(), 4 * a);
75        let mut out = vec![0.0f32; 4 * a];
76        for i in 0..a {
77            let l = ltrb[i];
78            let t = ltrb[a + i];
79            let r = ltrb[2 * a + i];
80            let b = ltrb[3 * a + i];
81            let s = self.strides[i];
82            out[i]           = self.cx[i] + s * (r - l) * 0.5; // cx
83            out[a + i]       = self.cy[i] + s * (b - t) * 0.5; // cy
84            out[2 * a + i]   = s * (l + r);                     // w
85            out[3 * a + i]   = s * (t + b);                     // h
86        }
87        out
88    }
89
90    /// Backward through the LTRB → XYWH decode.
91    ///
92    /// `d_xywh`: `[4, A]` gradient w.r.t. decoded (cx, cy, w, h).
93    /// Returns: `[4, A]` gradient w.r.t. raw (l, t, r, b) predictions.
94    pub fn decode_backward(&self, d_xywh: &[f32]) -> Vec<f32> {
95        let a = self.n_anchors;
96        assert_eq!(d_xywh.len(), 4 * a);
97        let mut d_ltrb = vec![0.0f32; 4 * a];
98        for i in 0..a {
99            let d_cx = d_xywh[i];
100            let d_cy = d_xywh[a + i];
101            let d_w  = d_xywh[2 * a + i];
102            let d_h  = d_xywh[3 * a + i];
103            let s = self.strides[i];
104            d_ltrb[i]         = d_cx * (-s * 0.5) + d_w * s; // d_l
105            d_ltrb[a + i]     = d_cy * (-s * 0.5) + d_h * s; // d_t
106            d_ltrb[2 * a + i] = d_cx * ( s * 0.5) + d_w * s; // d_r
107            d_ltrb[3 * a + i] = d_cy * ( s * 0.5) + d_h * s; // d_b
108        }
109        d_ltrb
110    }
111}