1use crate::error::{Result, VisionError};
7use scirs2_core::ndarray::{Array1, Array2};
8
9use super::types::{PromptType, SAMConfig, SegmentationPrompt};
10
11const NUM_FREQUENCIES: usize = 64;
17
18fn positional_encoding_2d(x_norm: f64, y_norm: f64) -> Array1<f64> {
23 let dim = 4 * NUM_FREQUENCIES;
24 let mut enc = Array1::zeros(dim);
25 for i in 0..NUM_FREQUENCIES {
26 let freq = std::f64::consts::PI * 2.0_f64.powi(i as i32);
27 enc[4 * i] = (x_norm * freq).sin();
28 enc[4 * i + 1] = (x_norm * freq).cos();
29 enc[4 * i + 2] = (y_norm * freq).sin();
30 enc[4 * i + 3] = (y_norm * freq).cos();
31 }
32 enc
33}
34
35fn project_to_embed_dim(enc: &Array1<f64>, embed_dim: usize) -> Array1<f64> {
38 let src_len = enc.len();
39 let mut out = Array1::zeros(embed_dim);
40 let copy_len = src_len.min(embed_dim);
41 for i in 0..copy_len {
42 out[i] = enc[i];
43 }
44 out
45}
46
47#[derive(Debug, Clone)]
53pub struct PromptEmbedding {
54 pub sparse_embeddings: Array2<f64>,
57 pub dense_embeddings: Array2<f64>,
60}
61
62#[derive(Debug, Clone)]
69pub struct PromptEncoder {
70 config: SAMConfig,
72 fg_token: Array1<f64>,
74 bg_token: Array1<f64>,
76}
77
78impl PromptEncoder {
79 pub fn new(config: &SAMConfig) -> Self {
81 let mut fg = Array1::zeros(config.embed_dim);
83 let mut bg = Array1::zeros(config.embed_dim);
84 for i in 0..config.embed_dim {
85 fg[i] = 0.1 * ((i as f64 * 0.1).sin());
86 bg[i] = -0.1 * ((i as f64 * 0.1).cos());
87 }
88 Self {
89 config: config.clone(),
90 fg_token: fg,
91 bg_token: bg,
92 }
93 }
94
95 pub fn encode(
106 &self,
107 prompt: &SegmentationPrompt,
108 image_size: (usize, usize),
109 ) -> Result<PromptEmbedding> {
110 let (img_h, img_w) = image_size;
111 if img_h == 0 || img_w == 0 {
112 return Err(VisionError::InvalidParameter(
113 "prompt_encoder: image_size must be non-zero".into(),
114 ));
115 }
116
117 #[allow(unreachable_patterns)]
118 match &prompt.prompt_type {
119 PromptType::Point {
120 x,
121 y,
122 is_foreground,
123 } => self.encode_point(*x, *y, *is_foreground, img_h, img_w),
124 PromptType::BoundingBox { x1, y1, x2, y2 } => {
125 self.encode_box(*x1, *y1, *x2, *y2, img_h, img_w)
126 }
127 PromptType::MaskPrompt { mask } => self.encode_mask(mask, img_h, img_w),
128 PromptType::MultiPoint { points } => self.encode_multi_point(points, img_h, img_w),
129 _ => Err(VisionError::InvalidParameter(
130 "prompt_encoder: unknown prompt type variant".into(),
131 )),
132 }
133 }
134
135 fn encode_point(
138 &self,
139 x: usize,
140 y: usize,
141 is_foreground: bool,
142 img_h: usize,
143 img_w: usize,
144 ) -> Result<PromptEmbedding> {
145 let x_norm = x as f64 / img_w.max(1) as f64;
146 let y_norm = y as f64 / img_h.max(1) as f64;
147
148 let pos_enc = positional_encoding_2d(x_norm, y_norm);
149 let mut token = project_to_embed_dim(&pos_enc, self.config.embed_dim);
150
151 let label_token = if is_foreground {
153 &self.fg_token
154 } else {
155 &self.bg_token
156 };
157 for i in 0..self.config.embed_dim {
158 token[i] += label_token[i];
159 }
160
161 let sparse = Array2::from_shape_vec((1, self.config.embed_dim), token.to_vec())
162 .map_err(|e| VisionError::OperationError(format!("sparse reshape: {e}")))?;
163
164 Ok(PromptEmbedding {
165 sparse_embeddings: sparse,
166 dense_embeddings: Array2::zeros((0, self.config.embed_dim)),
167 })
168 }
169
170 fn encode_box(
173 &self,
174 x1: usize,
175 y1: usize,
176 x2: usize,
177 y2: usize,
178 img_h: usize,
179 img_w: usize,
180 ) -> Result<PromptEmbedding> {
181 let tl = self.point_token(x1, y1, true, img_h, img_w);
183 let br = self.point_token(x2, y2, false, img_h, img_w);
184
185 let mut data = Vec::with_capacity(2 * self.config.embed_dim);
186 data.extend(tl.iter());
187 data.extend(br.iter());
188
189 let sparse = Array2::from_shape_vec((2, self.config.embed_dim), data)
190 .map_err(|e| VisionError::OperationError(format!("box sparse reshape: {e}")))?;
191
192 Ok(PromptEmbedding {
193 sparse_embeddings: sparse,
194 dense_embeddings: Array2::zeros((0, self.config.embed_dim)),
195 })
196 }
197
198 fn encode_mask(
201 &self,
202 mask: &Array2<f64>,
203 img_h: usize,
204 img_w: usize,
205 ) -> Result<PromptEmbedding> {
206 let (mh, mw) = mask.dim();
207 if mh == 0 || mw == 0 {
208 return Err(VisionError::InvalidParameter(
209 "prompt_encoder: mask must be non-empty".into(),
210 ));
211 }
212
213 let ds_h = img_h.div_ceil(4);
215 let ds_w = img_w.div_ceil(4);
216 let num_spatial = ds_h * ds_w;
217
218 let mut dense = Array2::zeros((num_spatial, self.config.embed_dim));
219
220 for dy in 0..ds_h {
221 for dx in 0..ds_w {
222 let sy = dy * 4;
224 let sx = dx * 4;
225 let mut sum = 0.0f64;
226 let mut count = 0usize;
227 for ky in 0..4 {
228 for kx in 0..4 {
229 let my = sy + ky;
230 let mx = sx + kx;
231 if my < mh && mx < mw {
232 sum += mask[[my, mx]];
233 count += 1;
234 }
235 }
236 }
237 let avg = if count > 0 { sum / count as f64 } else { 0.0 };
238
239 let x_norm = dx as f64 / ds_w.max(1) as f64;
242 let y_norm = dy as f64 / ds_h.max(1) as f64;
243 let pos = positional_encoding_2d(x_norm, y_norm);
244 let proj = project_to_embed_dim(&pos, self.config.embed_dim);
245 let idx = dy * ds_w + dx;
246 for c in 0..self.config.embed_dim {
247 dense[[idx, c]] = avg * proj[c];
248 }
249 }
250 }
251
252 Ok(PromptEmbedding {
253 sparse_embeddings: Array2::zeros((0, self.config.embed_dim)),
254 dense_embeddings: dense,
255 })
256 }
257
258 fn encode_multi_point(
261 &self,
262 points: &[(usize, usize, bool)],
263 img_h: usize,
264 img_w: usize,
265 ) -> Result<PromptEmbedding> {
266 if points.is_empty() {
267 return Err(VisionError::InvalidParameter(
268 "prompt_encoder: MultiPoint must have at least one point".into(),
269 ));
270 }
271
272 let n = points.len();
273 let mut data = Vec::with_capacity(n * self.config.embed_dim);
274 for &(x, y, is_fg) in points {
275 let tok = self.point_token(x, y, is_fg, img_h, img_w);
276 data.extend(tok.iter());
277 }
278
279 let sparse = Array2::from_shape_vec((n, self.config.embed_dim), data)
280 .map_err(|e| VisionError::OperationError(format!("multi-point reshape: {e}")))?;
281
282 Ok(PromptEmbedding {
283 sparse_embeddings: sparse,
284 dense_embeddings: Array2::zeros((0, self.config.embed_dim)),
285 })
286 }
287
288 fn point_token(
291 &self,
292 x: usize,
293 y: usize,
294 is_fg: bool,
295 img_h: usize,
296 img_w: usize,
297 ) -> Array1<f64> {
298 let x_norm = x as f64 / img_w.max(1) as f64;
299 let y_norm = y as f64 / img_h.max(1) as f64;
300 let pos = positional_encoding_2d(x_norm, y_norm);
301 let mut tok = project_to_embed_dim(&pos, self.config.embed_dim);
302 let label = if is_fg {
303 &self.fg_token
304 } else {
305 &self.bg_token
306 };
307 for i in 0..self.config.embed_dim {
308 tok[i] += label[i];
309 }
310 tok
311 }
312}
313
314#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::prompt_segmentation::types::{PromptType, SAMConfig, SegmentationPrompt};
322 use scirs2_core::ndarray::Array2;
323
324 fn small_config() -> SAMConfig {
325 SAMConfig {
326 image_size: 32,
327 embed_dim: 16,
328 num_mask_outputs: 3,
329 iou_head_hidden: 16,
330 encoder_stages: 2,
331 }
332 }
333
334 #[test]
335 fn test_encode_point() {
336 let cfg = small_config();
337 let enc = PromptEncoder::new(&cfg);
338 let prompt = SegmentationPrompt::new(PromptType::Point {
339 x: 5,
340 y: 10,
341 is_foreground: true,
342 });
343 let emb = enc.encode(&prompt, (32, 32)).expect("encode point");
344 assert_eq!(emb.sparse_embeddings.dim(), (1, 16));
345 assert_eq!(emb.dense_embeddings.dim().0, 0);
346 }
347
348 #[test]
349 fn test_encode_box() {
350 let cfg = small_config();
351 let enc = PromptEncoder::new(&cfg);
352 let prompt = SegmentationPrompt::new(PromptType::BoundingBox {
353 x1: 2,
354 y1: 3,
355 x2: 20,
356 y2: 25,
357 });
358 let emb = enc.encode(&prompt, (32, 32)).expect("encode box");
359 assert_eq!(emb.sparse_embeddings.dim(), (2, 16));
361 }
362
363 #[test]
364 fn test_encode_mask() {
365 let cfg = small_config();
366 let enc = PromptEncoder::new(&cfg);
367 let mask = Array2::from_elem((32, 32), 1.0);
368 let prompt = SegmentationPrompt::new(PromptType::MaskPrompt { mask });
369 let emb = enc.encode(&prompt, (32, 32)).expect("encode mask");
370 assert_eq!(emb.dense_embeddings.dim(), (64, 16));
372 assert_eq!(emb.sparse_embeddings.dim().0, 0);
373 }
374
375 #[test]
376 fn test_encode_multipoint() {
377 let cfg = small_config();
378 let enc = PromptEncoder::new(&cfg);
379 let pts = vec![(1, 2, true), (10, 15, false), (5, 5, true)];
380 let prompt = SegmentationPrompt::new(PromptType::MultiPoint { points: pts });
381 let emb = enc.encode(&prompt, (32, 32)).expect("encode multipoint");
382 assert_eq!(emb.sparse_embeddings.dim(), (3, 16));
383 }
384
385 #[test]
386 fn test_encode_empty_multipoint_err() {
387 let cfg = small_config();
388 let enc = PromptEncoder::new(&cfg);
389 let prompt = SegmentationPrompt::new(PromptType::MultiPoint { points: vec![] });
390 assert!(enc.encode(&prompt, (32, 32)).is_err());
391 }
392
393 #[test]
394 fn test_encode_zero_image_size_err() {
395 let cfg = small_config();
396 let enc = PromptEncoder::new(&cfg);
397 let prompt = SegmentationPrompt::new(PromptType::Point {
398 x: 0,
399 y: 0,
400 is_foreground: true,
401 });
402 assert!(enc.encode(&prompt, (0, 0)).is_err());
403 }
404}