1use std::collections::{BTreeMap, HashMap, HashSet};
34use std::time::Instant;
35
36use anyhow::{anyhow, Result};
37use cortiq_core::CmfModel;
38
39use crate::inference::gelu_erf;
40use crate::pool::Pool;
41use crate::sam3::{self, Sam3, Sam3Out};
42use crate::sam3vid_net::{
43 dense, ln_chw, resize2d, sine, sine_1d, sigm, Net, TrkFeats, EMB, HID, MEM, NO_OBJ_SCORE,
44};
45
46pub const MASK_SIDE: usize = 288;
49const IMG: usize = 1008;
51const MEM_SIDE: usize = 1152;
53
54#[derive(Clone, Debug, PartialEq)]
60pub struct VidCfg {
61 pub score_threshold_detection: f32,
62 pub det_nms_thresh: f32,
63 pub assoc_iou_thresh: f32,
64 pub trk_assoc_iou_thresh: f32,
65 pub new_det_thresh: f32,
66 pub recondition_on_trk_masks: bool,
67 pub recondition_every_nth_frame: i64,
68 pub hotstart_delay: i64,
69 pub hotstart_unmatch_thresh: usize,
70 pub hotstart_dup_thresh: usize,
71 pub suppress_unmatched_only_within_hotstart: bool,
72 pub init_trk_keep_alive: i64,
73 pub max_trk_keep_alive: i64,
74 pub min_trk_keep_alive: i64,
75 pub suppress_recent_occlusion_thresh: f32,
76 pub decrease_keep_alive_for_empty: bool,
77 pub fill_hole_area: usize,
78 pub max_num_objects: usize,
79 pub high_conf_thresh: f32,
80 pub high_iou_thresh: f32,
81 pub low_res_mask_size: usize,
82 pub num_maskmem: usize,
83 pub max_cond_frame_num: usize,
84 pub max_object_pointers: usize,
85 pub sigmoid_scale: f32,
86 pub sigmoid_bias: f32,
87 pub temporal_ptr_pe: bool,
88 pub occlusion_spatial: bool,
89 pub multimask: bool,
92}
93
94impl Default for VidCfg {
95 fn default() -> Self {
96 Self {
97 score_threshold_detection: 0.5,
98 det_nms_thresh: 0.1,
99 assoc_iou_thresh: 0.1,
100 trk_assoc_iou_thresh: 0.5,
101 new_det_thresh: 0.7,
102 recondition_on_trk_masks: false,
103 recondition_every_nth_frame: 16,
104 hotstart_delay: 15,
105 hotstart_unmatch_thresh: 8,
106 hotstart_dup_thresh: 8,
107 suppress_unmatched_only_within_hotstart: true,
108 init_trk_keep_alive: 30,
109 max_trk_keep_alive: 30,
110 min_trk_keep_alive: -1,
111 suppress_recent_occlusion_thresh: 0.7,
112 decrease_keep_alive_for_empty: false,
113 fill_hole_area: 16,
114 max_num_objects: 10_000,
115 high_conf_thresh: 0.8,
116 high_iou_thresh: 0.8,
117 low_res_mask_size: MASK_SIDE,
118 num_maskmem: 7,
119 max_cond_frame_num: 4,
120 max_object_pointers: 16,
121 sigmoid_scale: 20.0,
122 sigmoid_bias: -10.0,
123 temporal_ptr_pe: true,
124 occlusion_spatial: true,
125 multimask: true,
126 }
127 }
128}
129
130impl VidCfg {
131 pub fn from_cmf(m: &CmfModel) -> Result<Self> {
133 let mut c = Self::default();
134 let raw = m
135 .tensor("cfg.tracker")
136 .ok_or_else(|| anyhow!("the CMF carries no cfg.tracker section — not a SAM3 video model"))?;
137 let bytes = m.entry_bytes(raw);
138 let v: serde_json::Value =
139 serde_json::from_slice(bytes).map_err(|e| anyhow!("cfg.tracker is not JSON: {e}"))?;
140 let f = |k: &str, d: f32| v[k].as_f64().map_or(d, |x| x as f32);
141 let i = |k: &str, d: i64| v[k].as_i64().unwrap_or(d);
142 let b = |k: &str, d: bool| v[k].as_bool().unwrap_or(d);
143 let u = |k: &str, d: usize| v[k].as_u64().map_or(d, |x| x as usize);
144
145 c.score_threshold_detection = f("score_threshold_detection", c.score_threshold_detection);
146 c.det_nms_thresh = f("det_nms_thresh", c.det_nms_thresh);
147 c.assoc_iou_thresh = f("assoc_iou_thresh", c.assoc_iou_thresh);
148 c.trk_assoc_iou_thresh = f("trk_assoc_iou_thresh", c.trk_assoc_iou_thresh);
149 c.new_det_thresh = f("new_det_thresh", c.new_det_thresh);
150 c.recondition_on_trk_masks = b("recondition_on_trk_masks", c.recondition_on_trk_masks);
151 c.recondition_every_nth_frame = i("recondition_every_nth_frame", c.recondition_every_nth_frame);
152 c.hotstart_delay = i("hotstart_delay", c.hotstart_delay);
153 c.hotstart_unmatch_thresh = u("hotstart_unmatch_thresh", c.hotstart_unmatch_thresh);
154 c.hotstart_dup_thresh = u("hotstart_dup_thresh", c.hotstart_dup_thresh);
155 c.suppress_unmatched_only_within_hotstart =
156 b("suppress_unmatched_only_within_hotstart", c.suppress_unmatched_only_within_hotstart);
157 c.init_trk_keep_alive = i("init_trk_keep_alive", c.init_trk_keep_alive);
158 c.max_trk_keep_alive = i("max_trk_keep_alive", c.max_trk_keep_alive);
159 c.min_trk_keep_alive = i("min_trk_keep_alive", c.min_trk_keep_alive);
160 c.suppress_recent_occlusion_thresh = f(
161 "suppress_overlapping_based_on_recent_occlusion_threshold",
162 c.suppress_recent_occlusion_thresh,
163 );
164 c.decrease_keep_alive_for_empty =
165 b("decrease_trk_keep_alive_for_empty_masklets", c.decrease_keep_alive_for_empty);
166 c.fill_hole_area = u("fill_hole_area", c.fill_hole_area);
167 c.max_num_objects = u("max_num_objects", c.max_num_objects);
168 c.high_conf_thresh = f("high_conf_thresh", c.high_conf_thresh);
169 c.high_iou_thresh = f("high_iou_thresh", c.high_iou_thresh);
170 c.low_res_mask_size = u("low_res_mask_size", c.low_res_mask_size);
171 let t = &v["tracker_config"];
172 c.num_maskmem = t["num_maskmem"].as_u64().map_or(c.num_maskmem, |x| x as usize);
173 c.max_cond_frame_num = t["max_cond_frame_num"].as_u64().map_or(c.max_cond_frame_num, |x| x as usize);
174 c.max_object_pointers =
175 t["max_object_pointers_in_encoder"].as_u64().map_or(c.max_object_pointers, |x| x as usize);
176 c.sigmoid_scale = t["sigmoid_scale_for_mem_enc"].as_f64().map_or(c.sigmoid_scale, |x| x as f32);
177 c.sigmoid_bias = t["sigmoid_bias_for_mem_enc"].as_f64().map_or(c.sigmoid_bias, |x| x as f32);
178 c.temporal_ptr_pe = t["enable_temporal_pos_encoding_for_object_pointers"]
179 .as_bool()
180 .unwrap_or(c.temporal_ptr_pe);
181 c.occlusion_spatial =
182 t["enable_occlusion_spatial_embedding"].as_bool().unwrap_or(c.occlusion_spatial);
183 c.multimask = t["multimask_output_in_sam"].as_bool().unwrap_or(true)
184 && t["multimask_output_for_tracking"].as_bool().unwrap_or(true);
185 anyhow::ensure!(
186 c.low_res_mask_size == MASK_SIDE,
187 "unsupported low_res_mask_size {}",
188 c.low_res_mask_size
189 );
190 anyhow::ensure!(c.num_maskmem <= 7, "num_maskmem {} > 7", c.num_maskmem);
191 Ok(c)
192 }
193}
194
195#[derive(Default, Clone)]
199struct ObjOut {
200 pred_masks: Vec<f32>,
202 obj_score: f32,
204 pointer: Vec<f32>,
206 mem_feat: Option<Vec<f32>>,
207 mem_pos: Option<Vec<f32>>,
208}
209
210#[derive(Default)]
211struct ObjState {
212 cond: BTreeMap<usize, ObjOut>,
213 non_cond: BTreeMap<usize, ObjOut>,
214}
215
216pub struct Session {
218 pub num_frames: usize,
221 pub obj_ids: Vec<i64>,
223 pub max_obj_id: i64,
224 objs: HashMap<i64, ObjState>,
225 score: HashMap<i64, f32>,
227 trk_score_fw: HashMap<i64, HashMap<usize, f32>>,
229 last_occluded: HashMap<i64, i64>,
230 first_frame: HashMap<i64, i64>,
231 unmatched: HashMap<i64, Vec<usize>>,
232 keep_alive: HashMap<i64, i64>,
233 overlap: HashMap<(i64, i64), Vec<usize>>,
234 removed: HashSet<i64>,
235 suppressed: HashSet<i64>,
236}
237
238impl Default for Session {
239 fn default() -> Self {
240 Self {
241 max_obj_id: -1,
244 num_frames: 0,
245 obj_ids: Vec::new(),
246 objs: HashMap::new(),
247 score: HashMap::new(),
248 trk_score_fw: HashMap::new(),
249 last_occluded: HashMap::new(),
250 first_frame: HashMap::new(),
251 unmatched: HashMap::new(),
252 keep_alive: HashMap::new(),
253 overlap: HashMap::new(),
254 removed: HashSet::new(),
255 suppressed: HashSet::new(),
256 }
257 }
258}
259
260impl Session {
261 pub fn new() -> Self {
262 Self::default()
263 }
264
265 pub fn hidden_ids(&self) -> Vec<i64> {
267 self.removed.union(&self.suppressed).copied().collect()
268 }
269}
270
271#[derive(Clone, Debug)]
275pub struct TrackObs {
276 pub id: i64,
277 pub score: f32,
278 pub box_px: [f32; 4],
280 pub mask: Vec<u8>,
282 pub from_detection: bool,
284 pub from_tracker: bool,
286}
287
288#[derive(Clone, Debug)]
290pub struct FrameOut {
291 pub frame: usize,
292 pub tracks: Vec<TrackObs>,
293 pub detections: usize,
295 pub presence: f32,
296 pub det_ms: f64,
297 pub trk_ms: f64,
298 pub total_ms: f64,
299}
300
301struct Det {
304 score: f32,
305 mask: Vec<f32>,
306}
307
308pub struct Sam3Video {
310 sam: Sam3,
311 pub cfg: VidCfg,
312}
313
314impl Sam3Video {
315 pub fn open(path: &str) -> Result<Self> {
316 let sam = Sam3::open(path)?;
317 let cfg = VidCfg::from_cmf(sam.cmf())?;
318 Ok(Self { sam, cfg })
319 }
320
321 pub fn detector(&mut self) -> &mut Sam3 {
323 &mut self.sam
324 }
325
326 fn net(&self) -> Net<'_> {
327 Net {
328 m: self.sam.cmf(),
329 pool: self.sam.pool_handle().map(|p| p.as_ref()),
330 cfg: &self.cfg,
331 }
332 }
333
334 pub fn forward(
337 &mut self,
338 s: &mut Session,
339 frame_idx: usize,
340 pixels: &[f32],
341 ids: &[u32],
342 attn: &[u8],
343 frame_size: (usize, usize),
344 ) -> Result<FrameOut> {
345 let t0 = Instant::now();
346 let t_fwd = Instant::now();
348 let out = self.sam.forward(pixels, ids, attn)?;
349 let mut det_ms = t_fwd.elapsed().as_secs_f64() * 1e3;
350 let t_det = Instant::now();
351 let dets = self.detect(&out)?;
352 det_ms += t_det.elapsed().as_secs_f64() * 1e3;
353 let presence = sigm(out.presence_logit);
354
355 let net = self.net();
356 let t_trk = Instant::now();
357
358 let feats = net.feats(&out.trunk_feats)?;
360
361 let pre_ids = s.obj_ids.clone();
363 let mut trk_masks: Vec<Vec<f32>> = Vec::new();
364 let mut trk_scores: Vec<f32> = Vec::new();
365 if !pre_ids.is_empty() {
366 for &id in &pre_ids {
367 let (o, _high) = self.propagate(&net, s, id, frame_idx, &feats)?;
368 trk_scores.push(o.obj_score);
369 trk_masks.push(o.pred_masks.clone());
370 self.store(s, id, frame_idx, false, o);
371 }
372 fill_holes(&mut trk_masks, self.cfg.fill_hole_area);
377 for (i, &id) in pre_ids.iter().enumerate() {
378 let v = sigm(trk_scores[i]);
379 s.trk_score_fw.entry(id).or_default().insert(frame_idx, v);
380 }
381 }
382
383 let plan = self.plan(&net, s, frame_idx, &dets, &trk_masks, &feats)?;
385
386 if !plan.new_inds.is_empty() {
388 let mut pending: Vec<(i64, ObjOut, Vec<f32>)> = Vec::new();
389 for (k, &di) in plan.new_inds.iter().enumerate() {
390 let id = plan.new_ids[k];
391 let mask: Vec<f32> = dets[di]
392 .mask
393 .iter()
394 .map(|&v| if v > 0.0 { 1.0 } else { 0.0 })
395 .collect();
396 let (o, high) = self.use_mask_as_output(&net, &feats, &mask)?;
397 self.store(s, id, frame_idx, true, o.clone());
398 if !s.obj_ids.contains(&id) {
399 s.obj_ids.push(id);
400 }
401 pending.push((id, o, high));
402 }
403 for &id in &pre_ids {
406 if plan.recond_ids.contains(&id) {
407 continue;
408 }
409 let (o, high) = self.propagate(&net, s, id, frame_idx, &feats)?;
410 self.store(s, id, frame_idx, false, o.clone());
411 pending.push((id, o, high));
412 }
413 let any_mask_input = !pending.is_empty();
414 self.encode_into(&net, s, frame_idx, pending, &feats.l2, IMG, IMG, any_mask_input)?;
415 }
416
417 let tracks = self.build_outputs(s, frame_idx, &pre_ids, &dets, &plan, frame_size)?;
419 let trk_ms = t_trk.elapsed().as_secs_f64() * 1e3;
420
421 for id in &plan.removed {
422 s.obj_ids.retain(|x| x != id);
423 s.objs.remove(id);
424 }
425 s.suppressed.extend(plan.suppressed.iter().copied());
426 s.removed.extend(plan.removed.iter().copied());
427
428 Ok(FrameOut {
429 frame: frame_idx,
430 tracks,
431 detections: dets.len(),
432 presence,
433 det_ms,
434 trk_ms,
435 total_ms: t0.elapsed().as_secs_f64() * 1e3,
436 })
437 }
438
439 fn detect(&self, out: &Sam3Out) -> Result<Vec<Det>> {
443 anyhow::ensure!(
444 !out.pred_boxes.is_empty() && out.pred_logits.len() == out.pred_boxes.len() / 4,
445 "forward returned no scores/masks (SAM3_ONLY debug env set?)"
446 );
447 let presence = sigm(out.presence_logit);
448 let nq = out.pred_boxes.len() / 4;
449 let hw = out.pred_masks.len() / nq.max(1);
450 anyhow::ensure!(hw == MASK_SIDE * MASK_SIDE, "unexpected mask shape {hw}");
451 let thr = self.cfg.score_threshold_detection;
452 let mut scores: Vec<f32> = out.pred_logits.iter().map(|&l| sigm(l) * presence).collect();
453 let keep_inital: Vec<bool> = scores.iter().map(|&s| s > thr).collect();
454 let mut keep = keep_inital.clone();
455
456 if self.cfg.det_nms_thresh > 0.0 {
457 let valid: Vec<usize> = (0..nq).filter(|&i| keep[i]).collect();
458 if valid.len() > 1 {
459 let bin = |q: usize| -> Vec<u8> {
460 out.pred_masks[q * hw..(q + 1) * hw]
461 .iter()
462 .map(|&v| u8::from(v > 0.0))
463 .collect()
464 };
465 let bins: Vec<(usize, Vec<u8>)> = valid.iter().map(|&q| (q, bin(q))).collect();
466 let mut order: Vec<usize> = (0..bins.len()).collect();
467 order.sort_by(|a, b| {
468 scores[bins[*a].0]
469 .partial_cmp(&scores[bins[*b].0])
470 .unwrap_or(std::cmp::Ordering::Equal)
471 });
472 let mut alive = vec![true; bins.len()];
473 for (k, &i) in order.iter().enumerate() {
474 if !alive[i] {
475 continue;
476 }
477 for &j in &order[k + 1..] {
478 if alive[j] && mask_iou(&bins[i].1, &bins[j].1) >= self.cfg.det_nms_thresh {
479 alive[j] = false;
480 }
481 }
482 }
483 for (idx, &(q, _)) in bins.iter().enumerate() {
484 if !alive[idx] {
485 keep[q] = false;
486 scores[q] = 0.0;
487 }
488 }
489 }
490 }
491 let mut dets = Vec::new();
492 for q in 0..nq {
493 if !keep[q] {
494 continue;
495 }
496 dets.push(Det {
497 score: scores[q],
498 mask: out.pred_masks[q * hw..(q + 1) * hw].to_vec(),
499 });
500 }
501 Ok(dets)
502 }
503
504 fn propagate(
510 &self,
511 net: &Net<'_>,
512 s: &Session,
513 id: i64,
514 frame_idx: usize,
515 feats: &TrkFeats,
516 ) -> Result<(ObjOut, Vec<f32>)> {
517 let max_ptr = if s.num_frames > 0 {
518 s.num_frames.min(self.cfg.max_object_pointers)
519 } else {
520 self.cfg.max_object_pointers
521 };
522 let st = s.objs.get(&id).ok_or_else(|| anyhow!("unknown object {id}"))?;
523 let cond = self.select_cond(st, frame_idx);
524 let memories = self.gather_memories(st, frame_idx, &cond)?;
525 let pointers = self.gather_pointers(st, frame_idx, &cond);
526 let pix = Net::pix_rows(feats);
527 let neck_pos = Net::neck_pos();
530 let pix = self.memory_cond(net, &pix, &neck_pos, &memories, &pointers, max_ptr)?;
531 let pos = net.image_pos()?;
532
533 let dense_p = net.no_mask_prompt()?;
534 let sparse = net.empty_sparse()?;
535 let dbg = std::env::var("SAM3_VID_DEBUG").is_ok();
536 if dbg {
537 let norm = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt();
538 let mn = pix.iter().cloned().fold(f32::INFINITY, f32::min);
539 let mx = pix.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
540 eprintln!(
541 "[vid] f{frame_idx} obj{id}: memories={} ptr={} pix n={:.3} ({}..{:.3})",
542 memories.len(),
543 pointers.len(),
544 norm(&pix),
545 mn,
546 mx
547 );
548 }
549 let (masks, iou, obj_score, tokens) = net.mask_decoder(
550 &sam3::rows_to_chw(&pix, EMB, EMB),
551 &pos,
552 &dense_p,
553 &sparse,
554 (&feats.l0, &feats.l1),
555 self.cfg.multimask,
556 )?;
557 let n_masks = masks.len() / (MASK_SIDE * MASK_SIDE);
558 let best = argmax(&iou).min(n_masks.saturating_sub(1));
559 if dbg {
560 let (mut cmn, mut cmx, mut cpos) = (f32::INFINITY, f32::NEG_INFINITY, 0usize);
561 for &v in masks.iter() {
562 cmn = cmn.min(v);
563 cmx = cmx.max(v);
564 cpos += usize::from(v > 0.0);
565 }
566 let base = best * MASK_SIDE * MASK_SIDE;
567 let spos = masks[base..base + MASK_SIDE * MASK_SIDE].iter().filter(|&&v| v > 0.0).count();
568 eprintln!(
569 "[vid] f{frame_idx} obj{id}: decoder iou={iou:?} obj_score={obj_score:.4} masks {cmn:.3}..{cmx:.3} pos={cpos}/{} chosen pos={spos}",
570 masks.len()
571 );
572 }
573 let low = masks[best * MASK_SIDE * MASK_SIDE..(best + 1) * MASK_SIDE * MASK_SIDE].to_vec();
574 let tok = &tokens[best * HID..(best + 1) * HID];
575 let appearing = obj_score > 0.0;
576 let low = if appearing {
577 low
578 } else {
579 if dbg {
580 eprintln!("[vid] f{frame_idx} obj{id}: NOT appearing (obj_score {obj_score}) -> mask forced to NO_OBJ");
581 }
582 vec![NO_OBJ_SCORE; MASK_SIDE * MASK_SIDE]
583 };
584 if dbg {
585 let pos = low.iter().filter(|&&v| v > 0.0).count();
586 eprintln!("[vid] f{frame_idx} obj{id}: propagate mask pos={pos}/{}", low.len());
587 }
588 let high = resize2d(&low, MASK_SIDE, MASK_SIDE, IMG, IMG);
589 let pointer = self.object_pointer(net, tok, appearing)?;
590 Ok((
591 ObjOut { pred_masks: low, obj_score, pointer, mem_feat: None, mem_pos: None },
592 high,
593 ))
594 }
595
596 fn use_mask_as_output(
599 &self,
600 net: &Net<'_>,
601 feats: &TrkFeats,
602 mask: &[f32],
603 ) -> Result<(ObjOut, Vec<f32>)> {
604 let appearing = mask.iter().any(|&v| v > 0.0);
605 let obj_score = if appearing { 10.0 } else { -10.0 };
606 let hi = resize2d(mask, MASK_SIDE, MASK_SIDE, IMG, IMG);
609 let high: Vec<f32> = hi
610 .iter()
611 .map(|v| v * self.cfg.sigmoid_scale + self.cfg.sigmoid_bias)
612 .collect();
613 let low = resize2d(&high, IMG, IMG, MASK_SIDE, MASK_SIDE);
614
615 let down = mask_downsample(net, &hi)?;
617 let down = resize2d(&down, IMG / 4, IMG / 4, MASK_SIDE, MASK_SIDE);
618 let dense_p = net.mask_prompt(&down)?;
619 let sparse = net.empty_sparse()?;
620 let pix = Net::pix_rows(feats);
621 let pos = net.image_pos()?;
622 let (_m, iou, dec_score, tokens) = net.mask_decoder(
623 &sam3::rows_to_chw(&pix, EMB, EMB),
624 &pos,
625 &dense_p,
626 &sparse,
627 (&feats.l0, &feats.l1),
628 self.cfg.multimask,
629 )?;
630 let n = tokens.len() / HID;
631 let best = argmax(&iou).min(n.saturating_sub(1));
634 let tok = &tokens[best * HID..(best + 1) * HID];
635 let mut pointer = self.object_pointer(net, tok, dec_score > 0.0)?;
636 if !appearing {
637 pointer = net.v("trk.no_obj_ptr")?;
638 }
639 Ok((
640 ObjOut { pred_masks: low, obj_score, pointer, mem_feat: None, mem_pos: None },
641 high,
642 ))
643 }
644
645 fn object_pointer(&self, net: &Net<'_>, tok: &[f32], appearing: bool) -> Result<Vec<f32>> {
646 let p = "trk.obj_ptr_proj.";
647 let mut h = dense(
649 &net.w(&format!("{p}layers.0.weight"))?,
650 &net.v(&format!("{p}layers.0.bias"))?,
651 tok,
652 1,
653 net.pool,
654 );
655 h = relu_vec(&h);
656 h = dense(
657 &net.w(&format!("{p}layers.1.weight"))?,
658 &net.v(&format!("{p}layers.1.bias"))?,
659 &h,
660 1,
661 net.pool,
662 );
663 h = relu_vec(&h);
664 let ptr = dense(
665 &net.w(&format!("{p}layers.2.weight"))?,
666 &net.v(&format!("{p}layers.2.bias"))?,
667 &h,
668 1,
669 net.pool,
670 );
671 if appearing {
672 Ok(ptr)
673 } else {
674 net.v("trk.no_obj_ptr")
675 }
676 }
677
678 fn select_cond(&self, st: &ObjState, frame_idx: usize) -> Vec<usize> {
683 let max = self.cfg.max_cond_frame_num;
684 if max == 0 || st.cond.len() <= max {
685 return st.cond.keys().copied().collect();
686 }
687 let mut sel: Vec<usize> = Vec::new();
688 if let Some(before) = st.cond.keys().copied().filter(|&t| t < frame_idx).next_back() {
689 sel.push(before);
690 }
691 if let Some(after) = st.cond.keys().copied().filter(|&t| t >= frame_idx).next() {
692 sel.push(after);
693 }
694 let remain = max.saturating_sub(sel.len());
695 let mut rest: Vec<usize> = st.cond.keys().copied().filter(|t| !sel.contains(t)).collect();
696 rest.sort_by_key(|t| (*t as i64 - frame_idx as i64).abs());
697 sel.extend(rest.into_iter().take(remain));
698 sel
699 }
700
701 fn gather_memories(
704 &self,
705 st: &ObjState,
706 frame_idx: usize,
707 cond: &[usize],
708 ) -> Result<Vec<(usize, Vec<f32>, Vec<f32>)>> {
709 let mut out = Vec::new();
710 for &t in cond {
711 if let Some(o) = st.cond.get(&t) {
712 if let (Some(f), Some(p)) = (&o.mem_feat, &o.mem_pos) {
713 out.push((0, f.clone(), p.clone()));
714 }
715 }
716 }
717 for off in (1..self.cfg.num_maskmem).rev() {
718 let prev = frame_idx as i64 - off as i64;
719 if prev < 0 {
720 continue;
721 }
722 if let Some(o) = st.non_cond.get(&(prev as usize)) {
723 if let (Some(f), Some(p)) = (&o.mem_feat, &o.mem_pos) {
724 out.push((off, f.clone(), p.clone()));
725 }
726 }
727 }
728 Ok(out)
729 }
730
731 fn gather_pointers(&self, st: &ObjState, frame_idx: usize, cond: &[usize]) -> Vec<(usize, Vec<f32>)> {
733 let mut offs = Vec::new();
734 let mut toks = Vec::new();
735 for &t in cond {
736 if t > frame_idx {
737 continue;
738 }
739 if let Some(o) = st.cond.get(&t) {
740 offs.push(frame_idx - t);
741 toks.push(o.pointer.clone());
742 }
743 }
744 for d in 1..self.cfg.max_object_pointers.max(2) {
745 let prev = frame_idx as i64 - d as i64;
746 if prev < 0 {
747 break;
748 }
749 if let Some(o) = st.non_cond.get(&(prev as usize)) {
750 offs.push(d);
751 toks.push(o.pointer.clone());
752 }
753 }
754 offs.into_iter().zip(toks).collect()
755 }
756
757 fn memory_cond(
760 &self,
761 net: &Net<'_>,
762 pix: &[f32],
763 pos: &[f32],
764 memories: &[(usize, Vec<f32>, Vec<f32>)],
765 pointers: &[(usize, Vec<f32>)],
766 max_ptr: usize,
767 ) -> Result<Vec<f32>> {
768 let n = EMB * EMB;
769 if memories.is_empty() {
770 return Ok(pix.to_vec());
772 }
773 let tpos = net.v("trk.maskmem_tpos_enc")?; let n_tpos = tpos.len() / MEM;
775 let mut mem: Vec<f32> = Vec::new();
776 let mut mem_pos: Vec<f32> = Vec::new();
777 for (off, f, p) in memories {
778 let row = if *off == 0 { n_tpos - 1 } else { (*off - 1).min(n_tpos - 1) };
781 for (i, v) in p.iter().enumerate() {
782 mem_pos.push(v + tpos[row * MEM + i % MEM]);
783 }
784 mem.extend_from_slice(f);
785 }
786 if !pointers.is_empty() {
787 let max_diff = max_ptr.saturating_sub(1).max(1) as f32;
788 let offs: Vec<f32> = pointers.iter().map(|(o, _)| *o as f32 / max_diff).collect();
789 let pe = sine_1d(&offs, HID);
790 let tw = net.w("trk.obj_ptr_tpos_proj.weight")?;
791 let tb = net.v("trk.obj_ptr_tpos_proj.bias")?;
792 for (i, (_o, p)) in pointers.iter().enumerate() {
793 let proj = dense(&tw, &tb, &pe[i * HID..(i + 1) * HID], 1, net.pool);
794 let splits = HID / MEM;
795 for s in 0..splits {
796 mem.extend_from_slice(&p[s * MEM..(s + 1) * MEM]);
797 mem_pos.extend_from_slice(&proj);
798 }
799 }
800 }
801 let mem_len = mem.len() / MEM;
804
805 let head_dim = HID;
807 let spatial = head_dim / 2;
808 let mut inv = vec![0f32; spatial / 2];
809 for i in 0..spatial / 2 {
810 inv[i] = 1.0 / 10000f32.powf((2 * i) as f32 / spatial as f32);
811 }
812 let mut cos = vec![0f32; n * head_dim];
813 let mut sin = vec![0f32; n * head_dim];
814 for y in 0..EMB {
815 for x in 0..EMB {
816 let base = (y * EMB + x) * head_dim;
817 for (k, src) in [x, y].iter().enumerate() {
819 for (j, iv) in inv.iter().enumerate() {
820 let ang = *src as f32 * *iv;
821 let dst = base + k * spatial + j * 2; cos[dst] = ang.cos();
823 cos[dst + 1] = ang.cos();
824 sin[dst] = ang.sin();
825 sin[dst + 1] = ang.sin();
826 }
827 }
828 }
829 }
830
831 let mut q = pix.to_vec();
832 for (a, b) in q.iter_mut().zip(pos) {
833 *a += 0.1 * *b;
834 }
835 let dump_dir = std::env::var("SAM3_VID_DUMP").ok();
836 let dump = |name: &str, v: &[f32]| {
837 if let Some(d) = &dump_dir {
838 let _ = std::fs::create_dir_all(d);
839 let _ = std::fs::write(format!("{d}/{name}.f32"), Self::f32_bytes(v));
840 }
841 };
842 dump("initial_seq", &q);
843 dump("cos", &cos);
844 dump("sin", &sin);
845 dump("memory", &mem);
846 dump("memory_pos", &mem_pos);
847 let tb = "trk.transformer.encoder.";
848 for layer in 0..4 {
849 let lb = format!("{tb}layers.{layer}.");
850 let mut h = q.clone();
851 net.ln_rows(&mut h, &format!("{lb}norm1.weight"), &format!("{lb}norm1.bias"), 1e-5)?;
852 dump(&format!("l{layer}_norm1"), &h);
853 let mut ao = vec![0f32; n * HID];
854 net.attn(&format!("{lb}self_attn."), &h, &h, &h, n, n, HID, HID, HID, Some((&cos, &sin, n)), &mut ao)?;
855 dump(&format!("l{layer}_self"), &ao);
856 for (a, c) in q.iter_mut().zip(&ao) {
857 *a += *c;
858 }
859 dump(&format!("l{layer}_res1"), &q);
860 let mut h = q.clone();
861 net.ln_rows(&mut h, &format!("{lb}norm2.weight"), &format!("{lb}norm2.bias"), 1e-5)?;
862 dump(&format!("l{layer}_norm2"), &h);
863 let k: Vec<f32> = mem
865 .chunks(MEM)
866 .zip(mem_pos.chunks(MEM))
867 .flat_map(|(a, b)| a.iter().zip(b).map(|(x, y)| x + y))
868 .collect();
869 if layer == 0 {
870 dump("cross_key", &k);
871 }
872 let mut ao = vec![0f32; n * HID];
873 net.attn(
874 &format!("{lb}cross_attn_image."),
875 &h,
876 &k,
877 &mem,
878 n,
879 mem_len,
880 HID,
881 MEM,
882 HID,
883 Some((&cos, &sin, mem_len - pointers.len() * (HID / MEM))),
884 &mut ao,
885 )?;
886 dump(&format!("l{layer}_cross"), &ao);
887 for (a, c) in q.iter_mut().zip(&ao) {
888 *a += *c;
889 }
890 dump(&format!("l{layer}_res2"), &q);
891 let mut h = q.clone();
892 net.ln_rows(&mut h, &format!("{lb}norm3.weight"), &format!("{lb}norm3.bias"), 1e-5)?;
893 let a = net.lin(&format!("{lb}"), "linear1", &h, n)?;
894 let a = relu_vec(&a);
895 let a = net.lin(&format!("{lb}"), "linear2", &a, n)?;
896 for (x, c) in q.iter_mut().zip(&a) {
897 *x += *c;
898 }
899 dump(&format!("l{layer}_out"), &q);
900 }
901 net.ln_rows(&mut q, &format!("{tb}norm.weight"), &format!("{tb}norm.bias"), 1e-5)?;
902 dump("final", &q);
903 Ok(q)
904 }
905
906 #[allow(dead_code)]
909 fn f32_bytes(v: &[f32]) -> Vec<u8> {
910 let mut b = Vec::with_capacity(v.len() * 4);
911 for &x in v {
912 b.extend_from_slice(&x.to_le_bytes());
913 }
914 b
915 }
916
917 fn encode_memory(
918 &self,
919 net: &Net<'_>,
920 pix: &[f32], mask_logits: &[f32], h: usize,
923 w: usize,
924 obj_score: f32,
925 from_pts: bool,
926 ) -> Result<(Vec<f32>, Vec<f32>)> {
927 let mut m = resize2d(mask_logits, h, w, MEM_SIDE, MEM_SIDE);
928 if from_pts {
929 for v in m.iter_mut() {
930 *v = if *v > 0.0 { 1.0 } else { 0.0 };
931 }
932 } else {
933 for v in m.iter_mut() {
934 *v = sigm(*v);
935 }
936 }
937 for v in m.iter_mut() {
938 *v = *v * self.cfg.sigmoid_scale + self.cfg.sigmoid_bias;
939 }
940 let dbg = std::env::var("SAM3_VID_DEBUG").is_ok();
941 if dbg {
942 let mn = m.iter().cloned().fold(f32::INFINITY, f32::min);
943 let mx = m.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
944 let pos = m.iter().filter(|&&v| v > 0.0).count();
945 eprintln!("[mem] mask input ({}): {mn:.3}..{mx:.3} pos={}/{} scale={} bias={}", m.len(), pos, m.len(), self.cfg.sigmoid_scale, self.cfg.sigmoid_bias);
946 }
947 let p = "trk.maskmem_backbone.mask_downsampler.encoder.";
949 let mut x = m;
950 let mut side = MEM_SIDE;
951 let mut c_in = 1usize;
952 for step in [0usize, 3, 6, 9] {
953 let wgt = net.v(&format!("{p}{step}.weight"))?;
954 let b = net.v(&format!("{p}{step}.bias"))?;
955 x = sam3::conv2d(&wgt, &b, &x, c_in, side, side, 3, 3, 2, 1, net.pool);
956 side /= 2;
957 let ch = b.len();
958 let lw = net.v(&format!("{p}{}.weight", step + 1))?;
959 let lb = net.v(&format!("{p}{}.bias", step + 1))?;
960 ln_chw(&mut x, ch, side * side, &lw, &lb, 1e-6);
961 x.iter_mut().for_each(|v| *v = gelu_erf(*v));
962 c_in = ch;
963 if dbg {
964 let mn = x.iter().cloned().fold(f32::INFINITY, f32::min);
965 let mx = x.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
966 eprintln!("[mem] after layer {step}: side={side} ch={ch} {mn:.3}..{mx:.3}");
967 }
968 }
969 let w12 = net.v(&format!("{p}12.weight"))?;
970 let b12 = net.v(&format!("{p}12.bias"))?;
971 x = sam3::conv2d(&w12, &b12, &x, c_in, side, side, 1, 1, 1, 0, net.pool);
972 anyhow::ensure!(x.len() == HID * EMB * EMB, "mask downsampler output {}", x.len());
973 if dbg {
974 let rms = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt() / (v.len() as f32).sqrt();
975 eprintln!("[mem] mask branch rms={:.4} | pix rms={:.4}", rms(&x), rms(pix));
976 }
977
978 let pw = net.v("trk.maskmem_backbone.pix_feat_proj.weight")?;
980 let pb = net.v("trk.maskmem_backbone.pix_feat_proj.bias")?;
981 let mut f = sam3::conv2d(&pw, &pb, pix, HID, EMB, EMB, 1, 1, 1, 0, net.pool);
982 if dbg {
983 let rms = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt() / (v.len() as f32).sqrt();
984 eprintln!("[mem] pix_proj rms={:.4}", rms(&f));
985 }
986 for (a, b) in f.iter_mut().zip(&x) {
987 *a += *b;
988 }
989 if dbg {
990 let rms = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt() / (v.len() as f32).sqrt();
991 eprintln!("[mem] fused pre-fuser rms={:.4}", rms(&f));
992 }
993 let dump_dir = std::env::var("SAM3_VID_DUMP").ok();
994 if let Some(d) = &dump_dir {
995 let _ = std::fs::create_dir_all(d);
996 let _ = std::fs::write(format!("{d}/mask_branch.f32"), Self::f32_bytes(&x));
997 let _ = std::fs::write(format!("{d}/pix_proj.f32"), Self::f32_bytes(&f));
998 }
999 for layer in 0..2 {
1001 let lp = format!("trk.maskmem_backbone.fuser.layers.{layer}.");
1002 let block_in = f.clone(); let dw = net.v(&format!("{lp}dwconv.weight"))?;
1004 let db = net.v(&format!("{lp}dwconv.bias"))?;
1005 f = depthwise(&dw, &db, &f, HID, EMB, EMB, 7, net.pool);
1006 let nw = net.v(&format!("{lp}norm.weight"))?;
1007 let nb = net.v(&format!("{lp}norm.bias"))?;
1008 ln_chw(&mut f, HID, EMB * EMB, &nw, &nb, 1e-6);
1009 let ln_out = if dump_dir.is_some() { Some(f.clone()) } else { None };
1010 let rows = EMB * EMB;
1013 let dw_out = if dump_dir.is_some() { Some(f.clone()) } else { None };
1014 let w1 = net.w(&format!("{lp}pwconv1.weight"))?;
1015 let b1 = net.v(&format!("{lp}pwconv1.bias"))?;
1016 let h_rows = sam3::chw_to_rows(&f, EMB, EMB);
1017 let mut hh = dense(&w1, &b1, &h_rows, rows, net.pool); hh.iter_mut().for_each(|v| *v = gelu_erf(*v));
1019 let pw1_out = if dump_dir.is_some() { Some(hh.clone()) } else { None };
1020 let w2 = net.w(&format!("{lp}pwconv2.weight"))?;
1021 let b2 = net.v(&format!("{lp}pwconv2.bias"))?;
1022 let hh = dense(&w2, &b2, &hh, rows, net.pool); let g = net.v(&format!("{lp}gamma"))?;
1024 if let Some(d) = &dump_dir {
1025 let _ = std::fs::write(format!("{d}/fuser_gamma{layer}.f32"), Self::f32_bytes(&g));
1026 }
1027 for c in 0..HID {
1028 for i in 0..rows {
1029 f[c * rows + i] = block_in[c * rows + i] + g[c] * hh[i * HID + c];
1030 }
1031 }
1032 if let Some(d) = &dump_dir {
1033 let _ = std::fs::write(format!("{d}/fuser_layer{layer}.f32"), Self::f32_bytes(&f));
1034 if layer == 0 {
1035 if let Some(v) = &dw_out {
1036 let _ = std::fs::write(format!("{d}/f0_dwconv.f32"), Self::f32_bytes(v));
1037 }
1038 if let Some(v) = &ln_out {
1039 let _ = std::fs::write(format!("{d}/f0_norm.f32"), Self::f32_bytes(v));
1040 }
1041 if let Some(v) = &pw1_out {
1042 let _ = std::fs::write(format!("{d}/f0_pw1.f32"), Self::f32_bytes(v));
1043 }
1044 let _ = std::fs::write(format!("{d}/f0_pw2.f32"), Self::f32_bytes(&hh));
1045 }
1046 }
1047 }
1048 if std::env::var("SAM3_VID_DEBUG").is_ok() {
1049 let rms = |v: &[f32]| v.iter().map(|x| x * x).sum::<f32>().sqrt() / (v.len() as f32).sqrt();
1050 eprintln!("[mem] post-fuser rms={:.4}", rms(&f));
1051 }
1052 let ow = net.v("trk.maskmem_backbone.out_proj.weight")?;
1053 let ob = net.v("trk.maskmem_backbone.out_proj.bias")?;
1054 let mut f = sam3::conv2d(&ow, &ob, &f, HID, EMB, EMB, 1, 1, 1, 0, net.pool);
1055 anyhow::ensure!(f.len() == MEM * EMB * EMB, "memory projection {}", f.len());
1056 if self.cfg.occlusion_spatial && obj_score <= 0.0 {
1057 let e = net.v("trk.no_obj_embed_spatial")?;
1058 let hw = EMB * EMB;
1059 for i in 0..hw {
1060 for (c, ev) in e.iter().enumerate() {
1061 f[c * hw + i] += ev;
1062 }
1063 }
1064 }
1065 let pos = sine(EMB, EMB, MEM / 2);
1066 let rows = sam3::chw_to_rows(&f, EMB, EMB);
1067 Ok((rows, pos))
1068 }
1069
1070 fn store(&self, s: &mut Session, id: i64, frame_idx: usize, cond: bool, o: ObjOut) {
1073 let st = s.objs.entry(id).or_default();
1074 if cond {
1075 st.cond.insert(frame_idx, o);
1076 } else {
1077 st.non_cond.insert(frame_idx, o);
1078 }
1079 }
1080
1081 fn encode_into(
1084 &self,
1085 net: &Net<'_>,
1086 s: &mut Session,
1087 frame_idx: usize,
1088 pending: Vec<(i64, ObjOut, Vec<f32>)>,
1089 pix: &[f32],
1090 h: usize,
1091 w: usize,
1092 from_pts: bool,
1093 ) -> Result<()> {
1094 if pending.is_empty() {
1095 return Ok(());
1096 }
1097 for (id, o, high) in pending {
1098 let (mf, mp) = self.encode_memory(net, pix, &high, h, w, o.obj_score, from_pts)?;
1099 let st = s.objs.entry(id).or_default();
1100 let slot = if st.cond.contains_key(&frame_idx) {
1101 st.cond.get_mut(&frame_idx)
1102 } else {
1103 st.non_cond.get_mut(&frame_idx)
1104 };
1105 if let Some(slot) = slot {
1106 slot.mem_feat = Some(mf);
1107 slot.mem_pos = Some(mp);
1108 }
1109 }
1110 Ok(())
1111 }
1112}
1113
1114struct Plan {
1117 new_inds: Vec<usize>,
1118 new_ids: Vec<i64>,
1119 recond_ids: HashSet<i64>,
1120 removed: HashSet<i64>,
1121 suppressed: HashSet<i64>,
1122}
1123
1124impl Sam3Video {
1125 #[allow(clippy::too_many_arguments)]
1126 fn plan(
1127 &self,
1128 net: &Net<'_>,
1129 s: &mut Session,
1130 frame_idx: usize,
1131 dets: &[Det],
1132 trk_masks: &[Vec<f32>],
1133 feats: &TrkFeats,
1134 ) -> Result<Plan> {
1135 let cfg = &self.cfg;
1136 let pre_ids: Vec<i64> = s.obj_ids.clone();
1137 let det_masks: Vec<&[f32]> = dets.iter().map(|d| d.mask.as_slice()).collect();
1138 let det_scores: Vec<f32> = dets.iter().map(|d| d.score).collect();
1139 let assoc = associate(&det_masks, &det_scores, trk_masks, &pre_ids, cfg);
1140 if std::env::var("SAM3_VID_DEBUG").is_ok() {
1141 let trk_pos: Vec<usize> = trk_masks.iter().map(|m| m.iter().filter(|&&v| v > 0.0).count()).collect();
1142 let det_pos: Vec<usize> = det_masks.iter().map(|m| m.iter().filter(|&&v| v > 0.0).count()).collect();
1143 eprintln!(
1144 "[vid] f{frame_idx} plan: dets={} (pos {:?}, scores {:?}) trk={} (pos {:?}) new={:?} unmatched={:?} empty={:?}",
1145 det_masks.len(), det_pos, det_scores, trk_masks.len(), trk_pos,
1146 assoc.new_inds, assoc.unmatched, assoc.empty
1147 );
1148 }
1149
1150 let mut new_inds = assoc.new_inds;
1152 if pre_ids.len() + new_inds.len() > cfg.max_num_objects {
1153 let room = cfg.max_num_objects.saturating_sub(pre_ids.len());
1154 let mut by_score = new_inds.clone();
1155 by_score.sort_by(|a, b| {
1156 det_scores[*b].partial_cmp(&det_scores[*a]).unwrap_or(std::cmp::Ordering::Equal)
1157 });
1158 new_inds = by_score.into_iter().take(room).collect();
1159 new_inds.sort_unstable();
1160 }
1161 let start = s.max_obj_id + 1;
1162 let new_ids: Vec<i64> = (0..new_inds.len() as i64).map(|k| start + k).collect();
1163
1164 let mut removed: HashSet<i64> = HashSet::new();
1166 let mut suppressed: HashSet<i64> = HashSet::new();
1167 for (k, &di) in new_inds.iter().enumerate() {
1168 let id = new_ids[k];
1169 let _ = di;
1170 s.first_frame.entry(id).or_insert(frame_idx as i64);
1171 s.keep_alive.insert(id, cfg.init_trk_keep_alive);
1172 }
1173 let matched: HashSet<i64> = assoc.det_to_matched.values().flatten().copied().collect();
1174 for id in &matched {
1175 let ka = s.keep_alive.get(id).copied().unwrap_or(cfg.init_trk_keep_alive);
1176 s.keep_alive.insert(*id, cfg.max_trk_keep_alive.max(ka + 1));
1177 }
1178 for id in &assoc.unmatched {
1179 s.unmatched.entry(*id).or_default().push(frame_idx);
1180 let ka = s.keep_alive.get(id).copied().unwrap_or(cfg.init_trk_keep_alive);
1181 s.keep_alive.insert(*id, cfg.min_trk_keep_alive.max(ka - 1));
1182 }
1183 if cfg.decrease_keep_alive_for_empty {
1184 for id in &assoc.empty {
1185 let ka = s.keep_alive.get(id).copied().unwrap_or(cfg.init_trk_keep_alive);
1186 s.keep_alive.insert(*id, cfg.min_trk_keep_alive.max(ka - 1));
1187 }
1188 }
1189 let hotstart_diff = frame_idx as i64 - cfg.hotstart_delay;
1190 let already_removed: HashSet<i64> = s.removed.clone();
1191 for (id, frames) in s.unmatched.clone() {
1192 if already_removed.contains(&id) || removed.contains(&id) {
1193 continue;
1194 }
1195 if frames.len() >= cfg.hotstart_unmatch_thresh {
1196 if s.first_frame.get(&id).copied().unwrap_or(0) > hotstart_diff {
1197 removed.insert(id);
1198 }
1199 }
1200 let ka = s.keep_alive.get(&id).copied().unwrap_or(0);
1201 if ka <= 0
1202 && !cfg.suppress_unmatched_only_within_hotstart
1203 && !already_removed.contains(&id)
1204 && !removed.contains(&id)
1205 {
1206 suppressed.insert(id);
1207 }
1208 }
1209 for ids in assoc.det_to_matched.values() {
1210 if ids.len() < 2 {
1211 continue;
1212 }
1213 let first = *ids
1214 .iter()
1215 .min_by_key(|i| s.first_frame.get(i).copied().unwrap_or(i64::MAX))
1216 .unwrap();
1217 for id in ids {
1218 if *id != first {
1219 s.overlap.entry((first, *id)).or_default().push(frame_idx);
1220 }
1221 }
1222 }
1223 let mut pairs: Vec<((i64, i64), Vec<usize>)> = s.overlap.clone().into_iter().collect();
1224 pairs.sort();
1225 for ((_, id), frames) in pairs {
1226 if s.removed.contains(&id) || removed.contains(&id) {
1227 continue;
1228 }
1229 if s.first_frame.get(&id).copied().unwrap_or(0) > hotstart_diff
1230 && frames.len() >= cfg.hotstart_dup_thresh
1231 {
1232 removed.insert(id);
1233 }
1234 }
1235 s.removed.extend(removed.iter().copied());
1236
1237 let mut recond_ids: HashSet<i64> = HashSet::new();
1239 let mut recond_masks: Vec<(usize, Vec<f32>)> = Vec::new();
1240 if cfg.recondition_every_nth_frame > 0
1241 && frame_idx as i64 % cfg.recondition_every_nth_frame == 0
1242 && !assoc.trk_to_high_conf.is_empty()
1243 {
1244 for (&id, &di) in &assoc.trk_to_high_conf {
1245 let Some(i) = pre_ids.iter().position(|x| *x == id) else { continue };
1246 let trk_score = s
1247 .trk_score_fw
1248 .get(&id)
1249 .and_then(|m| m.get(&frame_idx))
1250 .copied()
1251 .unwrap_or(0.0);
1252 if trk_score <= cfg.high_conf_thresh {
1253 continue;
1254 }
1255 recond_ids.insert(id);
1256 if cfg.recondition_on_trk_masks {
1257 recond_masks.push((i, trk_masks[i].clone()));
1258 } else {
1259 recond_masks.push((
1260 i,
1261 dets[di].mask.iter().map(|&v| if v > 0.0 { 1.0 } else { 0.0 }).collect(),
1262 ));
1263 }
1264 }
1265 }
1266
1267 let mut trk = trk_masks.to_vec();
1269 if cfg.suppress_recent_occlusion_thresh > 0.0 && !trk.is_empty() {
1270 let bins: Vec<Vec<u8>> = trk
1271 .iter()
1272 .map(|m| m.iter().map(|&v| u8::from(v > 0.0)).collect())
1273 .collect();
1274 let n = bins.len();
1275 let mut last: Vec<i64> = pre_ids
1276 .iter()
1277 .map(|id| {
1278 s.last_occluded.get(id).copied().unwrap_or(if s.removed.contains(id) {
1279 100_000
1280 } else {
1281 -1
1282 })
1283 })
1284 .collect();
1285 let mut to_suppress = vec![false; n];
1286 if n > 1 {
1287 for i in 0..n {
1288 for j in (i + 1)..n {
1289 if mask_iou(&bins[i], &bins[j]) < cfg.suppress_recent_occlusion_thresh {
1290 continue;
1291 }
1292 if last[i] > last[j] && last[j] > -1 {
1293 to_suppress[i] = true;
1294 }
1295 if last[j] > last[i] && last[i] > -1 {
1296 to_suppress[j] = true;
1297 }
1298 }
1299 }
1300 }
1301 for (i, m) in trk.iter_mut().enumerate() {
1302 if bins[i].iter().all(|&v| v == 0) || to_suppress[i] {
1303 for v in m.iter_mut() {
1304 *v = -10.0;
1305 }
1306 last[i] = frame_idx as i64;
1307 }
1308 }
1309 for (i, id) in pre_ids.iter().enumerate() {
1310 s.last_occluded.insert(*id, last[i]);
1311 }
1312 }
1313
1314 let mut per_obj = trk;
1316 for (i, m) in &recond_masks {
1317 per_obj[*i] = m.clone();
1318 }
1319 for (i, &id) in pre_ids.iter().enumerate() {
1320 if recond_ids.contains(&id) {
1321 if let Some(st) = s.objs.get_mut(&id) {
1322 if let Some(o) = st.non_cond.remove(&frame_idx) {
1323 st.cond.insert(frame_idx, o);
1324 }
1325 }
1326 }
1327 }
1328 let per_obj = suppress_area_shrink(&per_obj);
1329 if !per_obj.is_empty() {
1330 let mut pending = Vec::new();
1331 for (i, &id) in pre_ids.iter().enumerate() {
1332 let area = per_obj[i].iter().any(|&v| v > 0.0);
1333 let score = if area { 10.0 } else { -10.0 };
1334 if let Some(st) = s.objs.get_mut(&id) {
1335 let slot = if st.cond.contains_key(&frame_idx) {
1336 st.cond.get_mut(&frame_idx)
1337 } else {
1338 st.non_cond.get_mut(&frame_idx)
1339 };
1340 if let Some(slot) = slot {
1341 slot.pred_masks = per_obj[i].clone();
1342 slot.obj_score = score;
1343 }
1344 }
1345 pending.push((
1346 id,
1347 ObjOut {
1348 pred_masks: per_obj[i].clone(),
1349 obj_score: score,
1350 pointer: Vec::new(),
1351 mem_feat: None,
1352 mem_pos: None,
1353 },
1354 per_obj[i].clone(),
1355 ));
1356 }
1357 self.encode_into(net, s, frame_idx, pending, &feats.l2, MASK_SIDE, MASK_SIDE, false)?;
1358 }
1359
1360 for (k, &id) in new_ids.iter().enumerate() {
1362 s.score.insert(id, det_scores[new_inds[k]]);
1363 s.trk_score_fw.entry(id).or_default().insert(frame_idx, det_scores[new_inds[k]]);
1364 }
1365 for id in &removed {
1366 s.score.insert(*id, -1e4);
1367 s.trk_score_fw.entry(*id).or_default().insert(frame_idx, -1e4);
1368 }
1369 s.max_obj_id = (start + new_ids.len() as i64 - 1).max(s.max_obj_id);
1370
1371 Ok(Plan { new_inds, new_ids, recond_ids, removed, suppressed })
1372 }
1373}
1374
1375struct Assoc {
1378 new_inds: Vec<usize>,
1379 unmatched: Vec<i64>,
1380 empty: Vec<i64>,
1381 det_to_matched: HashMap<usize, Vec<i64>>,
1382 trk_to_high_conf: HashMap<i64, usize>,
1383}
1384
1385fn associate(
1387 det_masks: &[&[f32]],
1388 det_scores: &[f32],
1389 trk_masks: &[Vec<f32>],
1390 trk_ids: &[i64],
1391 cfg: &VidCfg,
1392) -> Assoc {
1393 let blank = Assoc {
1394 new_inds: Vec::new(),
1395 unmatched: Vec::new(),
1396 empty: Vec::new(),
1397 det_to_matched: HashMap::new(),
1398 trk_to_high_conf: HashMap::new(),
1399 };
1400 if trk_masks.is_empty() {
1401 return Assoc { new_inds: (0..det_masks.len()).collect(), ..blank };
1403 }
1404 if det_masks.is_empty() {
1405 let mut unmatched = Vec::new();
1406 let mut empty = Vec::new();
1407 for (i, m) in trk_masks.iter().enumerate() {
1408 if m.iter().any(|&v| v > 0.0) {
1409 unmatched.push(trk_ids[i]);
1410 } else {
1411 empty.push(trk_ids[i]);
1412 }
1413 }
1414 return Assoc { unmatched, empty, ..blank };
1415 }
1416 let dbin: Vec<Vec<u8>> = det_masks
1417 .iter()
1418 .map(|m| m.iter().map(|&v| u8::from(v > 0.0)).collect())
1419 .collect();
1420 let tbin: Vec<Vec<u8>> = trk_masks
1421 .iter()
1422 .map(|m| m.iter().map(|&v| u8::from(v > 0.0)).collect())
1423 .collect();
1424 let mut ious = vec![0f32; det_masks.len() * trk_masks.len()];
1425 for (i, d) in dbin.iter().enumerate() {
1426 for (j, t) in tbin.iter().enumerate() {
1427 ious[i * trk_masks.len() + j] = mask_iou(d, t);
1428 }
1429 }
1430 let at = |i: usize, j: usize| ious[i * trk_masks.len() + j];
1431
1432 let mut unmatched = Vec::new();
1433 let mut empty = Vec::new();
1434 for (j, t) in tbin.iter().enumerate() {
1435 let nonempty = t.iter().any(|&v| v != 0);
1436 let matched = (0..det_masks.len()).any(|i| at(i, j) >= cfg.trk_assoc_iou_thresh);
1437 if !nonempty {
1438 empty.push(trk_ids[j]);
1439 } else if !matched {
1440 unmatched.push(trk_ids[j]);
1441 }
1442 }
1443 let mut new_inds = Vec::new();
1444 let mut det_to_matched = HashMap::new();
1445 let mut trk_to_high_conf = HashMap::new();
1446 for i in 0..det_masks.len() {
1447 let matches_any = (0..trk_masks.len()).any(|j| at(i, j) >= cfg.assoc_iou_thresh);
1448 let is_new = det_scores[i] >= cfg.new_det_thresh && !matches_any;
1449 if is_new {
1450 new_inds.push(i);
1451 }
1452 let ms: Vec<i64> = (0..trk_masks.len())
1453 .filter(|&j| at(i, j) >= cfg.assoc_iou_thresh)
1454 .map(|j| trk_ids[j])
1455 .collect();
1456 let mut best = (0usize, 0f32);
1457 for j in 0..trk_masks.len() {
1458 if at(i, j) > best.1 {
1459 best = (j, at(i, j));
1460 }
1461 }
1462 if det_scores[i] >= cfg.high_conf_thresh && !is_new && best.1 >= cfg.high_iou_thresh {
1463 trk_to_high_conf.insert(trk_ids[best.0], i);
1464 }
1465 det_to_matched.insert(i, ms);
1466 }
1467 Assoc { new_inds, unmatched, empty, det_to_matched, trk_to_high_conf }
1468}
1469
1470impl Sam3Video {
1473 #[allow(clippy::too_many_arguments)]
1474 fn build_outputs(
1475 &self,
1476 s: &Session,
1477 frame_idx: usize,
1478 pre_ids: &[i64],
1479 dets: &[Det],
1480 plan: &Plan,
1481 frame_size: (usize, usize),
1482 ) -> Result<Vec<TrackObs>> {
1483 let (fw, fh) = frame_size;
1484 let mut out: Vec<TrackObs> = Vec::new();
1485 for &id in pre_ids {
1487 if plan.removed.contains(&id) {
1488 continue;
1489 }
1490 let Some(st) = s.objs.get(&id) else { continue };
1491 let Some(o) = st.cond.get(&frame_idx).or_else(|| st.non_cond.get(&frame_idx)) else {
1492 if std::env::var("SAM3_VID_DEBUG").is_ok() {
1493 eprintln!("[vid] f{frame_idx} out: obj{id} has NO output at this frame");
1494 }
1495 continue;
1496 };
1497 if o.pred_masks.len() != MASK_SIDE * MASK_SIDE {
1498 if std::env::var("SAM3_VID_DEBUG").is_ok() {
1499 eprintln!("[vid] f{frame_idx} out: obj{id} mask len {} != {}", o.pred_masks.len(), MASK_SIDE * MASK_SIDE);
1500 }
1501 continue;
1502 }
1503 if o.pred_masks.iter().all(|&v| v <= 0.0) {
1504 if std::env::var("SAM3_VID_DEBUG").is_ok() {
1505 eprintln!("[vid] f{frame_idx} out: obj{id} mask is ALL-NEGATIVE (min {:.3}) -> no observation",
1506 o.pred_masks.iter().cloned().fold(f32::INFINITY, f32::min));
1507 }
1508 continue;
1509 }
1510 let score = s.score.get(&id).copied().unwrap_or(0.0);
1511 push_obs(&mut out, id, score, &o.pred_masks, fw, fh, false, true);
1512 }
1513 for (k, &di) in plan.new_inds.iter().enumerate() {
1515 let id = plan.new_ids[k];
1516 if plan.removed.contains(&id) {
1517 continue;
1518 }
1519 let mut m = dets[di].mask.clone();
1520 fill_holes_one(&mut m, self.cfg.fill_hole_area);
1521 let score = s.score.get(&id).copied().unwrap_or(dets[di].score);
1522 push_obs(&mut out, id, score, &m, fw, fh, true, false);
1523 }
1524 let hidden: HashSet<i64> = s.removed.union(&s.suppressed).copied().collect();
1526 out.retain(|o| !hidden.contains(&o.id));
1527 if out.len() > 1 {
1528 apply_non_overlap(&mut out);
1529 }
1530 out.sort_by_key(|o| o.id);
1531 Ok(out)
1532 }
1533}
1534
1535fn push_obs(
1536 out: &mut Vec<TrackObs>,
1537 id: i64,
1538 score: f32,
1539 logits: &[f32],
1540 fw: usize,
1541 fh: usize,
1542 from_detection: bool,
1543 from_tracker: bool,
1544) {
1545 if logits.len() != MASK_SIDE * MASK_SIDE {
1546 eprintln!("[vid] push_obs: id {id} logits len {} != {MASK_SIDE}", logits.len());
1547 return;
1548 }
1549 let flat = resize2d(logits, MASK_SIDE, MASK_SIDE, fh, fw);
1550 let mask: Vec<u8> = flat.iter().map(|&v| u8::from(v > 0.0)).collect();
1551 if mask.iter().all(|&v| v == 0) {
1552 eprintln!("[vid] push_obs: id {id} empty after resize to {fw}x{fh}");
1553 return;
1554 }
1555 let mut x0 = usize::MAX;
1556 let mut y0 = usize::MAX;
1557 let mut x1 = 0usize;
1558 let mut y1 = 0usize;
1559 for y in 0..fh {
1560 for x in 0..fw {
1561 if mask[y * fw + x] != 0 {
1562 x0 = x0.min(x);
1563 y0 = y0.min(y);
1564 x1 = x1.max(x);
1565 y1 = y1.max(y);
1566 }
1567 }
1568 }
1569 out.push(TrackObs {
1570 id,
1571 score,
1572 box_px: [x0 as f32, y0 as f32, x1 as f32, y1 as f32],
1573 mask,
1574 from_detection,
1575 from_tracker,
1576 });
1577}
1578
1579fn apply_non_overlap(out: &mut [TrackObs]) {
1582 let len = match out.first() {
1583 Some(o) => o.mask.len(),
1584 None => return,
1585 };
1586 for i in 0..len {
1587 let mut best = usize::MAX;
1588 let mut best_score = f32::NEG_INFINITY;
1589 for (k, o) in out.iter().enumerate() {
1590 if o.mask[i] == 0 {
1591 continue;
1592 }
1593 if o.score > best_score {
1594 best_score = o.score;
1595 best = k;
1596 }
1597 }
1598 if best == usize::MAX {
1599 continue;
1600 }
1601 for (k, o) in out.iter_mut().enumerate() {
1602 if k != best && o.mask[i] != 0 {
1603 o.mask[i] = 0;
1604 }
1605 }
1606 }
1607}
1608
1609pub fn mask_iou(a: &[u8], b: &[u8]) -> f32 {
1613 debug_assert_eq!(a.len(), b.len());
1614 let mut inter = 0u32;
1615 let mut union = 0u32;
1616 for (&x, &y) in a.iter().zip(b) {
1617 let x = x != 0;
1618 let y = y != 0;
1619 inter += u32::from(x && y);
1620 union += u32::from(x || y);
1621 }
1622 if union == 0 {
1623 1.0
1624 } else {
1625 inter as f32 / union as f32
1626 }
1627}
1628
1629fn fill_holes(masks: &mut [Vec<f32>], max_area: usize) {
1632 for m in masks.iter_mut() {
1633 fill_holes_one(m, max_area);
1634 }
1635}
1636
1637fn fill_holes_one(mask: &mut [f32], max_area: usize) {
1638 if max_area == 0 || mask.len() != MASK_SIDE * MASK_SIDE {
1639 return;
1640 }
1641 let (w, h) = (MASK_SIDE, MASK_SIDE);
1642 let bg: Vec<bool> = mask.iter().map(|&v| v <= 0.0).collect();
1643 let areas = cc_areas(&bg, w, h);
1644 for i in 0..mask.len() {
1645 if bg[i] && areas[i] > 0 && areas[i] <= max_area {
1646 mask[i] = 0.1;
1647 }
1648 }
1649 let fg: Vec<bool> = mask.iter().map(|&v| v > 0.0).collect();
1650 let total: usize = fg.iter().filter(|&&v| v).count();
1651 let thr = (total / 2).min(max_area);
1652 let areas = cc_areas(&fg, w, h);
1653 for i in 0..mask.len() {
1654 if fg[i] && areas[i] > 0 && areas[i] <= thr {
1655 mask[i] = -0.1;
1656 }
1657 }
1658}
1659
1660fn cc_areas(mask: &[bool], w: usize, h: usize) -> Vec<usize> {
1662 let mut label = vec![usize::MAX; mask.len()];
1663 let mut sizes: Vec<usize> = Vec::new();
1664 let mut stack: Vec<usize> = Vec::new();
1665 for start in 0..mask.len() {
1666 if !mask[start] || label[start] != usize::MAX {
1667 continue;
1668 }
1669 let id = sizes.len();
1670 stack.clear();
1671 stack.push(start);
1672 label[start] = id;
1673 let mut count = 0usize;
1674 while let Some(p) = stack.pop() {
1675 count += 1;
1676 let (x, y) = (p % w, p / w);
1677 for (nx, ny) in
1678 [(x.wrapping_sub(1), y), (x + 1, y), (x, y.wrapping_sub(1)), (x, y + 1)]
1679 {
1680 if nx >= w || ny >= h {
1681 continue;
1682 }
1683 let q = ny * w + nx;
1684 if mask[q] && label[q] == usize::MAX {
1685 label[q] = id;
1686 stack.push(q);
1687 }
1688 }
1689 }
1690 sizes.push(count);
1691 }
1692 label
1693 .into_iter()
1694 .map(|l| if l == usize::MAX { 0 } else { sizes[l] })
1695 .collect()
1696}
1697
1698fn suppress_area_shrink(masks: &[Vec<f32>]) -> Vec<Vec<f32>> {
1701 if masks.len() <= 1 {
1702 return masks.to_vec();
1703 }
1704 let len = masks[0].len();
1705 let mut non_overlap: Vec<Vec<f32>> = masks.to_vec();
1706 for i in 0..len {
1707 let mut best = 0usize;
1708 let mut best_v = f32::NEG_INFINITY;
1709 for (k, m) in masks.iter().enumerate() {
1710 let v = if m[i] > 0.0 { m[i] } else { -10.0 };
1711 if v > best_v {
1712 best_v = v;
1713 best = k;
1714 }
1715 }
1716 for (k, m) in non_overlap.iter_mut().enumerate() {
1717 if k != best && m[i] > 0.0 {
1718 m[i] = -10.0;
1719 }
1720 }
1721 }
1722 let mut out = masks.to_vec();
1723 for (k, m) in masks.iter().enumerate() {
1724 let before = m.iter().filter(|&&v| v > 0.0).count();
1725 let after = non_overlap[k].iter().filter(|&&v| v > 0.0).count();
1726 if (after as f32) / (before.max(1) as f32) < 0.3 {
1727 for v in out[k].iter_mut() {
1728 if *v > -10.0 {
1729 *v = -10.0;
1730 }
1731 }
1732 }
1733 }
1734 out
1735}
1736
1737fn relu_vec(v: &[f32]) -> Vec<f32> {
1738 v.iter().map(|x| x.max(0.0)).collect()
1739}
1740
1741fn argmax(v: &[f32]) -> usize {
1742 v.iter()
1743 .enumerate()
1744 .fold((0usize, f32::NEG_INFINITY), |(bi, bv), (i, &x)| {
1745 if x > bv { (i, x) } else { (bi, bv) }
1746 })
1747 .0
1748}
1749
1750fn depthwise(
1752 w: &[f32],
1753 b: &[f32],
1754 x: &[f32],
1755 c: usize,
1756 h: usize,
1757 wid: usize,
1758 k: usize,
1759 pool: Option<&Pool>,
1760) -> Vec<f32> {
1761 let pad = k / 2;
1762 let mut out = vec![0f32; c * h * wid];
1763 let ob = out.as_mut_ptr() as usize;
1764 let run = |s: usize, e: usize| {
1765 let o = unsafe { std::slice::from_raw_parts_mut(ob as *mut f32, c * h * wid) };
1766 for idx in s..e {
1767 let ch = idx / (h * wid);
1768 let rem = idx % (h * wid);
1769 let (y, x2) = (rem / wid, rem % wid);
1770 let mut acc = b[ch];
1771 for dy in 0..k {
1772 let iy = y as isize + dy as isize - pad as isize;
1773 if iy < 0 || iy >= h as isize {
1774 continue;
1775 }
1776 for dx in 0..k {
1777 let ix = x2 as isize + dx as isize - pad as isize;
1778 if ix < 0 || ix >= wid as isize {
1779 continue;
1780 }
1781 acc += w[(ch * k + dy) * k + dx]
1782 * x[ch * h * wid + iy as usize * wid + ix as usize];
1783 }
1784 }
1785 o[idx] = acc;
1786 }
1787 };
1788 match pool {
1789 Some(p) => p.run_rows(c * h * wid, &run),
1790 None => run(0, c * h * wid),
1791 }
1792 out
1793}
1794
1795fn mask_downsample(net: &Net<'_>, mask: &[f32]) -> Result<Vec<f32>> {
1797 let w = net.v("trk.mask_downsample.weight")?;
1798 let b = net.v("trk.mask_downsample.bias")?;
1799 Ok(sam3::conv2d(&w, &b, mask, 1, IMG, IMG, 4, 4, 4, 0, net.pool))
1800}
1801
1802#[cfg(test)]
1803mod tests {
1804 use super::*;
1805
1806 fn cmf_path() -> Option<std::path::PathBuf> {
1807 [" /home/hermes/models/sam3-q8_2f.cmf"]
1808 .iter()
1809 .map(|s| std::path::PathBuf::from(s.trim()))
1810 .find(|p| p.exists())
1811 }
1812
1813 #[test]
1818 fn tracker_memory_smoke() {
1819 let Some(path) = cmf_path() else {
1820 eprintln!("SKIP tracker_memory_smoke: cmf not found");
1821 return;
1822 };
1823 let m = Sam3Video::open(path.to_str().expect("path")).expect("open video model");
1824 let net = m.net();
1825
1826 let fill = |len: usize, k: f32| -> Vec<f32> {
1828 (0..len)
1829 .map(|i| {
1830 let h = (i as u32).wrapping_mul(2654435761) >> 16;
1831 (h % 1000) as f32 / 1000.0 * k
1832 })
1833 .collect()
1834 };
1835 let feats = TrkFeats {
1836 l0: fill(32 * MASK_SIDE * MASK_SIDE, 0.05),
1837 l1: fill(64 * 144 * 144, 0.05),
1838 l2: fill(HID * EMB * EMB, 0.05),
1839 };
1840
1841 let mut mask = vec![0f32; MASK_SIDE * MASK_SIDE];
1843 for y in 48..144 {
1844 for x in 48..144 {
1845 mask[y * MASK_SIDE + x] = 1.0;
1846 }
1847 }
1848 let mut s = Session::new();
1849 s.obj_ids.push(0);
1850 s.max_obj_id = 0;
1851
1852 let (o, high) = m.use_mask_as_output(&net, &feats, &mask).expect("mask-as-output");
1853 assert_eq!(o.pred_masks.len(), MASK_SIDE * MASK_SIDE, "low-res mask shape");
1854 assert_eq!(o.pointer.len(), HID, "object pointer width");
1855 assert_eq!(high.len(), IMG * IMG, "high-res mask shape");
1856 assert!(o.obj_score > 0.0, "object score {}", o.obj_score);
1857 let area = 96 * 96;
1858 let pos = o.pred_masks.iter().filter(|&&v| v > 0.0).count();
1859 assert!(
1860 (pos as i64 - area as i64).abs() <= area as i64 / 4,
1861 "mask-as-output area {pos} vs input {area}"
1862 );
1863
1864 m.store(&mut s, 0, 0, true, o.clone());
1866 let pix = feats.l2.clone();
1867 m.encode_into(&net, &mut s, 0, vec![(0, o.clone(), high)], &pix, IMG, IMG, true)
1868 .expect("memory encode");
1869 assert!(s.objs[&0].cond[&0].mem_feat.is_some(), "memory features stored");
1870 assert_eq!(s.objs[&0].cond[&0].mem_feat.as_ref().unwrap().len(), EMB * EMB * MEM);
1871
1872 let (o1, high1) = m.propagate(&net, &s, 0, 1, &feats).expect("propagate");
1874 assert_eq!(o1.pred_masks.len(), MASK_SIDE * MASK_SIDE);
1875 assert_eq!(high1.len(), IMG * IMG);
1876 let pos1 = o1.pred_masks.iter().filter(|&&v| v > 0.0).count();
1877 let mn = o1.pred_masks.iter().cloned().fold(f32::INFINITY, f32::min);
1878 let mx = o1.pred_masks.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1879 eprintln!(
1880 "tracker smoke: mask-as-output {} px, propagated {} px (logit {:.2}..{:.2}), obj_score {:.3} -> {:.3}",
1881 pos,
1882 pos1,
1883 mn,
1884 mx,
1885 o.obj_score,
1886 o1.obj_score
1887 );
1888 assert!(o1.obj_score.is_finite(), "object score must be finite");
1893 assert!(mn.is_finite() && mx.is_finite(), "mask logits must be finite");
1894 assert!(mx > mn, "the decoder must not emit a constant mask ({mn})");
1895 }
1896
1897 fn f32s(p: &std::path::Path) -> Vec<f32> {
1898 let b = std::fs::read(p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()));
1899 b.chunks_exact(4).map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]])).collect()
1900 }
1901
1902 fn fx_dir() -> std::path::PathBuf {
1903 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/sam3_video")
1904 }
1905
1906 #[test]
1913 fn tracker_fixture_parity() {
1914 let fx = fx_dir();
1915 let Some(path) = cmf_path() else {
1916 eprintln!("SKIP tracker_fixture_parity: cmf not found");
1917 return;
1918 };
1919 if !fx.join("f1/obj0/non_cond_frame_outputs/object_score_logits.f32").exists() {
1920 eprintln!("SKIP tracker_fixture_parity: fixtures missing at {}", fx.display());
1921 return;
1922 }
1923 let m = Sam3Video::open(path.to_str().unwrap()).expect("open");
1924 let net = m.net();
1925 let fill = |len: usize| vec![0f32; len];
1926 let feats0 = TrkFeats { l0: fill(32 * MASK_SIDE * MASK_SIDE), l1: fill(64 * 144 * 144), l2: fill(HID * EMB * EMB) };
1927 let _ = &feats0;
1928
1929 let pix0 = f32s(&fx.join("f0/neck_l2.f32"));
1931 assert_eq!(pix0.len(), EMB * EMB * HID);
1932 let pix0_chw = pix0_to_chw(&pix0); let hi0 = f32s(&fx.join("f0/obj0/cond_frame_outputs/high_res_masks.f32"));
1934 let bin: Vec<f32> = hi0.iter().map(|&v| if v > 0.0 { 1.0 } else { 0.0 }).collect();
1935 assert_eq!(bin.len(), IMG * IMG);
1936 let ds = mask_downsample(&net, &bin).expect("mask_downsample");
1937 let ds = crate::sam3vid_net::resize2d(&ds, IMG / 4, IMG / 4, MASK_SIDE, MASK_SIDE);
1938 let dense = net.mask_prompt(&ds).expect("mask prompt");
1939 let sparse = net.empty_sparse().expect("sparse");
1940 let pos = net.image_pos().expect("pos");
1941 let (_m, iou0, sc0, toks) = net
1942 .mask_decoder(&pix0_to_chw(&pix0), &pos, &dense, &sparse, (&feats0.l0, &feats0.l1), true)
1943 .expect("decoder f0");
1944 let n = toks.len() / HID;
1945 let best = argmax(&iou0).min(n - 1);
1946 let ptr = m.object_pointer(&net, &toks[best * HID..(best + 1) * HID], sc0 > 0.0).expect("pointer");
1947 let want = f32s(&fx.join("f0/obj0/cond_frame_outputs/object_pointer.f32"));
1948 let cos = dot(&ptr, &want) / (norm(&ptr) * norm(&want) + 1e-9);
1949 eprintln!("fixture f0 pointer: cos={cos:.4} obj_score={sc0:.3} (ref mask prompt)");
1950 assert!(cos >= 0.99, "frame-0 object pointer cosine {cos:.4} vs reference");
1951
1952 let pix1 = f32s(&fx.join("f1/neck_l2.f32"));
1954 let mem_feat = f32s(&fx.join("f0/obj0/cond_frame_outputs/maskmem_features.f32"));
1955 let mem_pos = f32s(&fx.join("f0/obj0/cond_frame_outputs/maskmem_pos_enc.f32"));
1956 let mem_feat_ref = mem_feat.clone();
1957 let memories = vec![(0usize, mem_feat, mem_pos)];
1958 let pointers = vec![(1usize, want.clone())];
1959 let neck_pos = Net::neck_pos();
1960 let cond = m.memory_cond(&net, &pix1, &neck_pos, &memories, &pointers, 8).expect("memory cond");
1961 let cond_ref_path = fx.join("f1/memory_conditioned.f32");
1962 if cond_ref_path.exists() {
1963 let want_cond = f32s(&cond_ref_path);
1964 let cond_cos = dot(&cond, &want_cond) / (norm(&cond) * norm(&want_cond) + 1e-9);
1965 let cond_md = cond.iter().zip(&want_cond).map(|(a, b)| (a - b).abs()).fold(0f32, f32::max);
1966 eprintln!("fixture f1 memory conditioning: cos={cond_cos:.6} maxdiff={cond_md:.4}");
1967 assert!(cond_cos >= 0.99, "frame-1 memory conditioning cosine {cond_cos:.6}");
1968 }
1969 let dense = net.no_mask_prompt().expect("no mask");
1970 let (_m, iou1, sc1, t1) = net
1971 .mask_decoder(&pix0_to_chw(&cond), &pos, &dense, &sparse, (&feats0.l0, &feats0.l1), true)
1972 .expect("decoder f1");
1973 let want_sc = f32s(&fx.join("f1/obj0/non_cond_frame_outputs/object_score_logits.f32"))[0];
1974 eprintln!(
1975 "fixture f1: obj_score ours={sc1:.3} reference={want_sc:.3} (delta {:.3}), iou={iou1:?}",
1976 (sc1 - want_sc).abs()
1977 );
1978 assert!(
1979 (sc1 - want_sc).abs() < 1.0,
1980 "frame-1 object score {sc1:.3} vs reference {want_sc:.3}"
1981 );
1982 assert!(sc1 > 0.0, "the tracked object must read as present (got {sc1:.3})");
1983 let best1 = argmax(&iou1).min(t1.len() / HID - 1);
1984 let ptr1 = m
1985 .object_pointer(&net, &t1[best1 * HID..(best1 + 1) * HID], sc1 > 0.0)
1986 .expect("pointer f1");
1987 let want_ptr1 = f32s(&fx.join("f1/obj0/non_cond_frame_outputs/object_pointer.f32"));
1988 for (j, tok) in t1.chunks_exact(HID).enumerate() {
1989 let p = m.object_pointer(&net, tok, sc1 > 0.0).expect("pointer candidate f1");
1990 let c = dot(&p, &want_ptr1) / (norm(&p) * norm(&want_ptr1) + 1e-9);
1991 eprintln!("fixture f1 pointer candidate {j}: cos={c:.5} iou={:.6}", iou1[j]);
1992 }
1993 let ptr_cos1 = dot(&ptr1, &want_ptr1) / (norm(&ptr1) * norm(&want_ptr1) + 1e-9);
1994 eprintln!("fixture f1 pointer: cos={ptr_cos1:.5}");
1995
1996 let pix2 = f32s(&fx.join("f2/neck_l2.f32"));
2001 let memories2 = vec![
2002 (0usize, mem_feat_ref.clone(), f32s(&fx.join("f0/obj0/cond_frame_outputs/maskmem_pos_enc.f32"))),
2003 (
2004 1usize,
2005 f32s(&fx.join("f1/obj0/non_cond_frame_outputs/maskmem_features.f32")),
2006 f32s(&fx.join("f1/obj0/non_cond_frame_outputs/maskmem_pos_enc.f32")),
2007 ),
2008 ];
2009 let pointers2 = vec![(2usize, want.clone()), (1usize, want_ptr1.clone())];
2010 let cond2 = m.memory_cond(&net, &pix2, &neck_pos, &memories2, &pointers2, 8).expect("memory cond f2");
2011 let cond2_ref_path = fx.join("f2/memory_conditioned.f32");
2012 if cond2_ref_path.exists() {
2013 let want_cond2 = f32s(&cond2_ref_path);
2014 let cond_cos2 = dot(&cond2, &want_cond2) / (norm(&cond2) * norm(&want_cond2) + 1e-9);
2015 let cond_md2 = cond2.iter().zip(&want_cond2).map(|(a, b)| (a - b).abs()).fold(0f32, f32::max);
2016 eprintln!("fixture f2 memory conditioning: cos={cond_cos2:.6} maxdiff={cond_md2:.4}");
2017 assert!(cond_cos2 >= 0.99, "frame-2 memory conditioning cosine {cond_cos2:.6}");
2018 }
2019 let (_m2, iou2, sc2, t2) = net
2020 .mask_decoder(&pix0_to_chw(&cond2), &pos, &dense, &sparse, (&feats0.l0, &feats0.l1), true)
2021 .expect("decoder f2");
2022 let want_sc2 = f32s(&fx.join("f2/obj0/non_cond_frame_outputs/object_score_logits.f32"))[0];
2023 let best2 = argmax(&iou2).min(t2.len() / HID - 1);
2024 let ptr2 = m
2025 .object_pointer(&net, &t2[best2 * HID..(best2 + 1) * HID], sc2 > 0.0)
2026 .expect("pointer f2");
2027 let want_ptr2 = f32s(&fx.join("f2/obj0/non_cond_frame_outputs/object_pointer.f32"));
2028 let ptr_cos2 = dot(&ptr2, &want_ptr2) / (norm(&ptr2) * norm(&want_ptr2) + 1e-9);
2029 eprintln!("fixture f2: obj_score ours={sc2:.3} reference={want_sc2:.3}, pointer cos={ptr_cos2:.5}");
2030 assert!(ptr_cos1 >= 0.99, "frame-1 object pointer cosine {ptr_cos1:.5}");
2031 assert!((sc2 - want_sc2).abs() < 1.0, "frame-2 object score {sc2:.3} vs {want_sc2:.3}");
2032 assert!(ptr_cos2 >= 0.99, "frame-2 object pointer cosine {ptr_cos2:.5}");
2033
2034 for from_pts in [true, false] {
2038 let (mf, mp) = m
2039 .encode_memory(&net, &pix0_chw, &hi0, IMG, IMG, sc0, from_pts)
2040 .expect("encode_memory");
2041 let cos = dot(&mf, &mem_feat_ref) / (norm(&mf) * norm(&mem_feat_ref) + 1e-9);
2042 let md = mf
2043 .iter()
2044 .zip(&mem_feat_ref)
2045 .map(|(a, b)| (a - b).abs())
2046 .fold(0f32, f32::max);
2047 eprintln!(
2048 "fixture mem encode from_pts={from_pts}: cos={cos:.5} maxdiff={md:.4} rms={:.4} (ref rms {:.4})",
2049 norm(&mf) / (mf.len() as f32).sqrt(),
2050 norm(&mem_feat_ref) / (mem_feat_ref.len() as f32).sqrt()
2051 );
2052 assert!(cos >= 0.99, "memory features cosine {cos:.5} (from_pts={from_pts})");
2053 let want_mp = f32s(&fx.join("f0/obj0/cond_frame_outputs/maskmem_pos_enc.f32"));
2054 let pos_cos = dot(&mp, &want_mp) / (norm(&mp) * norm(&want_mp) + 1e-9);
2055 assert!(pos_cos >= 0.99, "memory position cosine {pos_cos:.5}");
2056 }
2057 }
2058
2059 fn pix0_to_chw(rows: &[f32]) -> Vec<f32> {
2060 sam3::rows_to_chw(rows, EMB, EMB)
2061 }
2062
2063 fn dot(a: &[f32], b: &[f32]) -> f32 {
2064 a.iter().zip(b).map(|(x, y)| x * y).sum()
2065 }
2066
2067 fn norm(a: &[f32]) -> f32 {
2068 a.iter().map(|x| x * x).sum::<f32>().sqrt()
2069 }
2070}