Skip to main content

scirs2_ndimage/visualization/
mod.rs

1//! Visualization and Reporting Utilities for Image Processing Results
2//!
3//! This module provides comprehensive tools for creating visual representations of
4//! image processing results, statistical plots, and comprehensive analysis reports.
5//! Designed for scientific documentation and presentation of image analysis workflows.
6//!
7//! The module is organized into focused sub-modules for better maintainability:
8//!
9//! - [`types`] - Core data structures, enums, and configuration types
10//! - [`colormap`] - Color map implementations for scientific visualization
11//! - [`plotting`] - Basic plotting functions (histograms, profiles, heatmaps, etc.)
12//! - [`reports`] - Comprehensive report generation system
13//! - [`statistical`] - Statistical visualization and comparison functions
14//!
15//! # Examples
16//!
17//! ## Basic Plotting
18//!
19//! ```rust
20//! use scirs2_core::ndarray::Array2;
21//! use scirs2_ndimage::visualization::{PlotConfig, ColorMap, ReportFormat, plot_histogram};
22//!
23//! let data = Array2::from_shape_fn((100, 100), |(i, j)| {
24//!     ((i as f64).sin() * (j as f64).cos()).abs()
25//! });
26//!
27//! let config = PlotConfig::new()
28//!     .with_colormap(ColorMap::Viridis)
29//!     .with_format(ReportFormat::Html)
30//!     .with_title("Sample Heatmap");
31//!
32//! let histogram = plot_histogram(&data.view().into_shape_with_order(10000)?.view(), &config)?;
33//! # Ok::<(), Box<dyn std::error::Error>>(())
34//! ```
35//!
36//! ## Report Generation
37//!
38//! ```rust
39//! use scirs2_core::ndarray::Array2;
40//! use scirs2_ndimage::visualization::{ReportConfig, ReportFormat, generate_report};
41//!
42//! let image = Array2::from_shape_fn((50, 50), |(i, j)| {
43//!     (i + j) as f64 / 100.0
44//! });
45//!
46//! let config = ReportConfig::new()
47//!     .with_format(ReportFormat::Markdown)
48//!     .with_header("Analysis Report", "SciRS2 NDImage")
49//!     .with_all_sections();
50//!
51//! let report = generate_report(&image.view(), None, None, &config)?;
52//! # Ok::<(), Box<dyn std::error::Error>>(())
53//! ```
54//!
55//! ## Statistical Comparison
56//!
57//! ```rust
58//! use scirs2_core::ndarray::Array1;
59//! use scirs2_ndimage::visualization::{PlotConfig, ReportFormat, plot_statistical_comparison};
60//!
61//! let control = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
62//! let treatment = Array1::from_vec(vec![2.0, 3.0, 4.0, 5.0, 6.0]);
63//!
64//! let datasets = vec![
65//!     ("Control Group", control.view()),
66//!     ("Treatment Group", treatment.view()),
67//! ];
68//!
69//! let config = PlotConfig::new()
70//!     .with_format(ReportFormat::Markdown)
71//!     .with_title("Group Comparison");
72//!
73//! let comparison = plot_statistical_comparison(&datasets, &config)?;
74//! # Ok::<(), Box<dyn std::error::Error>>(())
75//! ```
76
77use scirs2_core::ndarray::ArrayStatCompat;
78
79// Sub-module declarations
80pub mod colormap;
81pub mod plotting;
82pub mod reports;
83pub mod statistical;
84pub mod types;
85
86// Re-export all core types for backward compatibility
87pub use types::{ColorMap, ColorSchemes, PlotConfig, ReportConfig, ReportFormat, RgbColor};
88
89// Re-export all colormap functions
90pub use colormap::{
91    apply_colormap_to_array, autumn_colormap, cool_colormap, create_colormap,
92    get_colormap_function, gray_colormap, hot_colormap, inferno_colormap, jet_colormap,
93    plasma_colormap, spring_colormap, summer_colormap, viridis_colormap, winter_colormap,
94};
95
96// Re-export all plotting functions
97pub use plotting::{
98    plot_contour, plot_gradient, plot_heatmap, plot_histogram, plot_profile, plot_surface,
99    visualize_gradient,
100};
101
102// Re-export all report generation functions
103pub use reports::{
104    add_basic_statistics, add_image_info, add_quality_metrics, add_texture_metrics, generate_report,
105};
106
107// Re-export all statistical visualization functions
108pub use statistical::{
109    calculate_dataset_statistics, create_image_montage, plot_correlation_matrix,
110    plot_statistical_comparison,
111};
112
113// Legacy aliases for backward compatibility
114/// Legacy alias for `create_image_montage`
115pub use statistical::create_image_montage as createimage_montage;
116
117/// Legacy alias for `add_image_info`
118pub use reports::add_image_info as addimage_info;
119
120/// Legacy alias for `add_quality_metrics`
121pub use reports::add_quality_metrics as add_qualitymetrics;
122
123/// Legacy alias for `add_texture_metrics`
124pub use reports::add_texture_metrics as addtexturemetrics;
125
126// Export utilities module inline for compatibility
127/// Export utilities for saving visualization output to files
128pub mod export {
129    use super::reports::generate_report;
130    use super::types::{ReportConfig, ReportFormat};
131    use crate::analysis::{ImageQualityMetrics, TextureMetrics};
132    use crate::error::{NdimageError, NdimageResult};
133    use scirs2_core::ndarray::ArrayView2;
134    use scirs2_core::numeric::{Float, FromPrimitive, ToPrimitive};
135    use std::fmt::Debug;
136    use std::fs;
137    use std::path::Path;
138
139    /// Export configuration for file output
140    #[derive(Debug, Clone)]
141    pub struct ExportConfig {
142        /// Output file path
143        pub output_path: String,
144        /// Image quality (for compressed formats)
145        pub quality: Option<u8>,
146        /// DPI for vector formats
147        pub dpi: Option<u32>,
148        /// Whether to include metadata
149        pub include_metadata: bool,
150    }
151
152    impl Default for ExportConfig {
153        fn default() -> Self {
154            Self {
155                output_path: "output.html".to_string(),
156                quality: Some(95),
157                dpi: Some(300),
158                include_metadata: true,
159            }
160        }
161    }
162
163    /// Save a generated plot to file
164    pub fn save_plot(content: &str, config: &ExportConfig) -> NdimageResult<()> {
165        let path = Path::new(&config.output_path);
166
167        // Create parent directories if they don't exist
168        if let Some(parent) = path.parent() {
169            fs::create_dir_all(parent).map_err(|e| {
170                NdimageError::ComputationError(format!("Failed to create directory: {}", e))
171            })?;
172        }
173
174        // Add metadata if requested
175        let mut output_content = content.to_string();
176        if config.include_metadata {
177            let timestamp = std::time::SystemTime::now()
178                .duration_since(std::time::UNIX_EPOCH)
179                .unwrap_or_default()
180                .as_secs();
181
182            let metadata = format!(
183                "\n<!-- Generated by scirs2-ndimage visualization module at {} -->\n",
184                timestamp
185            );
186
187            if content.contains("</html>") {
188                output_content = content.replace("</html>", &format!("{}</html>", metadata));
189            } else if content.contains("# ") {
190                output_content = format!(
191                    "{}\n{}",
192                    content,
193                    metadata.replace("<!--", "").replace("-->", "")
194                );
195            } else {
196                output_content = format!(
197                    "{}\n{}",
198                    content,
199                    metadata.replace("<!--", "").replace("-->", "")
200                );
201            }
202        }
203
204        fs::write(path, output_content)
205            .map_err(|e| NdimageError::ComputationError(format!("Failed to write file: {}", e)))?;
206
207        Ok(())
208    }
209
210    /// Generate and save a comprehensive analysis report
211    pub fn export_analysis_report<T>(
212        image: &ArrayView2<T>,
213        qualitymetrics: Option<&ImageQualityMetrics<T>>,
214        texturemetrics: Option<&TextureMetrics<T>>,
215        output_path: &str,
216        format: ReportFormat,
217    ) -> NdimageResult<()>
218    where
219        T: Float + FromPrimitive + ToPrimitive + Debug + Clone,
220    {
221        let config = ReportConfig {
222            title: "Comprehensive Image Analysis Report".to_string(),
223            author: "SciRS2 NDImage".to_string(),
224            format,
225            ..ReportConfig::default()
226        };
227
228        let report = generate_report(image, qualitymetrics, texturemetrics, &config)?;
229
230        let export_config = ExportConfig {
231            output_path: output_path.to_string(),
232            ..ExportConfig::default()
233        };
234
235        save_plot(&report, &export_config)?;
236        Ok(())
237    }
238}
239
240// Advanced visualization utilities module inline for compatibility
241/// Advanced visualization utilities
242pub mod advanced {
243    use super::plotting::plot_heatmap;
244    use super::types::{ColorMap, PlotConfig, ReportFormat};
245    use crate::error::{NdimageError, NdimageResult};
246    use scirs2_core::ndarray::{ArrayStatCompat, ArrayView2};
247    use scirs2_core::numeric::{Float, FromPrimitive, ToPrimitive, Zero};
248    use std::fmt::{Debug, Write};
249
250    /// Create an interactive HTML visualization with multiple views
251    pub fn create_interactive_visualization<T>(
252        image: &ArrayView2<T>,
253        title: &str,
254    ) -> NdimageResult<String>
255    where
256        T: Float + FromPrimitive + ToPrimitive + Debug + Clone,
257    {
258        let mut html = String::new();
259
260        writeln!(&mut html, "<!DOCTYPE html>")?;
261        writeln!(&mut html, "<html><head>")?;
262        writeln!(&mut html, "<title>{}</title>", title)?;
263        writeln!(&mut html, "<style>")?;
264        writeln!(
265            &mut html,
266            r#"
267            body {{ font-family: Arial, sans-serif; margin: 20px; }}
268            .visualization-container {{ display: flex; flex-wrap: wrap; gap: 20px; }}
269            .plot-panel {{ border: 1px solid #ccc; padding: 15px; border-radius: 5px; min-width: 300px; }}
270            .plot-title {{ font-weight: bold; margin-bottom: 10px; color: #333; }}
271            .controls {{ margin-bottom: 15px; }}
272            .control-group {{ margin-bottom: 10px; }}
273            label {{ display: inline-block; width: 100px; }}
274            select, input {{ margin-left: 10px; }}
275            .stats-grid {{ display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }}
276            .stat-item {{ background: #f5f5f5; padding: 8px; border-radius: 3px; }}
277            .heatmap-container {{ position: relative; width: 400px; height: 300px; }}
278            .colorbar {{ width: 20px; height: 300px; background: linear-gradient(to top, blue, cyan, yellow, red); }}
279        "#
280        )?;
281        writeln!(&mut html, "</style>")?;
282        writeln!(&mut html, "<script>")?;
283        writeln!(
284            &mut html,
285            r#"
286            function updateVisualization() {{
287                const colormap = document.getElementById('colormap').value;
288                const plotType = document.getElementById('plotType').value;
289                // Update visualization based on controls
290                console.log('Updating visualization:', colormap, plotType);
291            }}
292
293            function exportView() {{
294                const content = document.getElementById('main-content').innerHTML;
295                const blob = new Blob([content], {{ type: 'text/html' }});
296                const url = URL.createObjectURL(blob);
297                const a = document.createElement('a');
298                a.href = url;
299                a.download = 'visualization.html';
300                a.click();
301                URL.revokeObjectURL(url);
302            }}
303        "#
304        )?;
305        writeln!(&mut html, "</script>")?;
306        writeln!(&mut html, "</head><body>")?;
307
308        writeln!(&mut html, "<h1>{}</h1>", title)?;
309
310        // Controls panel
311        writeln!(&mut html, "<div class='controls'>")?;
312        writeln!(&mut html, "<div class='control-group'>")?;
313        writeln!(&mut html, "<label>Color Map:</label>")?;
314        writeln!(
315            &mut html,
316            r#"<select id="colormap" onchange="updateVisualization()">"#
317        )?;
318        writeln!(&mut html, "<option value='viridis'>Viridis</option>")?;
319        writeln!(&mut html, "<option value='plasma'>Plasma</option>")?;
320        writeln!(&mut html, "<option value='jet'>Jet</option>")?;
321        writeln!(&mut html, "<option value='hot'>Hot</option>")?;
322        writeln!(&mut html, "</select>")?;
323        writeln!(&mut html, "</div>")?;
324
325        writeln!(&mut html, "<div class='control-group'>")?;
326        writeln!(&mut html, "<label>Plot Type:</label>")?;
327        writeln!(
328            &mut html,
329            r#"<select id="plotType" onchange="updateVisualization()">"#
330        )?;
331        writeln!(&mut html, "<option value='heatmap'>Heatmap</option>")?;
332        writeln!(&mut html, "<option value='contour'>Contour</option>")?;
333        writeln!(&mut html, "<option value='surface'>3D Surface</option>")?;
334        writeln!(&mut html, "</select>")?;
335        writeln!(&mut html, "</div>")?;
336
337        writeln!(
338            &mut html,
339            r#"<button onclick="exportView()">Export View</button>"#
340        )?;
341        writeln!(&mut html, "</div>")?;
342
343        writeln!(
344            &mut html,
345            "<div id='main-content' class='visualization-container'>"
346        )?;
347
348        // Statistics panel
349        let (height, width) = image.dim();
350        let mean = image.mean_or(T::zero());
351        let min_val = image.iter().cloned().fold(T::infinity(), T::min);
352        let max_val = image.iter().cloned().fold(T::neg_infinity(), T::max);
353
354        writeln!(&mut html, "<div class='plot-panel'>")?;
355        writeln!(&mut html, "<div class='plot-title'>Image Statistics</div>")?;
356        writeln!(&mut html, "<div class='stats-grid'>")?;
357        writeln!(&mut html, "<div class='stat-item'>Width: {}</div>", width)?;
358        writeln!(&mut html, "<div class='stat-item'>Height: {}</div>", height)?;
359        writeln!(
360            &mut html,
361            "<div class='stat-item'>Mean: {:.4}</div>",
362            mean.to_f64().unwrap_or(0.0)
363        )?;
364        writeln!(
365            &mut html,
366            "<div class='stat-item'>Min: {:.4}</div>",
367            min_val.to_f64().unwrap_or(0.0)
368        )?;
369        writeln!(
370            &mut html,
371            "<div class='stat-item'>Max: {:.4}</div>",
372            max_val.to_f64().unwrap_or(0.0)
373        )?;
374        writeln!(
375            &mut html,
376            "<div class='stat-item'>Pixels: {}</div>",
377            width * height
378        )?;
379        writeln!(&mut html, "</div>")?;
380        writeln!(&mut html, "</div>")?;
381
382        // Heatmap panel
383        writeln!(&mut html, "<div class='plot-panel'>")?;
384        writeln!(&mut html, "<div class='plot-title'>Heatmap View</div>")?;
385        writeln!(&mut html, "<div class='heatmap-container'>")?;
386
387        // Generate a simplified heatmap representation
388        let config = PlotConfig {
389            format: ReportFormat::Html,
390            colormap: ColorMap::Viridis,
391            title: "Interactive Heatmap".to_string(),
392            ..PlotConfig::default()
393        };
394
395        let heatmap = plot_heatmap(image, &config)?;
396        writeln!(&mut html, "{}", heatmap)?;
397
398        writeln!(&mut html, "</div>")?;
399        writeln!(&mut html, "</div>")?;
400
401        writeln!(&mut html, "</div>")?;
402        writeln!(&mut html, "</body></html>")?;
403
404        Ok(html)
405    }
406
407    /// Create a comparison visualization between multiple images
408    pub fn create_comparison_view<T>(
409        images: &[(&str, ArrayView2<T>)],
410        title: &str,
411    ) -> NdimageResult<String>
412    where
413        T: Float + FromPrimitive + ToPrimitive + Debug + Clone,
414    {
415        if images.is_empty() {
416            return Err(NdimageError::InvalidInput(
417                "No images provided for comparison".into(),
418            ));
419        }
420
421        let mut html = String::new();
422
423        writeln!(&mut html, "<!DOCTYPE html>")?;
424        writeln!(&mut html, "<html><head>")?;
425        writeln!(&mut html, "<title>{}</title>", title)?;
426        writeln!(&mut html, "<style>")?;
427        writeln!(
428            &mut html,
429            r#"
430            body {{ font-family: Arial, sans-serif; margin: 20px; }}
431            .comparison-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }}
432            .image-panel {{ border: 1px solid #ccc; padding: 15px; border-radius: 5px; }}
433            .image-title {{ font-weight: bold; margin-bottom: 10px; color: #333; text-align: center; }}
434            .image-stats {{ background: #f9f9f9; padding: 10px; margin-top: 10px; border-radius: 3px; }}
435            .stat-row {{ display: flex; justify-content: space-between; margin-bottom: 5px; }}
436            .difference-highlight {{ background: #ffe6e6; }}
437        "#
438        )?;
439        writeln!(&mut html, "</style>")?;
440        writeln!(&mut html, "</head><body>")?;
441
442        writeln!(&mut html, "<h1>{}</h1>", title)?;
443        writeln!(&mut html, "<div class='comparison-grid'>")?;
444
445        for (name, image) in images {
446            writeln!(&mut html, "<div class='image-panel'>")?;
447            writeln!(&mut html, "<div class='image-title'>{}</div>", name)?;
448
449            // Generate heatmap for this image
450            let config = PlotConfig {
451                format: ReportFormat::Html,
452                colormap: ColorMap::Viridis,
453                title: name.to_string(),
454                width: 250,
455                height: 200,
456                ..PlotConfig::default()
457            };
458
459            let heatmap = plot_heatmap(image, &config)?;
460            writeln!(&mut html, "{}", heatmap)?;
461
462            // Add statistics
463            let (height, width) = image.dim();
464            let mean = image.mean_or(T::zero());
465            let min_val = image.iter().cloned().fold(T::infinity(), T::min);
466            let max_val = image.iter().cloned().fold(T::neg_infinity(), T::max);
467
468            writeln!(&mut html, "<div class='image-stats'>")?;
469            writeln!(
470                &mut html,
471                "<div class='stat-row'><span>Dimensions:</span><span>{}×{}</span></div>",
472                height, width
473            )?;
474            writeln!(
475                &mut html,
476                "<div class='stat-row'><span>Mean:</span><span>{:.4}</span></div>",
477                mean.to_f64().unwrap_or(0.0)
478            )?;
479            writeln!(
480                &mut html,
481                "<div class='stat-row'><span>Range:</span><span>[{:.3}, {:.3}]</span></div>",
482                min_val.to_f64().unwrap_or(0.0),
483                max_val.to_f64().unwrap_or(0.0)
484            )?;
485            writeln!(&mut html, "</div>")?;
486
487            writeln!(&mut html, "</div>")?;
488        }
489
490        writeln!(&mut html, "</div>")?;
491        writeln!(&mut html, "</body></html>")?;
492
493        Ok(html)
494    }
495}
496
497#[cfg(test)]
498mod tests {
499    use super::*;
500    use scirs2_core::ndarray::{Array1, Array2};
501
502    #[test]
503    fn test_module_exports() {
504        // Test that all re-exports are available
505        let _config = PlotConfig::new();
506        let _colormap = ColorMap::Viridis;
507        let _format = ReportFormat::Html;
508        let _color = RgbColor::new(255, 0, 0);
509    }
510
511    #[test]
512    fn test_backward_compatibility_aliases() {
513        let img1 = Array2::<f64>::zeros((5, 5));
514        let img2 = Array2::<f64>::ones((5, 5));
515        let images = vec![img1.view(), img2.view()];
516        let config = PlotConfig::new().with_format(ReportFormat::Text);
517
518        // Test legacy alias
519        let result = createimage_montage(&images, 2, &config);
520        assert!(result.is_ok());
521    }
522
523    #[test]
524    fn test_statistical_functions() {
525        let data1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
526        let data2 = Array1::from_vec(vec![2.0, 4.0, 6.0, 8.0, 10.0]);
527
528        let datasets = vec![("Test A", data1.view()), ("Test B", data2.view())];
529
530        let config = PlotConfig::new()
531            .with_format(ReportFormat::Text)
532            .with_title("Statistical Test");
533
534        let result = plot_statistical_comparison(&datasets, &config);
535        assert!(result.is_ok());
536    }
537
538    #[test]
539    fn test_plotting_functions() {
540        let data = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
541        let config = PlotConfig::new()
542            .with_format(ReportFormat::Text)
543            .with_title("Histogram Test");
544
545        let result = plot_histogram(&data.view(), &config);
546        assert!(result.is_ok());
547    }
548
549    #[test]
550    fn test_colormap_functions() {
551        let colors = create_colormap(ColorMap::Viridis, 10);
552        assert_eq!(colors.len(), 10);
553
554        let color = viridis_colormap(0.5);
555        assert!(color.r > 0 || color.g > 0 || color.b > 0);
556    }
557
558    #[test]
559    fn test_report_generation() {
560        let image = Array2::<f64>::ones((10, 10));
561        let config = ReportConfig::new()
562            .with_format(ReportFormat::Text)
563            .minimal();
564
565        let result = generate_report(&image.view(), None, None, &config);
566        assert!(result.is_ok());
567    }
568}