Skip to main content

scirs2_ndimage/visualization/
types.rs

1//! Core Types and Configurations for Visualization
2//!
3//! This module contains all the fundamental data structures, enums, and configuration
4//! types used throughout the visualization system.
5
6/// Color map types for visualization
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ColorMap {
9    /// Grayscale color map
10    Gray,
11    /// Jet color map (blue to red)
12    Jet,
13    /// Viridis perceptually uniform color map
14    Viridis,
15    /// Plasma color map
16    Plasma,
17    /// Inferno color map
18    Inferno,
19    /// Hot color map (black to white through red/yellow)
20    Hot,
21    /// Cool color map (cyan to magenta)
22    Cool,
23    /// Spring color map (magenta to yellow)
24    Spring,
25    /// Summer color map (green to yellow)
26    Summer,
27    /// Autumn color map (red to yellow)
28    Autumn,
29    /// Winter color map (blue to green)
30    Winter,
31}
32
33/// Report output format options
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ReportFormat {
36    /// HTML format with styling
37    Html,
38    /// Markdown format
39    Markdown,
40    /// Plain text format
41    Text,
42}
43
44/// RGB color representation
45#[derive(Debug, Clone, Copy)]
46pub struct RgbColor {
47    /// Red component (0-255)
48    pub r: u8,
49    /// Green component (0-255)
50    pub g: u8,
51    /// Blue component (0-255)
52    pub b: u8,
53}
54
55impl RgbColor {
56    /// Create a new RGB color
57    pub fn new(r: u8, g: u8, b: u8) -> Self {
58        Self { r, g, b }
59    }
60
61    /// Convert to hexadecimal color string
62    pub fn to_hex(&self) -> String {
63        format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
64    }
65
66    /// Convert to RGB tuple
67    pub fn to_tuple(&self) -> (u8, u8, u8) {
68        (self.r, self.g, self.b)
69    }
70
71    /// Create from hex string (e.g., "#FF0000" for red)
72    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    /// Create from HSV values (hue: 0-360, saturation: 0-1, value: 0-1)
86    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/// Configuration for plotting operations
115#[derive(Debug, Clone)]
116pub struct PlotConfig {
117    /// Width of the plot in pixels
118    pub width: usize,
119    /// Height of the plot in pixels
120    pub height: usize,
121    /// Title of the plot
122    pub title: String,
123    /// X-axis label
124    pub xlabel: String,
125    /// Y-axis label
126    pub ylabel: String,
127    /// Color map to use
128    pub colormap: ColorMap,
129    /// Whether to show grid
130    pub show_grid: bool,
131    /// Number of bins for histograms
132    pub num_bins: usize,
133    /// Output format
134    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    /// Create a new plot configuration
155    pub fn new() -> Self {
156        Self::default()
157    }
158
159    /// Set the plot dimensions
160    pub fn with_dimensions(mut self, width: usize, height: usize) -> Self {
161        self.width = width;
162        self.height = height;
163        self
164    }
165
166    /// Set the plot title
167    pub fn with_title(mut self, title: impl Into<String>) -> Self {
168        self.title = title.into();
169        self
170    }
171
172    /// Set the axis labels
173    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    /// Set the color map
180    pub fn with_colormap(mut self, colormap: ColorMap) -> Self {
181        self.colormap = colormap;
182        self
183    }
184
185    /// Set the output format
186    pub fn with_format(mut self, format: ReportFormat) -> Self {
187        self.format = format;
188        self
189    }
190
191    /// Set the number of bins for histograms
192    pub fn with_bins(mut self, num_bins: usize) -> Self {
193        self.num_bins = num_bins;
194        self
195    }
196
197    /// Enable or disable grid display
198    pub fn with_grid(mut self, show_grid: bool) -> Self {
199        self.show_grid = show_grid;
200        self
201    }
202}
203
204/// Configuration for report generation
205#[derive(Debug, Clone)]
206pub struct ReportConfig {
207    /// Title of the report
208    pub title: String,
209    /// Author information
210    pub author: String,
211    /// Include detailed statistics
212    pub include_statistics: bool,
213    /// Include quality metrics
214    pub include_qualitymetrics: bool,
215    /// Include texture analysis
216    pub includetexture_analysis: bool,
217    /// Include histograms
218    pub include_histograms: bool,
219    /// Include profile plots
220    pub include_profiles: bool,
221    /// Output format (html, markdown, text)
222    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    /// Create a new report configuration
242    pub fn new() -> Self {
243        Self::default()
244    }
245
246    /// Set the report title and author
247    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    /// Set the output format
254    pub fn with_format(mut self, format: ReportFormat) -> Self {
255        self.format = format;
256        self
257    }
258
259    /// Configure which sections to include
260    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    /// Enable all sections
277    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    /// Disable all optional sections (only basic info)
287    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
297/// Predefined color schemes for different visualization purposes
298pub struct ColorSchemes;
299
300impl ColorSchemes {
301    /// Scientific publication-friendly colors
302    pub fn scientific() -> [RgbColor; 6] {
303        [
304            RgbColor::new(31, 120, 180), // Blue
305            RgbColor::new(51, 160, 44),  // Green
306            RgbColor::new(227, 26, 28),  // Red
307            RgbColor::new(255, 127, 0),  // Orange
308            RgbColor::new(106, 61, 154), // Purple
309            RgbColor::new(177, 89, 40),  // Brown
310        ]
311    }
312
313    /// Colorblind-friendly palette
314    pub fn colorblind_friendly() -> [RgbColor; 8] {
315        [
316            RgbColor::new(0, 0, 0),       // Black
317            RgbColor::new(230, 159, 0),   // Orange
318            RgbColor::new(86, 180, 233),  // Sky blue
319            RgbColor::new(0, 158, 115),   // Bluish green
320            RgbColor::new(240, 228, 66),  // Yellow
321            RgbColor::new(0, 114, 178),   // Blue
322            RgbColor::new(213, 94, 0),    // Vermillion
323            RgbColor::new(204, 121, 167), // Reddish purple
324        ]
325    }
326
327    /// High contrast colors for presentations
328    pub fn high_contrast() -> [RgbColor; 4] {
329        [
330            RgbColor::new(0, 0, 0),       // Black
331            RgbColor::new(255, 255, 255), // White
332            RgbColor::new(255, 0, 0),     // Red
333            RgbColor::new(0, 0, 255),     // Blue
334        ]
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        // Test invalid formats
375        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        // Red (0 degrees)
383        let red = RgbColor::from_hsv(0.0, 1.0, 1.0);
384        assert_eq!(red.r, 255);
385        assert!(red.g < 5); // Should be 0, but allow for rounding
386        assert!(red.b < 5);
387
388        // Green (120 degrees)
389        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        // Blue (240 degrees)
395        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        // Test that first color in high contrast is black
450        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        // Test enum equality
458        assert_eq!(ColorMap::Viridis, ColorMap::Viridis);
459        assert_ne!(ColorMap::Viridis, ColorMap::Jet);
460
461        // Test debug formatting
462        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}