1use crate::error::{Result, VisionError};
9use scirs2_core::ndarray::Array2;
10
11use super::prompt_encoder::PromptEmbedding;
12use super::types::{SAMConfig, SegmentationResult};
13
14fn simple_attention(
26 queries: &Array2<f64>,
27 keys: &Array2<f64>,
28 values: &Array2<f64>,
29) -> Result<Array2<f64>> {
30 let (n_q, d) = queries.dim();
31 let (n_k, kd) = keys.dim();
32 let (n_v, d_v) = values.dim();
33
34 if d != kd {
35 return Err(VisionError::DimensionMismatch(format!(
36 "attention: query dim {d} != key dim {kd}"
37 )));
38 }
39 if n_k != n_v {
40 return Err(VisionError::DimensionMismatch(format!(
41 "attention: key count {n_k} != value count {n_v}"
42 )));
43 }
44
45 let scale = 1.0 / (d as f64).sqrt();
46
47 let mut out = Array2::zeros((n_q, d_v));
48 for q in 0..n_q {
49 let mut scores = vec![0.0f64; n_k];
51 let mut max_score = f64::NEG_INFINITY;
52 for k in 0..n_k {
53 let mut dot = 0.0f64;
54 for i in 0..d {
55 dot += queries[[q, i]] * keys[[k, i]];
56 }
57 scores[k] = dot * scale;
58 if scores[k] > max_score {
59 max_score = scores[k];
60 }
61 }
62
63 let mut sum_exp = 0.0f64;
65 for s in &mut scores {
66 *s = (*s - max_score).exp();
67 sum_exp += *s;
68 }
69 if sum_exp > 0.0 {
70 for s in &mut scores {
71 *s /= sum_exp;
72 }
73 }
74
75 for k in 0..n_k {
77 for v in 0..d_v {
78 out[[q, v]] += scores[k] * values[[k, v]];
79 }
80 }
81 }
82 Ok(out)
83}
84
85fn mlp_forward(input: &Array2<f64>, hidden: usize, out_dim: usize) -> Array2<f64> {
89 let (n, in_dim) = input.dim();
90
91 let gen = |total: usize, seed_start: u64, fan_in: usize| -> Vec<f64> {
93 let std_dev = (2.0 / fan_in as f64).sqrt();
94 let mut v = Vec::with_capacity(total);
95 let mut s = seed_start;
96 for _ in 0..total {
97 s = s.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
98 let u = (s >> 33) as f64 / (1u64 << 31) as f64 - 1.0;
99 v.push(u * std_dev);
100 }
101 v
102 };
103
104 let w1_data = gen(in_dim * hidden, 123, in_dim);
105 let w2_data = gen(hidden * out_dim, 456, hidden);
106
107 let mut h = Array2::zeros((n, hidden));
109 for i in 0..n {
110 for j in 0..hidden {
111 let mut val = 0.0f64;
112 for k in 0..in_dim {
113 val += input[[i, k]] * w1_data[k * hidden + j];
114 }
115 h[[i, j]] = val.max(0.0); }
117 }
118
119 let mut out = Array2::zeros((n, out_dim));
121 for i in 0..n {
122 for j in 0..out_dim {
123 let mut val = 0.0f64;
124 for k in 0..hidden {
125 val += h[[i, k]] * w2_data[k * out_dim + j];
126 }
127 out[[i, j]] = val;
128 }
129 }
130 out
131}
132
133fn upsample_2x(input: &Array2<f64>, h: usize, w: usize) -> Result<(usize, usize, Array2<f64>)> {
137 let (n, c) = input.dim();
138 if n != h * w {
139 return Err(VisionError::DimensionMismatch(format!(
140 "upsample: n={n} != h*w={}",
141 h * w
142 )));
143 }
144
145 let oh = h * 2;
146 let ow = w * 2;
147 let mut out = Array2::zeros((oh * ow, c));
148
149 for oy in 0..oh {
150 for ox in 0..ow {
151 let sy = (oy as f64) / 2.0;
153 let sx = (ox as f64) / 2.0;
154
155 let y0 = (sy.floor() as usize).min(h.saturating_sub(1));
156 let x0 = (sx.floor() as usize).min(w.saturating_sub(1));
157 let y1 = (y0 + 1).min(h.saturating_sub(1));
158 let x1 = (x0 + 1).min(w.saturating_sub(1));
159
160 let fy = sy - y0 as f64;
161 let fx = sx - x0 as f64;
162
163 let w00 = (1.0 - fy) * (1.0 - fx);
164 let w01 = (1.0 - fy) * fx;
165 let w10 = fy * (1.0 - fx);
166 let w11 = fy * fx;
167
168 let i00 = y0 * w + x0;
169 let i01 = y0 * w + x1;
170 let i10 = y1 * w + x0;
171 let i11 = y1 * w + x1;
172 let oi = oy * ow + ox;
173
174 for ch in 0..c {
175 out[[oi, ch]] = w00 * input[[i00, ch]]
176 + w01 * input[[i01, ch]]
177 + w10 * input[[i10, ch]]
178 + w11 * input[[i11, ch]];
179 }
180 }
181 }
182 Ok((oh, ow, out))
183}
184
185#[derive(Debug, Clone)]
191pub struct MaskDecoder {
192 config: SAMConfig,
194}
195
196impl MaskDecoder {
197 pub fn new(config: &SAMConfig) -> Self {
199 Self {
200 config: config.clone(),
201 }
202 }
203
204 pub fn decode(
218 &self,
219 image_features: &Array2<f64>,
220 prompt_embedding: &PromptEmbedding,
221 image_size: (usize, usize),
222 ) -> Result<SegmentationResult> {
223 let (img_h, img_w) = image_size;
224 if img_h == 0 || img_w == 0 {
225 return Err(VisionError::InvalidParameter(
226 "mask_decoder: image_size must be non-zero".into(),
227 ));
228 }
229
230 let embed_dim = self.config.embed_dim;
231 let n_masks = self.config.num_mask_outputs;
232
233 let n_sparse = prompt_embedding.sparse_embeddings.dim().0;
236 let total_tokens = n_sparse + n_masks;
237
238 let mut tokens = Array2::zeros((total_tokens, embed_dim));
239
240 for i in 0..n_sparse {
242 let src_cols = prompt_embedding.sparse_embeddings.dim().1.min(embed_dim);
243 for j in 0..src_cols {
244 tokens[[i, j]] = prompt_embedding.sparse_embeddings[[i, j]];
245 }
246 }
247
248 for m in 0..n_masks {
250 let row = n_sparse + m;
251 for j in 0..embed_dim {
252 tokens[[row, j]] = 0.01 * ((m * embed_dim + j) as f64 * 0.07).sin();
253 }
254 }
255
256 let feat_cols = image_features.dim().1;
258 let proj_features = if feat_cols != embed_dim {
259 project_features(image_features, embed_dim)
261 } else {
262 image_features.clone()
263 };
264
265 let tokens = simple_attention(&tokens, &proj_features, &proj_features)?;
266
267 let tokens = simple_attention(&tokens, &tokens, &tokens)?;
269
270 let n_dense = prompt_embedding.dense_embeddings.dim().0;
272 let combined = if n_dense > 0 {
273 let mut avg = vec![0.0f64; embed_dim];
275 let dense_cols = prompt_embedding.dense_embeddings.dim().1.min(embed_dim);
276 for i in 0..n_dense {
277 for (j, avg_val) in avg.iter_mut().enumerate().take(dense_cols) {
278 *avg_val += prompt_embedding.dense_embeddings[[i, j]];
279 }
280 }
281 if n_dense > 0 {
282 for v in &mut avg {
283 *v /= n_dense as f64;
284 }
285 }
286 let mut combined = tokens.clone();
287 for i in 0..total_tokens {
288 for j in 0..embed_dim {
289 combined[[i, j]] += avg[j];
290 }
291 }
292 combined
293 } else {
294 tokens
295 };
296
297 let mask_token_start = n_sparse;
299 let mut mask_tokens = Array2::zeros((n_masks, embed_dim));
300 for m in 0..n_masks {
301 let src_row = (mask_token_start + m).min(combined.dim().0.saturating_sub(1));
302 for j in 0..embed_dim {
303 mask_tokens[[m, j]] = combined[[src_row, j]];
304 }
305 }
306
307 let iou_hidden = self.config.iou_head_hidden;
309 let iou_raw = mlp_forward(&mask_tokens, iou_hidden, 1);
310 let iou_predictions: Vec<f64> = (0..n_masks).map(|m| sigmoid(iou_raw[[m, 0]])).collect();
311
312 let n_feat = proj_features.dim().0;
315 let feat_side = (n_feat as f64).sqrt().ceil() as usize;
316 let feat_h = feat_side;
317 let feat_w = if feat_h > 0 {
318 n_feat.div_ceil(feat_h)
319 } else {
320 0
321 };
322
323 let mut masks = Vec::with_capacity(n_masks);
324 let mut stability_scores = Vec::with_capacity(n_masks);
325
326 for m in 0..n_masks {
327 let mut logit_map = Array2::zeros((feat_h, feat_w));
329 for fy in 0..feat_h {
330 for fx in 0..feat_w {
331 let fi = fy * feat_w + fx;
332 if fi < n_feat {
333 let mut dot = 0.0f64;
334 for d in 0..embed_dim {
335 dot += mask_tokens[[m, d]] * proj_features[[fi, d]];
336 }
337 logit_map[[fy, fx]] = dot;
338 }
339 }
340 }
341
342 let full_mask = upsample_to_size(&logit_map, img_h, img_w)?;
344
345 let stab = compute_stability_score(&full_mask, 0.0, -1.0);
346 stability_scores.push(stab);
347 masks.push(full_mask);
348 }
349
350 Ok(SegmentationResult {
351 masks,
352 iou_predictions,
353 stability_scores,
354 })
355 }
356}
357
358fn sigmoid(x: f64) -> f64 {
364 1.0 / (1.0 + (-x).exp())
365}
366
367fn project_features(features: &Array2<f64>, out_dim: usize) -> Array2<f64> {
369 let (n, in_dim) = features.dim();
370 let mut out = Array2::zeros((n, out_dim));
371 let copy_dim = in_dim.min(out_dim);
372 for i in 0..n {
373 for j in 0..copy_dim {
374 out[[i, j]] = features[[i, j]];
375 }
376 }
377 out
378}
379
380fn upsample_to_size(map: &Array2<f64>, target_h: usize, target_w: usize) -> Result<Array2<f64>> {
382 let (src_h, src_w) = map.dim();
383 if src_h == 0 || src_w == 0 {
384 return Ok(Array2::zeros((target_h, target_w)));
385 }
386
387 let mut out = Array2::zeros((target_h, target_w));
388 for ty in 0..target_h {
389 for tx in 0..target_w {
390 let sy = ty as f64 * (src_h as f64) / target_h.max(1) as f64;
391 let sx = tx as f64 * (src_w as f64) / target_w.max(1) as f64;
392
393 let y0 = (sy.floor() as usize).min(src_h.saturating_sub(1));
394 let x0 = (sx.floor() as usize).min(src_w.saturating_sub(1));
395 let y1 = (y0 + 1).min(src_h.saturating_sub(1));
396 let x1 = (x0 + 1).min(src_w.saturating_sub(1));
397
398 let fy = sy - y0 as f64;
399 let fx = sx - x0 as f64;
400
401 let val = (1.0 - fy) * (1.0 - fx) * map[[y0, x0]]
402 + (1.0 - fy) * fx * map[[y0, x1]]
403 + fy * (1.0 - fx) * map[[y1, x0]]
404 + fy * fx * map[[y1, x1]];
405
406 out[[ty, tx]] = val;
407 }
408 }
409 Ok(out)
410}
411
412pub fn compute_stability_score(mask: &Array2<f64>, threshold_high: f64, threshold_low: f64) -> f64 {
418 let (h, w) = mask.dim();
419 let mut intersection = 0usize;
420 let mut union = 0usize;
421
422 for r in 0..h {
423 for c in 0..w {
424 let v = mask[[r, c]];
425 let in_high = v > threshold_high;
426 let in_low = v > threshold_low;
427 if in_high || in_low {
428 union += 1;
429 }
430 if in_high && in_low {
431 intersection += 1;
432 }
433 }
434 }
435
436 if union == 0 {
437 1.0
438 } else {
439 intersection as f64 / union as f64
440 }
441}
442
443#[cfg(test)]
448mod tests {
449 use super::*;
450 use crate::prompt_segmentation::prompt_encoder::PromptEmbedding;
451 use crate::prompt_segmentation::types::SAMConfig;
452 use scirs2_core::ndarray::Array2;
453
454 fn small_config() -> SAMConfig {
455 SAMConfig {
456 image_size: 16,
457 embed_dim: 8,
458 num_mask_outputs: 3,
459 iou_head_hidden: 8,
460 encoder_stages: 2,
461 }
462 }
463
464 #[test]
465 fn test_simple_attention_identity() {
466 let q = Array2::from_shape_vec((2, 3), vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0]).expect("shape");
467 let k = q.clone();
468 let v = q.clone();
469 let out = simple_attention(&q, &k, &v).expect("attention");
470 assert_eq!(out.dim(), (2, 3));
471 }
472
473 #[test]
474 fn test_upsample_2x() {
475 let input = Array2::from_shape_vec((4, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
476 .expect("shape");
477 let (oh, ow, out) = upsample_2x(&input, 2, 2).expect("upsample");
478 assert_eq!(oh, 4);
479 assert_eq!(ow, 4);
480 assert_eq!(out.dim(), (16, 2));
481 }
482
483 #[test]
484 fn test_stability_score_all_above() {
485 let mask = Array2::from_elem((4, 4), 5.0);
486 let s = compute_stability_score(&mask, 0.0, -1.0);
487 assert!((s - 1.0).abs() < 1e-9);
488 }
489
490 #[test]
491 fn test_stability_score_mixed() {
492 let mut mask = Array2::zeros((4, 4));
493 for r in 0..2 {
495 for c in 0..4 {
496 mask[[r, c]] = 1.0;
497 }
498 }
499 for r in 2..4 {
500 for c in 0..4 {
501 mask[[r, c]] = -0.5;
502 }
503 }
504 let s = compute_stability_score(&mask, 0.0, -1.0);
505 assert!((s - 0.5).abs() < 1e-9);
507 }
508
509 #[test]
510 fn test_mask_decoder_smoke() {
511 let cfg = small_config();
512 let decoder = MaskDecoder::new(&cfg);
513
514 let image_features = Array2::from_elem((16, 8), 0.1);
515 let prompt_emb = PromptEmbedding {
516 sparse_embeddings: Array2::from_elem((1, 8), 0.5),
517 dense_embeddings: Array2::zeros((0, 8)),
518 };
519
520 let result = decoder
521 .decode(&image_features, &prompt_emb, (16, 16))
522 .expect("decode");
523
524 assert_eq!(result.masks.len(), 3);
525 assert_eq!(result.iou_predictions.len(), 3);
526 assert_eq!(result.stability_scores.len(), 3);
527
528 for mask in &result.masks {
529 assert_eq!(mask.dim(), (16, 16));
530 }
531 for &iou in &result.iou_predictions {
532 assert!((0.0..=1.0).contains(&iou));
533 }
534 }
535
536 #[test]
537 fn test_mask_decoder_with_dense() {
538 let cfg = small_config();
539 let decoder = MaskDecoder::new(&cfg);
540
541 let image_features = Array2::from_elem((16, 8), 0.1);
542 let prompt_emb = PromptEmbedding {
543 sparse_embeddings: Array2::zeros((0, 8)),
544 dense_embeddings: Array2::from_elem((4, 8), 0.3),
545 };
546
547 let result = decoder
548 .decode(&image_features, &prompt_emb, (8, 8))
549 .expect("decode with dense");
550
551 assert_eq!(result.masks.len(), 3);
552 for mask in &result.masks {
553 assert_eq!(mask.dim(), (8, 8));
554 }
555 }
556
557 #[test]
558 fn test_mask_decoder_zero_image_err() {
559 let cfg = small_config();
560 let decoder = MaskDecoder::new(&cfg);
561 let prompt_emb = PromptEmbedding {
562 sparse_embeddings: Array2::zeros((1, 8)),
563 dense_embeddings: Array2::zeros((0, 8)),
564 };
565 assert!(decoder
566 .decode(&Array2::zeros((4, 8)), &prompt_emb, (0, 0))
567 .is_err());
568 }
569}