glint_mask_tools/core/
postprocessor.rs1use crate::error::Result;
7use ndarray::Array2;
8
9pub trait PostProcessor: Send + Sync {
14 fn process_mask(&self, mask: &Array2<u8>) -> Result<Array2<u8>>;
24
25 fn name(&self) -> &'static str;
27
28 fn description(&self) -> &'static str;
30
31 fn validate_parameters(&self) -> Result<()> {
33 Ok(())
34 }
35}
36
37pub struct CompositePostProcessor {
39 processors: Vec<Box<dyn PostProcessor>>,
40}
41
42impl CompositePostProcessor {
43 pub fn new() -> Self {
45 Self {
46 processors: Vec::new(),
47 }
48 }
49
50 pub fn add_processor(mut self, processor: Box<dyn PostProcessor>) -> Self {
52 self.processors.push(processor);
53 self
54 }
55
56 pub fn len(&self) -> usize {
58 self.processors.len()
59 }
60
61 pub fn is_empty(&self) -> bool {
63 self.processors.is_empty()
64 }
65}
66
67impl Default for CompositePostProcessor {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73impl PostProcessor for CompositePostProcessor {
74 fn process_mask(&self, mask: &Array2<u8>) -> Result<Array2<u8>> {
75 let mut result = mask.clone();
76
77 for processor in &self.processors {
78 result = processor.process_mask(&result)?;
79 }
80
81 Ok(result)
82 }
83
84 fn name(&self) -> &'static str {
85 "Composite"
86 }
87
88 fn description(&self) -> &'static str {
89 "Applies multiple post-processors in sequence"
90 }
91
92 fn validate_parameters(&self) -> Result<()> {
93 for processor in &self.processors {
94 processor.validate_parameters()?;
95 }
96 Ok(())
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct PixelBufferProcessor {
103 radius: usize,
104 kernel: Array2<bool>,
105}
106
107impl PixelBufferProcessor {
108 pub fn new(radius: usize) -> Self {
110 let kernel = create_circular_kernel(radius);
111 Self { radius, kernel }
112 }
113
114 pub fn radius(&self) -> usize {
116 self.radius
117 }
118
119 fn create_kernel(&self) -> &Array2<bool> {
121 &self.kernel
122 }
123}
124
125impl PostProcessor for PixelBufferProcessor {
126 fn process_mask(&self, mask: &Array2<u8>) -> Result<Array2<u8>> {
127 if self.radius == 0 {
128 return Ok(mask.clone());
129 }
130
131 let (height, width) = mask.dim();
132 let mut result = Array2::zeros((height, width));
133 let kernel = self.create_kernel();
134 let (kernel_height, kernel_width) = kernel.dim();
135 let kernel_center_y = kernel_height / 2;
136 let kernel_center_x = kernel_width / 2;
137
138 for y in 0..height {
139 for x in 0..width {
140 let mut should_mask = false;
141
142 for ky in 0..kernel_height {
144 for kx in 0..kernel_width {
145 if !kernel[[ky, kx]] {
146 continue;
147 }
148
149 let img_y = y as i32 + ky as i32 - kernel_center_y as i32;
150 let img_x = x as i32 + kx as i32 - kernel_center_x as i32;
151
152 if img_y >= 0
153 && img_y < height as i32
154 && img_x >= 0
155 && img_x < width as i32
156 && mask[[img_y as usize, img_x as usize]] > 0
157 {
158 should_mask = true;
159 break;
160 }
161 }
162 if should_mask {
163 break;
164 }
165 }
166
167 result[[y, x]] = if should_mask { 1 } else { 0 };
168 }
169 }
170
171 Ok(result)
172 }
173
174 fn name(&self) -> &'static str {
175 "PixelBuffer"
176 }
177
178 fn description(&self) -> &'static str {
179 "Dilates the mask by a specified pixel radius"
180 }
181
182 fn validate_parameters(&self) -> Result<()> {
183 Ok(())
185 }
186}
187
188#[derive(Debug, Clone)]
190pub struct MetashapeConverter;
191
192impl MetashapeConverter {
193 pub fn new() -> Self {
195 Self
196 }
197}
198
199impl Default for MetashapeConverter {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205impl PostProcessor for MetashapeConverter {
206 fn process_mask(&self, mask: &Array2<u8>) -> Result<Array2<u8>> {
207 let result = mask.map(|&pixel| if pixel > 0 { 0 } else { 255 });
211 Ok(result)
212 }
213
214 fn name(&self) -> &'static str {
215 "Metashape"
216 }
217
218 fn description(&self) -> &'static str {
219 "Converts mask to Metashape format (inverted, 0-255 range)"
220 }
221}
222
223fn create_circular_kernel(radius: usize) -> Array2<bool> {
225 let size = 2 * radius + 1;
226 let mut kernel = Array2::from_elem((size, size), false);
227 let center = radius as i32;
228
229 for y in 0..size {
230 for x in 0..size {
231 let dy = y as i32 - center;
232 let dx = x as i32 - center;
233 let distance_sq = dx * dx + dy * dy;
234
235 if distance_sq <= (radius as i32) * (radius as i32) {
236 kernel[[y, x]] = true;
237 }
238 }
239 }
240
241 kernel
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_circular_kernel() {
250 let kernel = create_circular_kernel(1);
251 assert_eq!(kernel.dim(), (3, 3));
252 assert!(kernel[[1, 1]]);
254 assert!(kernel[[0, 1]]);
256 assert!(kernel[[1, 0]]);
257 assert!(kernel[[2, 1]]);
258 assert!(kernel[[1, 2]]);
259 assert!(!kernel[[0, 0]]);
261 assert!(!kernel[[2, 2]]);
262 }
263
264 #[test]
265 fn test_pixel_buffer_processor() {
266 let mut mask = ndarray::Array2::zeros((5, 5));
267 mask[[2, 2]] = 1; let processor = PixelBufferProcessor::new(1);
270 let result = processor.process_mask(&mask).unwrap();
271
272 assert_eq!(result[[2, 2]], 1);
274 assert_eq!(result[[1, 2]], 1);
275 assert_eq!(result[[3, 2]], 1);
276 assert_eq!(result[[2, 1]], 1);
277 assert_eq!(result[[2, 3]], 1);
278
279 assert_eq!(result[[0, 0]], 0);
281 assert_eq!(result[[4, 4]], 0);
282 }
283
284 #[test]
285 fn test_metashape_converter() {
286 let mut mask = ndarray::Array2::zeros((3, 3));
287 mask[[1, 1]] = 1; let converter = MetashapeConverter::new();
290 let result = converter.process_mask(&mask).unwrap();
291
292 assert_eq!(result[[1, 1]], 0);
294 assert_eq!(result[[0, 0]], 255);
296 assert_eq!(result[[2, 2]], 255);
297 }
298
299 #[test]
300 fn test_composite_processor() {
301 let mut mask = ndarray::Array2::zeros((5, 5));
302 mask[[2, 2]] = 1;
303
304 let processor = CompositePostProcessor::new()
305 .add_processor(Box::new(PixelBufferProcessor::new(1)))
306 .add_processor(Box::new(MetashapeConverter::new()));
307
308 let result = processor.process_mask(&mask).unwrap();
309
310 assert_eq!(result[[2, 2]], 0); assert_eq!(result[[1, 2]], 0); assert_eq!(result[[0, 0]], 255); }
315}