1515
1616from __future__ import annotations
1717
18+ import math
19+ from dataclasses import dataclass , field
20+ from typing import Any
21+
1822import mlx .core as mx
1923import mlx .nn as nn
2024
2529from .real_video_config import Sam3TrackerVideoConfig
2630from .real_vision import Sam3VisionConfig , Sam3VisionNeck
2731
28- __all__ = ["Sam3TrackerVideoModel" , "Sam3VideoModel" , "build_sam3_video_real" ]
32+ __all__ = [
33+ "Sam3TrackerStageOutput" ,
34+ "Sam3TrackerVideoModel" ,
35+ "Sam3VideoModel" ,
36+ "build_sam3_video_real" ,
37+ ]
38+
39+
40+ def _upsample_nhwc (x : mx .array , factor : int ) -> mx .array :
41+ """Nearest-neighbour integer upsample of an NHWC tensor along H and W."""
42+ if factor == 1 :
43+ return x
44+ return mx .repeat (mx .repeat (x , factor , axis = 1 ), factor , axis = 2 )
45+
46+
47+ def _get_1d_sine_pe (positions : mx .array , dim : int , temperature : float = 10000.0 ) -> mx .array :
48+ """1D sinusoidal positional encoding of ``positions`` -> ``[..., dim]`` (SAM2 ``get_1d_sine_pe``)."""
49+ half = dim // 2
50+ dim_t = temperature ** (2.0 * (mx .arange (half ).astype (mx .float32 ) // 1 ) / dim )
51+ angles = positions [..., None ] / dim_t
52+ return mx .concatenate ([mx .sin (angles ), mx .cos (angles )], axis = - 1 )
53+
54+
55+ @dataclass
56+ class Sam3TrackerStageOutput :
57+ """One frame's faithful tracker output (object batch ``B`` along axis 0)."""
58+
59+ low_res_masks : mx .array # NHWC [B, 4g, 4g, 1]
60+ high_res_masks : mx .array # NHWC [B, 16g, 16g, 1]
61+ iou_pred : mx .array # [B, 1, 1]
62+ object_score_logits : mx .array # [B, 1, 1]
63+ obj_ptr : mx .array # [B, hidden_dim]
64+ maskmem_features : mx .array | None = None # NHWC [B, g, g, mem_dim]
65+ maskmem_pos_enc : mx .array | None = None # NHWC [B, g, g, mem_dim]
66+ extra : dict [str , Any ] = field (default_factory = dict )
2967
3068
3169class Sam3TrackerVideoModel (nn .Module ):
@@ -50,6 +88,138 @@ def __init__(self, config: Sam3TrackerVideoConfig, hidden_dim: int = 256, mem_di
5088 self .no_object_pointer = mx .zeros ((1 , hidden_dim ))
5189 self .occlusion_spatial_embedding_parameter = mx .zeros ((1 , mem_dim ))
5290
91+ self .hidden_dim = hidden_dim
92+ self .mem_dim = mem_dim
93+ self .num_maskmem = config .num_maskmem
94+
95+ # ---- faithful per-frame streaming step (slice 12) ------------------------
96+
97+ def _assemble_memory (
98+ self , previous_frames : list [Sam3TrackerStageOutput ], batch : int
99+ ) -> tuple [mx .array , mx .array , int ]:
100+ """Stack prior-frame spatial memory + object pointers -> ``([mem_seq,B,mem_dim], pos, num_ptr_tokens)``.
101+
102+ Spatial memory carries the learned ``memory_temporal_positional_encoding`` per slot; object
103+ pointers are split into ``hidden_dim // mem_dim`` tokens (appended last so memory-attention can
104+ exclude them from RoPE) with a sine temporal encoding projected to ``mem_dim``.
105+ """
106+ recent = previous_frames [- (self .num_maskmem - 1 ) :]
107+ spatial_mem : list [mx .array ] = []
108+ spatial_pos : list [mx .array ] = []
109+ for offset , frame in enumerate (reversed (recent )):
110+ t_pos = offset + 1 # 1 == most recent prior frame
111+ feats = frame .maskmem_features
112+ b , h , w , c = feats .shape
113+ spatial_mem .append (feats .reshape (b , h * w , c ).transpose (1 , 0 , 2 ))
114+ pos = frame .maskmem_pos_enc .reshape (b , h * w , c ).transpose (1 , 0 , 2 )
115+ spatial_pos .append (pos + self .memory_temporal_positional_encoding [self .num_maskmem - t_pos - 1 ])
116+
117+ obj_tokens : list [mx .array ] = []
118+ obj_token_pos : list [mx .array ] = []
119+ num_obj_ptr_tokens = 0
120+ if recent :
121+ stacked = mx .stack ([frame .obj_ptr for frame in recent ], axis = 0 ) # [n, B, hidden_dim]
122+ n = stacked .shape [0 ]
123+ tokens_per_ptr = self .hidden_dim // self .mem_dim
124+ stacked = stacked .reshape (n , batch , tokens_per_ptr , self .mem_dim )
125+ stacked = stacked .transpose (0 , 2 , 1 , 3 ).reshape (n * tokens_per_ptr , batch , self .mem_dim )
126+ rel = mx .arange (n ).astype (mx .float32 )
127+ sine = self .temporal_positional_encoding_projection_layer (_get_1d_sine_pe (rel , self .hidden_dim ))
128+ sine = mx .repeat (sine , tokens_per_ptr , axis = 0 )
129+ obj_tokens .append (stacked )
130+ obj_token_pos .append (mx .broadcast_to (sine [:, None , :], (n * tokens_per_ptr , batch , self .mem_dim )))
131+ num_obj_ptr_tokens = n * tokens_per_ptr
132+
133+ memory = mx .concatenate (spatial_mem + obj_tokens , axis = 0 )
134+ memory_pos = mx .concatenate (spatial_pos + obj_token_pos , axis = 0 )
135+ return memory , memory_pos , num_obj_ptr_tokens
136+
137+ def _condition_on_memory (
138+ self ,
139+ vision_features : mx .array ,
140+ vision_pos : mx .array ,
141+ previous_frames : list [Sam3TrackerStageOutput ] | None ,
142+ is_init_cond_frame : bool ,
143+ ) -> mx .array :
144+ """Fuse current NHWC features with the memory bank -> NHWC ``[B, g, g, C]``."""
145+ batch , height , width , channels = vision_features .shape
146+ if is_init_cond_frame or not previous_frames :
147+ # First frame: directly add the no-memory embedding (no transformer pass).
148+ return vision_features + self .no_memory_embedding .reshape (1 , 1 , 1 , channels )
149+
150+ memory , memory_pos , num_obj_ptr_tokens = self ._assemble_memory (previous_frames , batch )
151+ current = vision_features .reshape (batch , height * width , channels ).transpose (1 , 0 , 2 )
152+ current_pos = vision_pos .reshape (batch , height * width , channels ).transpose (1 , 0 , 2 )
153+ conditioned = self .memory_attention (
154+ current_vision_features = current ,
155+ memory = memory ,
156+ current_vision_position_embeddings = current_pos ,
157+ memory_position_embeddings = memory_pos ,
158+ num_object_pointer_tokens = num_obj_ptr_tokens ,
159+ ) # [1, B, seq, C]
160+ return conditioned .reshape (batch , height , width , channels )
161+
162+ def track_step (
163+ self ,
164+ * ,
165+ vision_features : mx .array , # NHWC [B, g, g, C]
166+ vision_pos : mx .array , # NHWC [B, g, g, C]
167+ high_res_features : list [mx .array ] | tuple [mx .array , mx .array ], # [4g-res, 2g-res] raw FPN, NHWC
168+ is_init_cond_frame : bool = False ,
169+ point_inputs : tuple [mx .array , mx .array ] | None = None , # (coords [B,N,2], labels [B,N])
170+ mask_inputs : mx .array | None = None , # NHWC [B, Hm, Wm, 1]
171+ previous_frames : list [Sam3TrackerStageOutput ] | None = None ,
172+ run_mem_encoder : bool = True ,
173+ ) -> Sam3TrackerStageOutput :
174+ """One faithful SAM2-style tracker step over a single frame (object batch ``B``)."""
175+ batch , grid_h , grid_w , channels = vision_features .shape
176+
177+ conditioned = self ._condition_on_memory (vision_features , vision_pos , previous_frames , is_init_cond_frame )
178+
179+ # High-res guides for mask upscaling (conv_s0 / conv_s1 own the channel projections).
180+ feat_s0 = self .mask_decoder .conv_s0 (high_res_features [0 ])
181+ feat_s1 = self .mask_decoder .conv_s1 (high_res_features [1 ])
182+
183+ # Sparse prompt: explicit points/box, else a single padding point (label -1).
184+ if point_inputs is not None :
185+ coords , labels = point_inputs
186+ else :
187+ coords = mx .zeros ((batch , 1 , 2 ))
188+ labels = - mx .ones ((batch , 1 ))
189+ sparse = self .prompt_encoder .encode_sparse (coords , labels )[:, None ] # [B, 1, N, C]
190+
191+ # Dense prompt + dense positional encoding.
192+ dense = self .prompt_encoder (mask_inputs , batch_size = batch ) # NHWC [B, g, g, C]
193+ image_pe = mx .broadcast_to (self .prompt_encoder .get_dense_pe (), (batch , grid_h , grid_w , channels ))
194+
195+ decoded = self .mask_decoder (conditioned , image_pe , sparse , dense , [feat_s0 , feat_s1 ], multimask_output = False )
196+ low_res_masks = decoded .masks [:, 0 , 0 ][..., None ] # NHWC [B, 4g, 4g, 1]
197+ high_res_masks = _upsample_nhwc (low_res_masks , 4 ) # NHWC [B, 16g, 16g, 1] for the memory encoder
198+
199+ # Object pointer from the SAM token, gated by object presence (occlusion handling).
200+ sam_token = decoded .sam_tokens_out [:, 0 , 0 ] # [B, C]
201+ obj_ptr = self .object_pointer_proj (sam_token )
202+ is_obj = (decoded .object_score_logits [:, 0 ] > 0 ).astype (obj_ptr .dtype ) # [B, 1]
203+ obj_ptr = is_obj * obj_ptr + (1.0 - is_obj ) * self .no_object_pointer
204+
205+ maskmem_features = maskmem_pos_enc = None
206+ if run_mem_encoder :
207+ mask_for_mem = mx .sigmoid (high_res_masks )
208+ mask_for_mem = mask_for_mem * self .config .sigmoid_scale_for_mem_enc + self .config .sigmoid_bias_for_mem_enc
209+ mem = self .memory_encoder (conditioned , mask_for_mem )
210+ maskmem_features = mem .vision_features
211+ maskmem_pos_enc = mem .vision_pos_enc
212+
213+ return Sam3TrackerStageOutput (
214+ low_res_masks = low_res_masks ,
215+ high_res_masks = high_res_masks ,
216+ iou_pred = decoded .iou_pred ,
217+ object_score_logits = decoded .object_score_logits ,
218+ obj_ptr = obj_ptr ,
219+ maskmem_features = maskmem_features ,
220+ maskmem_pos_enc = maskmem_pos_enc ,
221+ )
222+
53223
54224class Sam3VideoModel (nn .Module ):
55225 """Faithful SAM 3 video model: detector + tracker + tracker neck (1797 tensors)."""
0 commit comments