1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ColorMap {
9 Gray,
11 Jet,
13 Viridis,
15 Plasma,
17 Inferno,
19 Hot,
21 Cool,
23 Spring,
25 Summer,
27 Autumn,
29 Winter,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ReportFormat {
36 Html,
38 Markdown,
40 Text,
42}
43
44#[derive(Debug, Clone, Copy)]
46pub struct RgbColor {
47 pub r: u8,
49 pub g: u8,
51 pub b: u8,
53}
54
55impl RgbColor {
56 pub fn new(r: u8, g: u8, b: u8) -> Self {
58 Self { r, g, b }
59 }
60
61 pub fn to_hex(&self) -> String {
63 format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
64 }
65
66 pub fn to_tuple(&self) -> (u8, u8, u8) {
68 (self.r, self.g, self.b)
69 }
70
71 pub fn from_hex(hex: &str) -> Result<Self, &'static str> {
73 if !hex.starts_with('#') || hex.len() != 7 {
74 return Err("Invalid hex format, expected #RRGGBB");
75 }
76
77 let hex = &hex[1..];
78 let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Invalid red component")?;
79 let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| "Invalid green component")?;
80 let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Invalid blue component")?;
81
82 Ok(RgbColor::new(r, g, b))
83 }
84
85 pub fn from_hsv(h: f64, s: f64, v: f64) -> Self {
87 let h = h.rem_euclid(360.0);
88 let c = v * s;
89 let x = c * (1.0 - ((h / 60.0).rem_euclid(2.0) - 1.0).abs());
90 let m = v - c;
91
92 let (r_prime, g_prime, b_prime) = if h < 60.0 {
93 (c, x, 0.0)
94 } else if h < 120.0 {
95 (x, c, 0.0)
96 } else if h < 180.0 {
97 (0.0, c, x)
98 } else if h < 240.0 {
99 (0.0, x, c)
100 } else if h < 300.0 {
101 (x, 0.0, c)
102 } else {
103 (c, 0.0, x)
104 };
105
106 RgbColor::new(
107 ((r_prime + m) * 255.0) as u8,
108 ((g_prime + m) * 255.0) as u8,
109 ((b_prime + m) * 255.0) as u8,
110 )
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct PlotConfig {
117 pub width: usize,
119 pub height: usize,
121 pub title: String,
123 pub xlabel: String,
125 pub ylabel: String,
127 pub colormap: ColorMap,
129 pub show_grid: bool,
131 pub num_bins: usize,
133 pub format: ReportFormat,
135}
136
137impl Default for PlotConfig {
138 fn default() -> Self {
139 Self {
140 width: 800,
141 height: 600,
142 title: "Image Analysis Plot".to_string(),
143 xlabel: "X".to_string(),
144 ylabel: "Y".to_string(),
145 colormap: ColorMap::Viridis,
146 show_grid: true,
147 num_bins: 256,
148 format: ReportFormat::Text,
149 }
150 }
151}
152
153impl PlotConfig {
154 pub fn new() -> Self {
156 Self::default()
157 }
158
159 pub fn with_dimensions(mut self, width: usize, height: usize) -> Self {
161 self.width = width;
162 self.height = height;
163 self
164 }
165
166 pub fn with_title(mut self, title: impl Into<String>) -> Self {
168 self.title = title.into();
169 self
170 }
171
172 pub fn with_labels(mut self, xlabel: impl Into<String>, ylabel: impl Into<String>) -> Self {
174 self.xlabel = xlabel.into();
175 self.ylabel = ylabel.into();
176 self
177 }
178
179 pub fn with_colormap(mut self, colormap: ColorMap) -> Self {
181 self.colormap = colormap;
182 self
183 }
184
185 pub fn with_format(mut self, format: ReportFormat) -> Self {
187 self.format = format;
188 self
189 }
190
191 pub fn with_bins(mut self, num_bins: usize) -> Self {
193 self.num_bins = num_bins;
194 self
195 }
196
197 pub fn with_grid(mut self, show_grid: bool) -> Self {
199 self.show_grid = show_grid;
200 self
201 }
202}
203
204#[derive(Debug, Clone)]
206pub struct ReportConfig {
207 pub title: String,
209 pub author: String,
211 pub include_statistics: bool,
213 pub include_qualitymetrics: bool,
215 pub includetexture_analysis: bool,
217 pub include_histograms: bool,
219 pub include_profiles: bool,
221 pub format: ReportFormat,
223}
224
225impl Default for ReportConfig {
226 fn default() -> Self {
227 Self {
228 title: "Image Analysis Report".to_string(),
229 author: "SciRS2 Image Analysis".to_string(),
230 include_statistics: true,
231 include_qualitymetrics: true,
232 includetexture_analysis: true,
233 include_histograms: true,
234 include_profiles: true,
235 format: ReportFormat::Markdown,
236 }
237 }
238}
239
240impl ReportConfig {
241 pub fn new() -> Self {
243 Self::default()
244 }
245
246 pub fn with_header(mut self, title: impl Into<String>, author: impl Into<String>) -> Self {
248 self.title = title.into();
249 self.author = author.into();
250 self
251 }
252
253 pub fn with_format(mut self, format: ReportFormat) -> Self {
255 self.format = format;
256 self
257 }
258
259 pub fn with_sections(
261 mut self,
262 statistics: bool,
263 quality_metrics: bool,
264 texture_analysis: bool,
265 histograms: bool,
266 profiles: bool,
267 ) -> Self {
268 self.include_statistics = statistics;
269 self.include_qualitymetrics = quality_metrics;
270 self.includetexture_analysis = texture_analysis;
271 self.include_histograms = histograms;
272 self.include_profiles = profiles;
273 self
274 }
275
276 pub fn with_all_sections(mut self) -> Self {
278 self.include_statistics = true;
279 self.include_qualitymetrics = true;
280 self.includetexture_analysis = true;
281 self.include_histograms = true;
282 self.include_profiles = true;
283 self
284 }
285
286 pub fn minimal(mut self) -> Self {
288 self.include_statistics = false;
289 self.include_qualitymetrics = false;
290 self.includetexture_analysis = false;
291 self.include_histograms = false;
292 self.include_profiles = false;
293 self
294 }
295}
296
297pub struct ColorSchemes;
299
300impl ColorSchemes {
301 pub fn scientific() -> [RgbColor; 6] {
303 [
304 RgbColor::new(31, 120, 180), RgbColor::new(51, 160, 44), RgbColor::new(227, 26, 28), RgbColor::new(255, 127, 0), RgbColor::new(106, 61, 154), RgbColor::new(177, 89, 40), ]
311 }
312
313 pub fn colorblind_friendly() -> [RgbColor; 8] {
315 [
316 RgbColor::new(0, 0, 0), RgbColor::new(230, 159, 0), RgbColor::new(86, 180, 233), RgbColor::new(0, 158, 115), RgbColor::new(240, 228, 66), RgbColor::new(0, 114, 178), RgbColor::new(213, 94, 0), RgbColor::new(204, 121, 167), ]
325 }
326
327 pub fn high_contrast() -> [RgbColor; 4] {
329 [
330 RgbColor::new(0, 0, 0), RgbColor::new(255, 255, 255), RgbColor::new(255, 0, 0), RgbColor::new(0, 0, 255), ]
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_rgb_color_creation() {
344 let color = RgbColor::new(255, 128, 64);
345 assert_eq!(color.r, 255);
346 assert_eq!(color.g, 128);
347 assert_eq!(color.b, 64);
348 }
349
350 #[test]
351 fn test_rgb_to_hex() {
352 let color = RgbColor::new(255, 0, 0);
353 assert_eq!(color.to_hex(), "#ff0000");
354
355 let color = RgbColor::new(0, 255, 0);
356 assert_eq!(color.to_hex(), "#00ff00");
357
358 let color = RgbColor::new(0, 0, 255);
359 assert_eq!(color.to_hex(), "#0000ff");
360 }
361
362 #[test]
363 fn test_rgb_from_hex() {
364 let color = RgbColor::from_hex("#FF0000").expect("Operation failed");
365 assert_eq!(color.r, 255);
366 assert_eq!(color.g, 0);
367 assert_eq!(color.b, 0);
368
369 let color = RgbColor::from_hex("#00FF00").expect("Operation failed");
370 assert_eq!(color.r, 0);
371 assert_eq!(color.g, 255);
372 assert_eq!(color.b, 0);
373
374 assert!(RgbColor::from_hex("FF0000").is_err());
376 assert!(RgbColor::from_hex("#FF00").is_err());
377 assert!(RgbColor::from_hex("#GGGGGG").is_err());
378 }
379
380 #[test]
381 fn test_rgb_from_hsv() {
382 let red = RgbColor::from_hsv(0.0, 1.0, 1.0);
384 assert_eq!(red.r, 255);
385 assert!(red.g < 5); assert!(red.b < 5);
387
388 let green = RgbColor::from_hsv(120.0, 1.0, 1.0);
390 assert!(green.r < 5);
391 assert_eq!(green.g, 255);
392 assert!(green.b < 5);
393
394 let blue = RgbColor::from_hsv(240.0, 1.0, 1.0);
396 assert!(blue.r < 5);
397 assert!(blue.g < 5);
398 assert_eq!(blue.b, 255);
399 }
400
401 #[test]
402 fn test_plot_config_builder() {
403 let config = PlotConfig::new()
404 .with_dimensions(1024, 768)
405 .with_title("Test Plot")
406 .with_labels("X Axis", "Y Axis")
407 .with_colormap(ColorMap::Jet)
408 .with_bins(128)
409 .with_grid(false);
410
411 assert_eq!(config.width, 1024);
412 assert_eq!(config.height, 768);
413 assert_eq!(config.title, "Test Plot");
414 assert_eq!(config.xlabel, "X Axis");
415 assert_eq!(config.ylabel, "Y Axis");
416 assert_eq!(config.colormap, ColorMap::Jet);
417 assert_eq!(config.num_bins, 128);
418 assert!(!config.show_grid);
419 }
420
421 #[test]
422 fn test_report_config_builder() {
423 let config = ReportConfig::new()
424 .with_header("Custom Report", "Test Author")
425 .with_format(ReportFormat::Html)
426 .with_sections(true, true, false, true, false);
427
428 assert_eq!(config.title, "Custom Report");
429 assert_eq!(config.author, "Test Author");
430 assert_eq!(config.format, ReportFormat::Html);
431 assert!(config.include_statistics);
432 assert!(config.include_qualitymetrics);
433 assert!(!config.includetexture_analysis);
434 assert!(config.include_histograms);
435 assert!(!config.include_profiles);
436 }
437
438 #[test]
439 fn test_color_schemes() {
440 let scientific = ColorSchemes::scientific();
441 assert_eq!(scientific.len(), 6);
442
443 let colorblind = ColorSchemes::colorblind_friendly();
444 assert_eq!(colorblind.len(), 8);
445
446 let contrast = ColorSchemes::high_contrast();
447 assert_eq!(contrast.len(), 4);
448
449 assert_eq!(contrast[0].r, 0);
451 assert_eq!(contrast[0].g, 0);
452 assert_eq!(contrast[0].b, 0);
453 }
454
455 #[test]
456 fn test_colormap_enum() {
457 assert_eq!(ColorMap::Viridis, ColorMap::Viridis);
459 assert_ne!(ColorMap::Viridis, ColorMap::Jet);
460
461 let colormap = ColorMap::Plasma;
463 let debug_str = format!("{:?}", colormap);
464 assert!(debug_str.contains("Plasma"));
465 }
466
467 #[test]
468 fn test_report_format_enum() {
469 let format = ReportFormat::Markdown;
470 assert_eq!(format, ReportFormat::Markdown);
471 assert_ne!(format, ReportFormat::Html);
472
473 let debug_str = format!("{:?}", format);
474 assert!(debug_str.contains("Markdown"));
475 }
476}