scirs2_vision/prompt_segmentation/types.rs
1//! Core types for SAM-style prompt-based segmentation.
2
3use scirs2_core::ndarray::Array2;
4
5// ---------------------------------------------------------------------------
6// Prompt types
7// ---------------------------------------------------------------------------
8
9/// The kind of prompt that drives mask prediction.
10#[derive(Debug, Clone)]
11#[non_exhaustive]
12pub enum PromptType {
13 /// A single point with foreground/background label.
14 Point {
15 /// Horizontal coordinate (column).
16 x: usize,
17 /// Vertical coordinate (row).
18 y: usize,
19 /// `true` = foreground, `false` = background.
20 is_foreground: bool,
21 },
22 /// An axis-aligned bounding box.
23 BoundingBox {
24 /// Left column.
25 x1: usize,
26 /// Top row.
27 y1: usize,
28 /// Right column (exclusive).
29 x2: usize,
30 /// Bottom row (exclusive).
31 y2: usize,
32 },
33 /// A dense mask prompt (e.g. a rough user scribble).
34 MaskPrompt {
35 /// 2-D mask whose spatial size matches the input image.
36 mask: Array2<f64>,
37 },
38 /// Multiple points with per-point foreground/background labels.
39 MultiPoint {
40 /// Each entry is `(x, y, is_foreground)`.
41 points: Vec<(usize, usize, bool)>,
42 },
43}
44
45// ---------------------------------------------------------------------------
46// Configuration
47// ---------------------------------------------------------------------------
48
49/// Configuration for the SAM-style segmentation pipeline.
50#[derive(Debug, Clone)]
51pub struct SAMConfig {
52 /// Encoder input resolution (images are conceptually rescaled to this).
53 pub image_size: usize,
54 /// Embedding dimensionality throughout the pipeline.
55 pub embed_dim: usize,
56 /// Number of candidate masks produced by the decoder.
57 pub num_mask_outputs: usize,
58 /// Hidden size of the IoU prediction head.
59 pub iou_head_hidden: usize,
60 /// Number of encoder down-sampling stages (scales).
61 pub encoder_stages: usize,
62}
63
64impl Default for SAMConfig {
65 fn default() -> Self {
66 Self {
67 image_size: 1024,
68 embed_dim: 256,
69 num_mask_outputs: 3,
70 iou_head_hidden: 256,
71 encoder_stages: 3,
72 }
73 }
74}
75
76// ---------------------------------------------------------------------------
77// Results
78// ---------------------------------------------------------------------------
79
80/// Output of the mask decoder.
81#[derive(Debug, Clone)]
82pub struct SegmentationResult {
83 /// Predicted masks, one per candidate. Each mask has the same spatial size
84 /// as the input image and contains logit values (higher = more likely
85 /// foreground).
86 pub masks: Vec<Array2<f64>>,
87 /// Per-mask IoU predictions (model confidence).
88 pub iou_predictions: Vec<f64>,
89 /// Per-mask stability scores (IoU between high-threshold and
90 /// low-threshold binarisations).
91 pub stability_scores: Vec<f64>,
92}
93
94// ---------------------------------------------------------------------------
95// Prompt wrapper
96// ---------------------------------------------------------------------------
97
98/// A user-supplied segmentation prompt together with an optional label.
99#[derive(Debug, Clone)]
100pub struct SegmentationPrompt {
101 /// The prompt geometry.
102 pub prompt_type: PromptType,
103 /// Optional human-readable label for the prompt.
104 pub label: Option<String>,
105}
106
107impl SegmentationPrompt {
108 /// Create a new prompt without a label.
109 pub fn new(prompt_type: PromptType) -> Self {
110 Self {
111 prompt_type,
112 label: None,
113 }
114 }
115
116 /// Create a new prompt with a label.
117 pub fn with_label(prompt_type: PromptType, label: impl Into<String>) -> Self {
118 Self {
119 prompt_type,
120 label: Some(label.into()),
121 }
122 }
123}
124
125// ---------------------------------------------------------------------------
126// Tests
127// ---------------------------------------------------------------------------
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use scirs2_core::ndarray::Array2;
133
134 #[test]
135 fn test_sam_config_default() {
136 let cfg = SAMConfig::default();
137 assert_eq!(cfg.image_size, 1024);
138 assert_eq!(cfg.embed_dim, 256);
139 assert_eq!(cfg.num_mask_outputs, 3);
140 assert_eq!(cfg.iou_head_hidden, 256);
141 assert_eq!(cfg.encoder_stages, 3);
142 }
143
144 #[test]
145 fn test_prompt_type_point() {
146 let p = PromptType::Point {
147 x: 10,
148 y: 20,
149 is_foreground: true,
150 };
151 if let PromptType::Point {
152 x,
153 y,
154 is_foreground,
155 } = &p
156 {
157 assert_eq!(*x, 10);
158 assert_eq!(*y, 20);
159 assert!(*is_foreground);
160 } else {
161 panic!("expected Point variant");
162 }
163 }
164
165 #[test]
166 fn test_prompt_type_bounding_box() {
167 let p = PromptType::BoundingBox {
168 x1: 5,
169 y1: 10,
170 x2: 50,
171 y2: 60,
172 };
173 if let PromptType::BoundingBox { x1, y1, x2, y2 } = &p {
174 assert_eq!(*x1, 5);
175 assert_eq!(*y1, 10);
176 assert_eq!(*x2, 50);
177 assert_eq!(*y2, 60);
178 } else {
179 panic!("expected BoundingBox variant");
180 }
181 }
182
183 #[test]
184 fn test_prompt_type_mask() {
185 let mask = Array2::<f64>::zeros((64, 64));
186 let p = PromptType::MaskPrompt { mask: mask.clone() };
187 if let PromptType::MaskPrompt { mask: m } = &p {
188 assert_eq!(m.dim(), (64, 64));
189 } else {
190 panic!("expected MaskPrompt variant");
191 }
192 }
193
194 #[test]
195 fn test_prompt_type_multipoint() {
196 let pts = vec![(1, 2, true), (3, 4, false)];
197 let p = PromptType::MultiPoint {
198 points: pts.clone(),
199 };
200 if let PromptType::MultiPoint { points } = &p {
201 assert_eq!(points.len(), 2);
202 } else {
203 panic!("expected MultiPoint variant");
204 }
205 }
206
207 #[test]
208 fn test_segmentation_prompt_new() {
209 let sp = SegmentationPrompt::new(PromptType::Point {
210 x: 0,
211 y: 0,
212 is_foreground: true,
213 });
214 assert!(sp.label.is_none());
215 }
216
217 #[test]
218 fn test_segmentation_prompt_with_label() {
219 let sp = SegmentationPrompt::with_label(
220 PromptType::BoundingBox {
221 x1: 0,
222 y1: 0,
223 x2: 10,
224 y2: 10,
225 },
226 "cat",
227 );
228 assert_eq!(sp.label.as_deref(), Some("cat"));
229 }
230}