1use super::config::{
24 SAM3_IMG_SIZE, SAM3_PATCH_GRID, SAM3_PIXEL_MEAN, SAM3_PIXEL_STD, Sam3VitConfig,
25};
26use anyhow::{Result, ensure};
27use rlx_core::weight_map::WeightMap;
28
29#[derive(Clone)]
30pub struct Sam3PreprocessWeights {
31 pub patch_proj_w: Vec<f32>,
33 pub patch_proj_b: Vec<f32>,
34 pub pos_embed: Option<Vec<f32>>,
35 pub embed_dim: usize,
36 pub patch_size: usize,
37 pub grid: usize,
38}
39
40pub(crate) fn extract_preprocess_weights(
41 weights: &mut WeightMap,
42 cfg: &Sam3VitConfig,
43) -> Result<Sam3PreprocessWeights> {
44 let e = cfg.embed_dim;
45 let ps = cfg.patch_size;
46 let grid = cfg.patch_grid();
47 let pd = 3 * ps * ps;
48
49 let (proj_raw, proj_shape) = take_first(
50 weights,
51 &[
52 "detector.backbone.vision_backbone.trunk.patch_embed.proj.weight",
53 "detector.backbone.visual.trunk.patch_embed.proj.weight",
54 "backbone.vision_backbone.trunk.patch_embed.proj.weight",
55 "backbone.visual.trunk.patch_embed.proj.weight",
56 "visual.trunk.patch_embed.proj.weight",
57 "trunk.patch_embed.proj.weight",
58 ],
59 )?;
60 let mut patch_proj_w = vec![0f32; e * pd];
64 if proj_shape == vec![e, 3, ps, ps] {
65 for ei in 0..e {
66 for d in 0..pd {
67 patch_proj_w[d * e + ei] = proj_raw[ei * pd + d];
68 }
69 }
70 } else if proj_shape == vec![e, ps, ps, 3] {
71 for ei in 0..e {
72 for c in 0..3 {
73 for kh in 0..ps {
74 for kw in 0..ps {
75 let d = c * ps * ps + kh * ps + kw;
76 let src = ei * (ps * ps * 3) + kh * (ps * 3) + kw * 3 + c;
77 patch_proj_w[d * e + ei] = proj_raw[src];
78 }
79 }
80 }
81 }
82 } else {
83 anyhow::bail!(
84 "SAM3 patch_embed.proj.weight expected [{e}, 3, {ps}, {ps}] (NCHW) or [{e}, {ps}, {ps}, 3] (NHWC), got {proj_shape:?}"
85 );
86 }
87
88 let patch_proj_b = if cfg.bias_patch_embed {
89 let (data, shape) = take_first(
90 weights,
91 &[
92 "detector.backbone.vision_backbone.trunk.patch_embed.proj.bias",
93 "detector.backbone.visual.trunk.patch_embed.proj.bias",
94 "backbone.vision_backbone.trunk.patch_embed.proj.bias",
95 "backbone.visual.trunk.patch_embed.proj.bias",
96 "visual.trunk.patch_embed.proj.bias",
97 "trunk.patch_embed.proj.bias",
98 ],
99 )?;
100 ensure!(
101 shape == vec![e],
102 "SAM3 patch bias expected [{e}], got {shape:?}"
103 );
104 data
105 } else {
106 vec![0.0; e]
107 };
108
109 let pos_embed = if cfg.use_abs_pos {
110 take_optional_first(
111 weights,
112 &[
113 "detector.backbone.vision_backbone.trunk.pos_embed",
114 "detector.backbone.visual.trunk.pos_embed",
115 "backbone.vision_backbone.trunk.pos_embed",
116 "backbone.visual.trunk.pos_embed",
117 "visual.trunk.pos_embed",
118 "trunk.pos_embed",
119 ],
120 )?
121 .map(|(data, shape)| materialize_pos_embed(&data, &shape, cfg, grid, e))
122 .transpose()?
123 } else {
124 None
125 };
126
127 Ok(Sam3PreprocessWeights {
128 patch_proj_w,
129 patch_proj_b,
130 pos_embed,
131 embed_dim: e,
132 patch_size: ps,
133 grid,
134 })
135}
136
137pub fn preprocess_image(rgb: &[u8], h_in: usize, w_in: usize) -> (Vec<f32>, (usize, usize)) {
139 let scale = (SAM3_IMG_SIZE as f32) / (h_in.max(w_in) as f32);
140 let new_h = ((h_in as f32) * scale).round() as usize;
141 let new_w = ((w_in as f32) * scale).round() as usize;
142
143 let mut resized = vec![0f32; 3 * new_h * new_w];
144 let sx = (w_in as f32 - 1.0) / (new_w.max(1) as f32 - 1.0).max(1.0);
145 let sy = (h_in as f32 - 1.0) / (new_h.max(1) as f32 - 1.0).max(1.0);
146 for y in 0..new_h {
147 let fy = y as f32 * sy;
148 let y0 = fy.floor() as usize;
149 let y1 = (y0 + 1).min(h_in - 1);
150 let dy = fy - y0 as f32;
151 for x in 0..new_w {
152 let fx = x as f32 * sx;
153 let x0 = fx.floor() as usize;
154 let x1 = (x0 + 1).min(w_in - 1);
155 let dx = fx - x0 as f32;
156 for c in 0..3 {
157 let p00 = rgb[(y0 * w_in + x0) * 3 + c] as f32 / 255.0;
158 let p01 = rgb[(y0 * w_in + x1) * 3 + c] as f32 / 255.0;
159 let p10 = rgb[(y1 * w_in + x0) * 3 + c] as f32 / 255.0;
160 let p11 = rgb[(y1 * w_in + x1) * 3 + c] as f32 / 255.0;
161 let top = p00 * (1.0 - dx) + p01 * dx;
162 let bot = p10 * (1.0 - dx) + p11 * dx;
163 let v = top * (1.0 - dy) + bot * dy;
164 resized[c * new_h * new_w + y * new_w + x] =
165 (v - SAM3_PIXEL_MEAN[c]) / SAM3_PIXEL_STD[c];
166 }
167 }
168 }
169
170 let mut padded = vec![0f32; 3 * SAM3_IMG_SIZE * SAM3_IMG_SIZE];
171 for c in 0..3 {
172 for y in 0..new_h {
173 let src_row = c * new_h * new_w + y * new_w;
174 let dst_row = c * SAM3_IMG_SIZE * SAM3_IMG_SIZE + y * SAM3_IMG_SIZE;
175 padded[dst_row..dst_row + new_w].copy_from_slice(&resized[src_row..src_row + new_w]);
176 }
177 }
178 (padded, (new_h, new_w))
179}
180
181pub fn assemble_patch_tokens(pre: &Sam3PreprocessWeights, image_nchw: &[f32]) -> Result<Vec<f32>> {
182 let e = pre.embed_dim;
183 let ps = pre.patch_size;
184 let grid = pre.grid;
185 let pd = 3 * ps * ps;
186 ensure!(
187 image_nchw.len() == 3 * SAM3_IMG_SIZE * SAM3_IMG_SIZE,
188 "SAM3 image must be [3, {SAM3_IMG_SIZE}, {SAM3_IMG_SIZE}] NCHW, got len {}",
189 image_nchw.len()
190 );
191 ensure!(
192 grid == SAM3_PATCH_GRID,
193 "SAM3 base grid must be {SAM3_PATCH_GRID}"
194 );
195
196 let mut out = vec![0f32; grid * grid * e];
197 let mut patch_buf = vec![0f32; pd];
198 for py in 0..grid {
199 for px in 0..grid {
200 for c in 0..3 {
201 for ry in 0..ps {
202 let src_y = py * ps + ry;
203 for rx in 0..ps {
204 let src_x = px * ps + rx;
205 let src = c * SAM3_IMG_SIZE * SAM3_IMG_SIZE + src_y * SAM3_IMG_SIZE + src_x;
206 let dst = c * ps * ps + ry * ps + rx;
207 patch_buf[dst] = image_nchw[src];
208 }
209 }
210 }
211 let row = py * grid + px;
212 let dst = &mut out[row * e..(row + 1) * e];
213 dst.copy_from_slice(&pre.patch_proj_b);
214 for d in 0..pd {
215 let v = patch_buf[d];
216 if v == 0.0 {
217 continue;
218 }
219 let w_row = &pre.patch_proj_w[d * e..(d + 1) * e];
220 for k in 0..e {
221 dst[k] += v * w_row[k];
222 }
223 }
224 }
225 }
226
227 if let Some(pos) = &pre.pos_embed {
228 ensure!(pos.len() == out.len(), "SAM3 pos_embed size mismatch");
229 for i in 0..out.len() {
230 out[i] += pos[i];
231 }
232 }
233
234 Ok(out)
235}
236
237fn materialize_pos_embed(
244 data: &[f32],
245 shape: &[usize],
246 cfg: &Sam3VitConfig,
247 grid: usize,
248 e: usize,
249) -> Result<Vec<f32>> {
250 if shape == [1, grid, grid, e] || shape == [grid, grid, e] {
251 return Ok(data.to_vec());
252 }
253 ensure!(
254 shape.len() == 3 && shape[0] == 1 && shape[2] == e,
255 "SAM3 pos_embed expected [1, *, {e}], got {shape:?}"
256 );
257 let num_positions = shape[1];
258 let has_cls = num_positions % 2 == 1;
259 let spatial = if has_cls {
260 num_positions - 1
261 } else {
262 num_positions
263 };
264 let pretrain_grid = (spatial as f64).sqrt().round() as usize;
265 ensure!(
266 pretrain_grid * pretrain_grid == spatial,
267 "SAM3 pos_embed spatial portion not square: {spatial} positions"
268 );
269
270 let src = if has_cls { &data[e..] } else { data };
271 let mut out = vec![0f32; grid * grid * e];
272
273 if cfg.tile_abs_pos {
274 for y in 0..grid {
275 for x in 0..grid {
276 let sy = y % pretrain_grid;
277 let sx = x % pretrain_grid;
278 let src_row = (sy * pretrain_grid + sx) * e;
279 let dst_row = (y * grid + x) * e;
280 out[dst_row..dst_row + e].copy_from_slice(&src[src_row..src_row + e]);
281 }
282 }
283 } else {
284 bicubic_interp_nhwc(src, pretrain_grid, pretrain_grid, &mut out, grid, grid, e);
287 }
288
289 Ok(out)
290}
291
292fn bicubic_interp_nhwc(
293 src: &[f32],
294 src_h: usize,
295 src_w: usize,
296 dst: &mut [f32],
297 dst_h: usize,
298 dst_w: usize,
299 c: usize,
300) {
301 let mut src_chw = vec![0f32; c * src_h * src_w];
303 for y in 0..src_h {
304 for x in 0..src_w {
305 for ch in 0..c {
306 src_chw[ch * src_h * src_w + y * src_w + x] = src[(y * src_w + x) * c + ch];
307 }
308 }
309 }
310 let scale_y = src_h as f32 / dst_h as f32;
311 let scale_x = src_w as f32 / dst_w as f32;
312 for y in 0..dst_h {
313 let fy = (y as f32 + 0.5) * scale_y - 0.5;
314 let y_floor = fy.floor() as i32;
315 let dy = fy - y_floor as f32;
316 let wy = cubic_weights(dy);
317 for x in 0..dst_w {
318 let fx = (x as f32 + 0.5) * scale_x - 0.5;
319 let x_floor = fx.floor() as i32;
320 let dx = fx - x_floor as f32;
321 let wx = cubic_weights(dx);
322 for ch in 0..c {
323 let plane = &src_chw[ch * src_h * src_w..(ch + 1) * src_h * src_w];
324 let mut v = 0.0f32;
325 for j in -1..=2 {
326 let sy = (y_floor + j).clamp(0, src_h as i32 - 1) as usize;
327 let mut row_acc = 0.0f32;
328 for i in -1..=2 {
329 let sx = (x_floor + i).clamp(0, src_w as i32 - 1) as usize;
330 row_acc += plane[sy * src_w + sx] * wx[(i + 1) as usize];
331 }
332 v += row_acc * wy[(j + 1) as usize];
333 }
334 dst[(y * dst_w + x) * c + ch] = v;
335 }
336 }
337 }
338}
339
340fn cubic_weights(t: f32) -> [f32; 4] {
341 let a = -0.75f32;
343 let t1 = 1.0 + t; let t2 = t; let t3 = 1.0 - t; let t4 = 2.0 - t; [
348 cubic_kernel(t1, a),
349 cubic_kernel(t2, a),
350 cubic_kernel(t3, a),
351 cubic_kernel(t4, a),
352 ]
353}
354
355fn cubic_kernel(x: f32, a: f32) -> f32 {
356 let x = x.abs();
357 if x < 1.0 {
358 (a + 2.0) * x * x * x - (a + 3.0) * x * x + 1.0
359 } else if x < 2.0 {
360 a * x * x * x - 5.0 * a * x * x + 8.0 * a * x - 4.0 * a
361 } else {
362 0.0
363 }
364}
365
366fn take_first(weights: &mut WeightMap, keys: &[&str]) -> Result<(Vec<f32>, Vec<usize>)> {
367 for key in keys {
368 if weights.has(key) {
369 return weights.take(key);
370 }
371 }
372 anyhow::bail!("none of the SAM3 weight keys were found: {keys:?}")
373}
374
375fn take_optional_first(
376 weights: &mut WeightMap,
377 keys: &[&str],
378) -> Result<Option<(Vec<f32>, Vec<usize>)>> {
379 for key in keys {
380 if weights.has(key) {
381 return weights.take(key).map(Some);
382 }
383 }
384 Ok(None)
385}