1use scirs2_core::ndarray::ArrayStatCompat;
8use scirs2_core::ndarray::ArrayView2;
9use scirs2_core::numeric::{Float, FromPrimitive, ToPrimitive, Zero};
10use std::fmt::{Debug, Write};
11
12use crate::analysis::{ImageQualityMetrics, TextureMetrics};
13use crate::error::{NdimageError, NdimageResult};
14use crate::visualization::types::{ReportConfig, ReportFormat};
15use statrs::statistics::Statistics;
16
17#[allow(dead_code)]
50pub fn generate_report<T>(
51 image: &ArrayView2<T>,
52 qualitymetrics: Option<&ImageQualityMetrics<T>>,
53 texturemetrics: Option<&TextureMetrics<T>>,
54 config: &ReportConfig,
55) -> NdimageResult<String>
56where
57 T: Float + FromPrimitive + ToPrimitive + Debug + Clone,
58{
59 let mut report = String::new();
60
61 match config.format {
62 ReportFormat::Html => {
63 writeln!(&mut report, "<!DOCTYPE html>")?;
64 writeln!(&mut report, "<html><head><title>{}</title>", config.title)?;
65 writeln!(&mut report, "<style>")?;
66 writeln!(
67 &mut report,
68 "body {{ font-family: Arial, sans-serif; margin: 20px; }}"
69 )?;
70 writeln!(
71 &mut report,
72 "table {{ border-collapse: collapse; width: 100%; }}"
73 )?;
74 writeln!(
75 &mut report,
76 "th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}"
77 )?;
78 writeln!(&mut report, "th {{ background-color: #f2f2f2; }}")?;
79 writeln!(
80 &mut report,
81 ".metric-value {{ font-weight: bold; color: #2E86AB; }}"
82 )?;
83 writeln!(&mut report, "</style></head><body>")?;
84 writeln!(&mut report, "<h1>{}</h1>", config.title)?;
85 writeln!(
86 &mut report,
87 "<p><em>Generated by {}</em></p>",
88 config.author
89 )?;
90 }
91 ReportFormat::Markdown => {
92 writeln!(&mut report, "# {}", config.title)?;
93 writeln!(&mut report)?;
94 writeln!(&mut report, "*Generated by {}*", config.author)?;
95 writeln!(&mut report)?;
96 }
97 ReportFormat::Text => {
98 writeln!(&mut report, "{}", config.title)?;
99 writeln!(&mut report, "{}", "=".repeat(config.title.len()))?;
100 writeln!(&mut report)?;
101 writeln!(&mut report, "Generated by: {}", config.author)?;
102 writeln!(&mut report)?;
103 }
104 }
105
106 let (height, width) = image.dim();
108 add_image_info(&mut report, width, height, config.format)?;
109
110 if config.include_statistics {
112 add_basic_statistics(&mut report, image, config.format)?;
113 }
114
115 if config.include_qualitymetrics {
117 if let Some(metrics) = qualitymetrics {
118 add_quality_metrics(&mut report, metrics, config.format)?;
119 }
120 }
121
122 if config.includetexture_analysis {
124 if let Some(metrics) = texturemetrics {
125 add_texture_metrics(&mut report, metrics, config.format)?;
126 }
127 }
128
129 if config.format == ReportFormat::Html {
131 writeln!(&mut report, "</body></html>")?;
132 }
133
134 Ok(report)
135}
136
137#[allow(dead_code)]
141pub fn add_image_info(
142 report: &mut String,
143 width: usize,
144 height: usize,
145 format: ReportFormat,
146) -> Result<(), std::fmt::Error> {
147 match format {
148 ReportFormat::Html => {
149 writeln!(report, "<h2>Image Information</h2>")?;
150 writeln!(report, "<table>")?;
151 writeln!(report, "<tr><th>Property</th><th>Value</th></tr>")?;
152 writeln!(
153 report,
154 "<tr><td>Width</td><td class='metric-value'>{}</td></tr>",
155 width
156 )?;
157 writeln!(
158 report,
159 "<tr><td>Height</td><td class='metric-value'>{}</td></tr>",
160 height
161 )?;
162 writeln!(
163 report,
164 "<tr><td>Total Pixels</td><td class='metric-value'>{}</td></tr>",
165 width * height
166 )?;
167 writeln!(
168 report,
169 "<tr><td>Aspect Ratio</td><td class='metric-value'>{:.3}</td></tr>",
170 width as f64 / height as f64
171 )?;
172 writeln!(report, "</table>")?;
173 }
174 ReportFormat::Markdown => {
175 writeln!(report, "## Image Information")?;
176 writeln!(report)?;
177 writeln!(report, "| Property | Value |")?;
178 writeln!(report, "|----------|-------|")?;
179 writeln!(report, "| Width | {} |", width)?;
180 writeln!(report, "| Height | {} |", height)?;
181 writeln!(report, "| Total Pixels | {} |", width * height)?;
182 writeln!(
183 report,
184 "| Aspect Ratio | {:.3} |",
185 width as f64 / height as f64
186 )?;
187 writeln!(report)?;
188 }
189 ReportFormat::Text => {
190 writeln!(report, "Image Information")?;
191 writeln!(report, "-----------------")?;
192 writeln!(report, "Width: {}", width)?;
193 writeln!(report, "Height: {}", height)?;
194 writeln!(report, "Total Pixels: {}", width * height)?;
195 writeln!(report, "Aspect Ratio: {:.3}", width as f64 / height as f64)?;
196 writeln!(report)?;
197 }
198 }
199 Ok(())
200}
201
202#[allow(dead_code)]
206pub fn add_basic_statistics<T>(
207 report: &mut String,
208 image: &ArrayView2<T>,
209 format: ReportFormat,
210) -> Result<(), std::fmt::Error>
211where
212 T: Float + FromPrimitive + ToPrimitive + Debug + Clone,
213{
214 let mean = image.mean_or(T::zero());
215 let min_val = image.iter().cloned().fold(T::infinity(), T::min);
216 let max_val = image.iter().cloned().fold(T::neg_infinity(), T::max);
217
218 let variance = image
219 .mapv(|x| (x - mean) * (x - mean))
220 .mean()
221 .unwrap_or(T::zero());
222 let std_dev = variance.sqrt();
223
224 match format {
225 ReportFormat::Html => {
226 writeln!(report, "<h2>Basic Statistics</h2>")?;
227 writeln!(report, "<table>")?;
228 writeln!(report, "<tr><th>Statistic</th><th>Value</th></tr>")?;
229 writeln!(
230 report,
231 "<tr><td>Mean</td><td class='metric-value'>{:.6}</td></tr>",
232 mean.to_f64().unwrap_or(0.0)
233 )?;
234 writeln!(
235 report,
236 "<tr><td>Standard Deviation</td><td class='metric-value'>{:.6}</td></tr>",
237 std_dev.to_f64().unwrap_or(0.0)
238 )?;
239 writeln!(
240 report,
241 "<tr><td>Variance</td><td class='metric-value'>{:.6}</td></tr>",
242 variance.to_f64().unwrap_or(0.0)
243 )?;
244 writeln!(
245 report,
246 "<tr><td>Minimum</td><td class='metric-value'>{:.6}</td></tr>",
247 min_val.to_f64().unwrap_or(0.0)
248 )?;
249 writeln!(
250 report,
251 "<tr><td>Maximum</td><td class='metric-value'>{:.6}</td></tr>",
252 max_val.to_f64().unwrap_or(0.0)
253 )?;
254 writeln!(
255 report,
256 "<tr><td>Range</td><td class='metric-value'>{:.6}</td></tr>",
257 (max_val - min_val).to_f64().unwrap_or(0.0)
258 )?;
259 writeln!(report, "</table>")?;
260 }
261 ReportFormat::Markdown => {
262 writeln!(report, "## Basic Statistics")?;
263 writeln!(report)?;
264 writeln!(report, "| Statistic | Value |")?;
265 writeln!(report, "|-----------|-------|")?;
266 writeln!(report, "| Mean | {:.6} |", mean.to_f64().unwrap_or(0.0))?;
267 writeln!(
268 report,
269 "| Standard Deviation | {:.6} |",
270 std_dev.to_f64().unwrap_or(0.0)
271 )?;
272 writeln!(
273 report,
274 "| Variance | {:.6} |",
275 variance.to_f64().unwrap_or(0.0)
276 )?;
277 writeln!(
278 report,
279 "| Minimum | {:.6} |",
280 min_val.to_f64().unwrap_or(0.0)
281 )?;
282 writeln!(
283 report,
284 "| Maximum | {:.6} |",
285 max_val.to_f64().unwrap_or(0.0)
286 )?;
287 writeln!(
288 report,
289 "| Range | {:.6} |",
290 (max_val - min_val).to_f64().unwrap_or(0.0)
291 )?;
292 writeln!(report)?;
293 }
294 ReportFormat::Text => {
295 writeln!(report, "Basic Statistics")?;
296 writeln!(report, "----------------")?;
297 writeln!(
298 report,
299 "Mean: {:.6}",
300 mean.to_f64().unwrap_or(0.0)
301 )?;
302 writeln!(
303 report,
304 "Standard Deviation:{:.6}",
305 std_dev.to_f64().unwrap_or(0.0)
306 )?;
307 writeln!(
308 report,
309 "Variance: {:.6}",
310 variance.to_f64().unwrap_or(0.0)
311 )?;
312 writeln!(
313 report,
314 "Minimum: {:.6}",
315 min_val.to_f64().unwrap_or(0.0)
316 )?;
317 writeln!(
318 report,
319 "Maximum: {:.6}",
320 max_val.to_f64().unwrap_or(0.0)
321 )?;
322 writeln!(
323 report,
324 "Range: {:.6}",
325 (max_val - min_val).to_f64().unwrap_or(0.0)
326 )?;
327 writeln!(report)?;
328 }
329 }
330 Ok(())
331}
332
333#[allow(dead_code)]
337pub fn add_quality_metrics<T>(
338 report: &mut String,
339 metrics: &ImageQualityMetrics<T>,
340 format: ReportFormat,
341) -> Result<(), std::fmt::Error>
342where
343 T: Float + ToPrimitive,
344{
345 match format {
346 ReportFormat::Html => {
347 writeln!(report, "<h2>Quality Metrics</h2>")?;
348 writeln!(report, "<table>")?;
349 writeln!(report, "<tr><th>Metric</th><th>Value</th></tr>")?;
350 writeln!(
351 report,
352 "<tr><td>PSNR</td><td class='metric-value'>{:.3} dB</td></tr>",
353 metrics.psnr.to_f64().unwrap_or(0.0)
354 )?;
355 writeln!(
356 report,
357 "<tr><td>SSIM</td><td class='metric-value'>{:.6}</td></tr>",
358 metrics.ssim.to_f64().unwrap_or(0.0)
359 )?;
360 writeln!(
361 report,
362 "<tr><td>MSE</td><td class='metric-value'>{:.6}</td></tr>",
363 metrics.mse.to_f64().unwrap_or(0.0)
364 )?;
365 writeln!(
366 report,
367 "<tr><td>SNR</td><td class='metric-value'>{:.3} dB</td></tr>",
368 metrics.snr.to_f64().unwrap_or(0.0)
369 )?;
370 writeln!(
371 report,
372 "<tr><td>Entropy</td><td class='metric-value'>{:.3} bits</td></tr>",
373 metrics.entropy.to_f64().unwrap_or(0.0)
374 )?;
375 writeln!(
376 report,
377 "<tr><td>Sharpness</td><td class='metric-value'>{:.6}</td></tr>",
378 metrics.sharpness.to_f64().unwrap_or(0.0)
379 )?;
380 writeln!(report, "</table>")?;
381 }
382 ReportFormat::Markdown => {
383 writeln!(report, "## Quality Metrics")?;
384 writeln!(report)?;
385 writeln!(report, "| Metric | Value |")?;
386 writeln!(report, "|--------|-------|")?;
387 writeln!(
388 report,
389 "| PSNR | {:.3} dB |",
390 metrics.psnr.to_f64().unwrap_or(0.0)
391 )?;
392 writeln!(
393 report,
394 "| SSIM | {:.6} |",
395 metrics.ssim.to_f64().unwrap_or(0.0)
396 )?;
397 writeln!(
398 report,
399 "| MSE | {:.6} |",
400 metrics.mse.to_f64().unwrap_or(0.0)
401 )?;
402 writeln!(
403 report,
404 "| SNR | {:.3} dB |",
405 metrics.snr.to_f64().unwrap_or(0.0)
406 )?;
407 writeln!(
408 report,
409 "| Entropy | {:.3} bits |",
410 metrics.entropy.to_f64().unwrap_or(0.0)
411 )?;
412 writeln!(
413 report,
414 "| Sharpness | {:.6} |",
415 metrics.sharpness.to_f64().unwrap_or(0.0)
416 )?;
417 writeln!(report)?;
418 }
419 ReportFormat::Text => {
420 writeln!(report, "Quality Metrics")?;
421 writeln!(report, "---------------")?;
422 writeln!(
423 report,
424 "PSNR: {:.3} dB",
425 metrics.psnr.to_f64().unwrap_or(0.0)
426 )?;
427 writeln!(
428 report,
429 "SSIM: {:.6}",
430 metrics.ssim.to_f64().unwrap_or(0.0)
431 )?;
432 writeln!(
433 report,
434 "MSE: {:.6}",
435 metrics.mse.to_f64().unwrap_or(0.0)
436 )?;
437 writeln!(
438 report,
439 "SNR: {:.3} dB",
440 metrics.snr.to_f64().unwrap_or(0.0)
441 )?;
442 writeln!(
443 report,
444 "Entropy: {:.3} bits",
445 metrics.entropy.to_f64().unwrap_or(0.0)
446 )?;
447 writeln!(
448 report,
449 "Sharpness: {:.6}",
450 metrics.sharpness.to_f64().unwrap_or(0.0)
451 )?;
452 writeln!(report)?;
453 }
454 }
455 Ok(())
456}
457
458#[allow(dead_code)]
462pub fn add_texture_metrics<T>(
463 report: &mut String,
464 metrics: &TextureMetrics<T>,
465 format: ReportFormat,
466) -> Result<(), std::fmt::Error>
467where
468 T: Float + ToPrimitive,
469{
470 match format {
471 ReportFormat::Html => {
472 writeln!(report, "<h2>Texture Analysis</h2>")?;
473 writeln!(report, "<table>")?;
474 writeln!(report, "<tr><th>Metric</th><th>Value</th></tr>")?;
475 writeln!(
476 report,
477 "<tr><td>GLCM Contrast</td><td class='metric-value'>{:.6}</td></tr>",
478 metrics.glcm_contrast.to_f64().unwrap_or(0.0)
479 )?;
480 writeln!(
481 report,
482 "<tr><td>GLCM Homogeneity</td><td class='metric-value'>{:.6}</td></tr>",
483 metrics.glcm_homogeneity.to_f64().unwrap_or(0.0)
484 )?;
485 writeln!(
486 report,
487 "<tr><td>GLCM Energy</td><td class='metric-value'>{:.6}</td></tr>",
488 metrics.glcm_energy.to_f64().unwrap_or(0.0)
489 )?;
490 writeln!(
491 report,
492 "<tr><td>LBP Uniformity</td><td class='metric-value'>{:.6}</td></tr>",
493 metrics.lbp_uniformity.to_f64().unwrap_or(0.0)
494 )?;
495 writeln!(
496 report,
497 "<tr><td>Gabor Mean</td><td class='metric-value'>{:.6}</td></tr>",
498 metrics.gabor_mean.to_f64().unwrap_or(0.0)
499 )?;
500 writeln!(
501 report,
502 "<tr><td>Gabor Std</td><td class='metric-value'>{:.6}</td></tr>",
503 metrics.gabor_std.to_f64().unwrap_or(0.0)
504 )?;
505 writeln!(
506 report,
507 "<tr><td>Fractal Dimension</td><td class='metric-value'>{:.3}</td></tr>",
508 metrics.fractal_dimension.to_f64().unwrap_or(0.0)
509 )?;
510 writeln!(report, "</table>")?;
511 }
512 ReportFormat::Markdown => {
513 writeln!(report, "## Texture Analysis")?;
514 writeln!(report)?;
515 writeln!(report, "| Metric | Value |")?;
516 writeln!(report, "|--------|-------|")?;
517 writeln!(
518 report,
519 "| GLCM Contrast | {:.6} |",
520 metrics.glcm_contrast.to_f64().unwrap_or(0.0)
521 )?;
522 writeln!(
523 report,
524 "| GLCM Homogeneity | {:.6} |",
525 metrics.glcm_homogeneity.to_f64().unwrap_or(0.0)
526 )?;
527 writeln!(
528 report,
529 "| GLCM Energy | {:.6} |",
530 metrics.glcm_energy.to_f64().unwrap_or(0.0)
531 )?;
532 writeln!(
533 report,
534 "| LBP Uniformity | {:.6} |",
535 metrics.lbp_uniformity.to_f64().unwrap_or(0.0)
536 )?;
537 writeln!(
538 report,
539 "| Gabor Mean | {:.6} |",
540 metrics.gabor_mean.to_f64().unwrap_or(0.0)
541 )?;
542 writeln!(
543 report,
544 "| Gabor Std | {:.6} |",
545 metrics.gabor_std.to_f64().unwrap_or(0.0)
546 )?;
547 writeln!(
548 report,
549 "| Fractal Dimension | {:.3} |",
550 metrics.fractal_dimension.to_f64().unwrap_or(0.0)
551 )?;
552 writeln!(report)?;
553 }
554 ReportFormat::Text => {
555 writeln!(report, "Texture Analysis")?;
556 writeln!(report, "----------------")?;
557 writeln!(
558 report,
559 "GLCM Contrast: {:.6}",
560 metrics.glcm_contrast.to_f64().unwrap_or(0.0)
561 )?;
562 writeln!(
563 report,
564 "GLCM Homogeneity: {:.6}",
565 metrics.glcm_homogeneity.to_f64().unwrap_or(0.0)
566 )?;
567 writeln!(
568 report,
569 "GLCM Energy: {:.6}",
570 metrics.glcm_energy.to_f64().unwrap_or(0.0)
571 )?;
572 writeln!(
573 report,
574 "LBP Uniformity: {:.6}",
575 metrics.lbp_uniformity.to_f64().unwrap_or(0.0)
576 )?;
577 writeln!(
578 report,
579 "Gabor Mean: {:.6}",
580 metrics.gabor_mean.to_f64().unwrap_or(0.0)
581 )?;
582 writeln!(
583 report,
584 "Gabor Std: {:.6}",
585 metrics.gabor_std.to_f64().unwrap_or(0.0)
586 )?;
587 writeln!(
588 report,
589 "Fractal Dimension: {:.3}",
590 metrics.fractal_dimension.to_f64().unwrap_or(0.0)
591 )?;
592 writeln!(report)?;
593 }
594 }
595 Ok(())
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601 use crate::visualization::types::{ReportConfig, ReportFormat};
602 use scirs2_core::ndarray::Array2;
603
604 #[test]
605 fn test_generate_basic_report() {
606 let image = Array2::<f64>::ones((10, 10));
607 let config = ReportConfig::new()
608 .with_format(ReportFormat::Text)
609 .with_header("Test Report", "Test Suite")
610 .minimal(); let result = generate_report(&image.view(), None, None, &config);
613 assert!(result.is_ok());
614
615 let report = result.expect("Operation failed");
616 assert!(report.contains("Test Report"));
617 assert!(report.contains("Test Suite"));
618 assert!(report.contains("Image Information"));
619 assert!(report.contains("Width: 10"));
620 assert!(report.contains("Height: 10"));
621 }
622
623 #[test]
624 fn test_generate_html_report() {
625 let image = Array2::<f64>::zeros((5, 5));
626 let config = ReportConfig::new()
627 .with_format(ReportFormat::Html)
628 .with_header("HTML Test", "HTML Suite")
629 .with_sections(true, false, false, false, false); let result = generate_report(&image.view(), None, None, &config);
632 assert!(result.is_ok());
633
634 let report = result.expect("Operation failed");
635 assert!(report.contains("<!DOCTYPE html>"));
636 assert!(report.contains("<h1>HTML Test</h1>"));
637 assert!(report.contains("Basic Statistics"));
638 assert!(report.contains("</body></html>"));
639 }
640
641 #[test]
642 fn test_generate_markdown_report() {
643 let image = Array2::from_elem((3, 3), 2.5f64);
644 let config = ReportConfig::new()
645 .with_format(ReportFormat::Markdown)
646 .with_header("Markdown Test", "MD Suite")
647 .with_sections(true, false, false, false, false);
648
649 let result = generate_report(&image.view(), None, None, &config);
650 assert!(result.is_ok());
651
652 let report = result.expect("Operation failed");
653 assert!(report.contains("# Markdown Test"));
654 assert!(report.contains("*Generated by MD Suite*"));
655 assert!(report.contains("## Image Information"));
656 assert!(report.contains("## Basic Statistics"));
657 assert!(report.contains("| Mean | 2.500000 |"));
658 }
659
660 #[test]
661 fn test_add_image_info_all_formats() {
662 let mut html_report = String::new();
663 let mut md_report = String::new();
664 let mut text_report = String::new();
665
666 assert!(add_image_info(&mut html_report, 100, 200, ReportFormat::Html).is_ok());
668 assert!(html_report.contains("<h2>Image Information</h2>"));
669 assert!(html_report.contains("100"));
670 assert!(html_report.contains("200"));
671 assert!(html_report.contains("20000")); assert!(add_image_info(&mut md_report, 100, 200, ReportFormat::Markdown).is_ok());
675 assert!(md_report.contains("## Image Information"));
676 assert!(md_report.contains("| Width | 100 |"));
677
678 assert!(add_image_info(&mut text_report, 100, 200, ReportFormat::Text).is_ok());
680 assert!(text_report.contains("Width: 100"));
681 assert!(text_report.contains("Height: 200"));
682 assert!(text_report.contains("Aspect Ratio: 0.500"));
683 }
684
685 #[test]
686 fn test_add_basic_statistics() {
687 let image =
688 Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0]).expect("Operation failed");
689 let mut report = String::new();
690
691 let result = add_basic_statistics(&mut report, &image.view(), ReportFormat::Text);
692 assert!(result.is_ok());
693
694 assert!(report.contains("Basic Statistics"));
695 assert!(report.contains("Mean: 2.500000")); assert!(report.contains("Minimum: 1.000000"));
697 assert!(report.contains("Maximum: 4.000000"));
698 assert!(report.contains("Range: 3.000000"));
699 }
700}