use std::collections::HashMap;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub struct ExampleTutorial {
steps: Vec<TutorialStep>,
outputs: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct TutorialStep {
pub title: String,
pub description: String,
pub code_example: String,
pub expected_output: String,
pub concepts: Vec<String>,
pub related_functions: Vec<String>,
}
impl ExampleTutorial {
pub fn new() -> Self {
Self {
steps: Vec::new(),
outputs: HashMap::new(),
}
}
pub fn add_step(&mut self, step: TutorialStep) {
self.steps.push(step);
}
pub fn generate_complete_tutorial(&mut self) -> Result<()> {
self.add_filter_examples()?;
self.add_morphology_examples()?;
self.add_interpolation_examples()?;
self.add_measurement_examples()?;
self.add_segmentation_examples()?;
self.add_feature_detection_examples()?;
self.add_advanced_workflow_examples()?;
Ok(())
}
fn add_filter_examples(&mut self) -> Result<()> {
self.add_step(TutorialStep {
title: "Gaussian Smoothing for Noise Reduction".to_string(),
description:
"Learn how to apply Gaussian filters to reduce noise while preserving edges"
.to_string(),
code_example: r#"
use scirs2_ndimage::filters::{gaussian_filter, BorderMode};
use scirs2_core::ndarray::Array2;
// Create a noisy image (in practice, you'd load from file)
let mut noisyimage = Array2::zeros((100, 100));
// Add some signal
for i in 40..60 {
for j in 40..60 {
noisyimage[[i, j]] = 1.0;
}
}
// Add noise (in practice, this would come from your data)
for i in 0..100 {
for j in 0..100 {
noisyimage[[i, j]] += 0.1 * ((i + j) as f64).sin();
}
}
// Apply Gaussian filter with different sigma values
let smooth_light = gaussian_filter(&noisyimage, 1.0, None, None)?;
let smooth_medium = gaussian_filter(&noisyimage, 2.0, None, None)?;
let smooth_heavy = gaussian_filter(&noisyimage, 4.0, None, None)?;
// Use different border modes
let reflected = gaussian_filter(&noisyimage, 2.0, Some(BorderMode::Reflect), None)?;
let wrapped = gaussian_filter(&noisyimage, 2.0, Some(BorderMode::Wrap), None)?;
let constant = gaussian_filter(&noisyimage, 2.0, Some(BorderMode::Constant), Some(0.0))?;
println!("Applied Gaussian filters with sigma: 1.0, 2.0, 4.0");
println!("Border modes: Reflect, Wrap, Constant");
"#
.to_string(),
expected_output: "Progressively smoother images with different boundary handling"
.to_string(),
concepts: vec![
"Gaussian smoothing".to_string(),
"Noise reduction".to_string(),
"Border mode handling".to_string(),
"Parameter selection".to_string(),
],
related_functions: vec![
"uniform_filter".to_string(),
"median_filter".to_string(),
"bilateral_filter".to_string(),
],
});
self.add_step(TutorialStep {
title: "Median Filtering for Impulse Noise Removal".to_string(),
description:
"Use median filters to remove salt-and-pepper noise while preserving edges"
.to_string(),
code_example: r#"
use scirs2_ndimage::filters::{median_filter, BorderMode};
use scirs2_core::ndarray::Array2;
// Create image with impulse noise
let mut image_with_impulses = Array2::from_shape_fn((50, 50), |(i, j)| {
// Create some structure
if (i as i32 - 25).pow(2) + (j as i32 - 25).pow(2) < 100 {
1.0
} else {
0.0
}
});
// Add impulse noise (salt and pepper)
image_with_impulses[[10, 10]] = 1.0; // salt
image_with_impulses[[35, 35]] = 0.0; // pepper
image_with_impulses[[20, 30]] = 1.0; // salt
image_with_impulses[[40, 15]] = 0.0; // pepper
// Apply median filter with different kernel sizes
let cleaned_3x3 = median_filter(&image_with_impulses, &[3, 3], None)?;
let cleaned_5x5 = median_filter(&image_with_impulses, &[5, 5], None)?;
let cleaned_7x7 = median_filter(&image_with_impulses, &[7, 7], None)?;
// Compare with Gaussian filter (less effective for impulse noise)
let gaussian_cleaned = gaussian_filter(&image_with_impulses, 1.0, None, None)?;
println!("Median filter effectively removes impulse noise");
println!("Larger kernels remove more noise but may blur edges");
"#
.to_string(),
expected_output:
"Clean images with impulse noise removed, edge preservation comparison".to_string(),
concepts: vec![
"Impulse noise removal".to_string(),
"Edge preservation".to_string(),
"Kernel size effects".to_string(),
"Filter comparison".to_string(),
],
related_functions: vec![
"rank_filter".to_string(),
"percentile_filter".to_string(),
"minimum_filter".to_string(),
"maximum_filter".to_string(),
],
});
self.add_step(TutorialStep {
title: "Edge Detection with Sobel and Laplacian Filters".to_string(),
description: "Detect edges using gradient-based and Laplacian operators".to_string(),
code_example: r#"
use scirs2_ndimage::filters::{sobel, laplace, gaussian_filter};
use scirs2_core::ndarray::Array2;
// Create test image with clear edges
let image = Array2::from_shape_fn((60, 60), |(i, j)| {
if i > 30 && j > 30 {
1.0
} else if i < 20 || j < 20 {
0.5
} else {
0.0
}
});
// Pre-smooth to reduce noise
let smoothed = gaussian_filter(&image, 1.0, None, None)?;
// Detect edges with Sobel filter
let edges_x = sobel(&smoothed, Some(0), None, None)?; // Vertical edges
let edges_y = sobel(&smoothed, Some(1), None, None)?; // Horizontal edges
let edges_magnitude = sobel(&smoothed, None, None, None)?; // Gradient magnitude
// Detect edges with Laplacian (second derivative)
let laplacian_edges = laplace(&smoothed, None)?;
// Combine for comprehensive edge detection
let combined_edges = Array2::from_shape_fn(image.dim(), |(i, j)| {
let sobel_val = edges_magnitude[[i, j]];
let laplacian_val = laplacian_edges[[i, j]].abs();
(sobel_val + 0.5 * laplacian_val).min(1.0)
});
println!("Detected edges using Sobel (gradient) and Laplacian (second derivative)");
println!("Combined approach provides comprehensive edge information");
"#
.to_string(),
expected_output: "Edge maps showing different types of edge information".to_string(),
concepts: vec![
"Gradient-based edge detection".to_string(),
"Laplacian edge detection".to_string(),
"Edge orientation".to_string(),
"Multi-scale edge detection".to_string(),
],
related_functions: vec![
"canny".to_string(),
"prewitt".to_string(),
"scharr".to_string(),
"roberts".to_string(),
],
});
Ok(())
}
fn add_morphology_examples(&mut self) -> Result<()> {
self.add_step(TutorialStep {
title: "Binary Morphology for Shape Analysis".to_string(),
description: "Use erosion, dilation, opening, and closing for shape processing".to_string(),
code_example: r#"
use scirs2_ndimage::morphology::{
binary_erosion, binary_dilation, binary_opening, binary_closing,
generate_binary_structure, disk_structure
};
use scirs2_core::ndarray::Array2;
// Create binary image with various shapes
let mut binary_image = Array2::from_elem((80, 80), false);
// Add some shapes
for i in 20..30 {
for j in 20..35 {
binary_image[[i, j]] = true; // Rectangle
}
}
for i in 50..70 {
for j in 50..70 {
if (i as i32 - 60).pow(2) + (j as i32 - 60).pow(2) < 80 {
binary_image[[i, j]] = true; // Circle
}
}
}
// Add some noise (small isolated pixels)
binary_image[[10, 10]] = true;
binary_image[[70, 20]] = true;
// Define structuring elements
let cross_3x3 = generate_binary_structure(2, 1)?; // 4-connected
let square_3x3 = generate_binary_structure(2, 2)?; // 8-connected
let disk_5 = disk_structure(5)?; // Circular structuring element
// Basic morphological operations
let eroded = binary_erosion(&binary_image, Some(&cross_3x3), None, None, None, None, None)?;
let dilated = binary_dilation(&binary_image, Some(&cross_3x3), None, None, None, None, None)?;
// Compound operations
let opened = binary_opening(&binary_image, Some(&cross_3x3), None, None, None, None)?; // Remove noise
let closed = binary_closing(&binary_image, Some(&cross_3x3), None, None, None, None)?; // Fill holes
// Different structuring elements produce different results
let opened_disk = binary_opening(&binary_image, Some(&disk_5), None, None, None, None)?;
println!("Applied morphological operations:");
println!("- Erosion: shrinks objects");
println!("- Dilation: expands objects");
println!("- Opening: removes noise, separates connected objects");
println!("- Closing: fills holes, connects nearby objects");
"#.to_string(),
expected_output: "Processed binary images showing shape modifications".to_string(),
concepts: vec![
"Binary morphology".to_string(),
"Structuring elements".to_string(),
"Noise removal".to_string(),
"Shape modification".to_string(),
],
related_functions: vec![
"grey_erosion".to_string(),
"grey_dilation".to_string(),
"white_tophat".to_string(),
"black_tophat".to_string(),
],
});
self.add_step(TutorialStep {
title: "Grayscale Morphology for Contrast Enhancement".to_string(),
description: "Apply morphological operations to grayscale images for various effects"
.to_string(),
code_example: r#"
use scirs2_ndimage::morphology::{
grey_erosion, grey_dilation, grey_opening, grey_closing,
white_tophat, black_tophat, morphological_gradient
};
use scirs2_core::ndarray::Array2;
// Create grayscale test image
let image = Array2::from_shape_fn((60, 60), |(i, j)| {
let center_i = 30.0;
let center_j = 30.0;
let distance = ((i as f64 - center_i).powi(2) + (j as f64 - center_j).powi(2)).sqrt();
if distance < 15.0 {
0.8 - distance / 20.0 // Bright center, fading to edges
} else {
0.1 + 0.1 * ((i + j) as f64 * 0.2).sin() // Textured background
}
});
// Basic grayscale morphology
let eroded = grey_erosion(&image, None, None, None, None, None)?;
let dilated = grey_dilation(&image, None, None, None, None, None)?;
// Compound operations
let opened = grey_opening(&image, None, None, None, None, None)?; // Remove bright noise
let closed = grey_closing(&image, None, None, None, None, None)?; // Remove dark noise
// Top-hat transformations for feature enhancement
let white_hat = white_tophat(&image, None, None, None, None, None)?; // Bright features
let black_hat = black_tophat(&image, None, None, None, None, None)?; // Dark features
// Morphological gradient for edge detection
let gradient = morphological_gradient(&image, None, None, None, None, None)?;
// Enhance contrast by combining operations
let enhanced = Array2::from_shape_fn(image.dim(), |(i, j)| {
let original = image[[i, j]];
let white_feature = "white_hat"[[i, j]];
let black_feature = "black_hat"[[i, j]];
(original + white_feature - black_feature).max(0.0).min(1.0)
});
println!("Applied grayscale morphological operations for:");
println!("- Noise removal (opening/closing)");
println!("- Feature enhancement (top-hat transforms)");
println!("- Edge detection (morphological gradient)");
println!("- Contrast enhancement (combination of operations)");
"#
.to_string(),
expected_output: "Enhanced grayscale images with improved contrast and features"
.to_string(),
concepts: vec![
"Grayscale morphology".to_string(),
"Feature enhancement".to_string(),
"Contrast adjustment".to_string(),
"Top-hat transforms".to_string(),
],
related_functions: vec![
"morphological_laplace".to_string(),
"distance_transform_edt".to_string(),
"label".to_string(),
],
});
Ok(())
}
fn add_interpolation_examples(&mut self) -> Result<()> {
self.add_step(TutorialStep {
title: "Image Interpolation and Geometric Transformations".to_string(),
description: "Resize, rotate, and transform images with various interpolation methods".to_string(),
code_example: r#"
use scirs2_ndimage::interpolation::{
zoom, rotate, shift, affine_transform, map_coordinates,
InterpolationOrder, BoundaryMode
};
use scirs2_core::ndarray::{Array2, Array1};
// Create test image with clear features
let image = Array2::from_shape_fn((40, 40), |(i, j)| {
let x = i as f64 - 20.0;
let y = j as f64 - 20.0;
// Create checkerboard pattern
if ((i / 5) + (j / 5)) % 2 == 0 {
1.0
} else {
0.0
}
});
// Zooming with different interpolation orders
let zoomed_nearest = zoom(&image, &[2.0, 2.0], Some(InterpolationOrder::Nearest), None, None, None)?;
let zoomed_linear = zoom(&image, &[2.0, 2.0], Some(InterpolationOrder::Linear), None, None, None)?;
let zoomed_cubic = zoom(&image, &[2.0, 2.0], Some(InterpolationOrder::Cubic), None, None, None)?;
// Rotation with different angles
let rotated_45 = rotate(&image, 45.0, None, None, None, None, None, None)?;
let rotated_30 = rotate(&image, 30.0, Some(InterpolationOrder::Linear), None, None, None, None, None)?;
// Translation (shifting)
let shifted = shift(&image, &[5.0, -3.0], None, None, None, None)?;
// Affine transformation (scaling + rotation + translation)
let transformation_matrix = Array2::from_shape_vec((2, 2), vec![
1.5 * 45.0_f64.to_radians().cos(), -1.5 * 45.0_f64.to_radians().sin(),
1.5 * 45.0_f64.to_radians().sin(), 1.5 * 45.0_f64.to_radians().cos()
])?;
let offset = Array1::from_vec(vec![5.0, -2.0]);
let transformed = affine_transform(
&image,
&transformation_matrix,
Some(&offset),
None,
Some(InterpolationOrder::Linear),
None,
None,
None
)?;
// Custom coordinate mapping
let coords = Array2::from_shape_fn((2, 40 * 40), |(axis, idx)| {
let i = idx / 40;
let j = idx % 40;
match axis {
0 => i as f64 + 2.0 * (j as f64 * 0.1).sin(), // Wavy distortion
1 => j as f64 + 1.0 * (i as f64 * 0.1).cos(, _ => 0.0
}
});
let warped = map_coordinates(&image, &coords, Some(InterpolationOrder::Linear), None, None, None)?;
println!("Applied various geometric transformations:");
println!("- Zooming with different interpolation orders");
println!("- Rotation and translation");
println!("- Affine transformations (combined scaling, rotation, translation)");
println!("- Custom coordinate mapping for complex distortions");
"#.to_string(),
expected_output: "Transformed images showing different interpolation and geometric effects".to_string(),
concepts: vec![
"Image interpolation".to_string(),
"Geometric transformations".to_string(),
"Interpolation order effects".to_string(),
"Coordinate mapping".to_string(),
],
related_functions: vec![
"geometric_transform".to_string(),
"spline_filter".to_string(),
"value_at_coordinates".to_string(),
],
});
Ok(())
}
fn add_measurement_examples(&mut self) -> Result<()> {
self.add_step(TutorialStep {
title: "Image Measurements and Region Analysis".to_string(),
description: "Extract quantitative information from images using measurement functions"
.to_string(),
code_example: r#"
use scirs2_ndimage::measurements::{
center_of_mass, find_objects, moments, label,
sum_labels, mean_labels, variance_labels,
extrema, region_properties
};
use scirs2_ndimage::morphology::label;
use scirs2_core::ndarray::Array2;
// Create test image with multiple objects
let mut image = Array2::zeros((60, 60));
// Object 1: Circle in top-left
for i in 10..20 {
for j in 10..20 {
if (i as i32 - 15).pow(2) + (j as i32 - 15).pow(2) < 25 {
image[[i, j]] = 2.0;
}
}
}
// Object 2: Rectangle in top-right
for i in 10..20 {
for j in 40..50 {
image[[i, j]] = 3.0;
}
}
// Object 3: Triangle-like shape in bottom
for i in 40..50 {
for j in (25 - (i - 40))..(25 + (i - 40) + 1) {
if j >= 0 && j < 60 {
image[[i, j]] = 1.5;
}
}
}
// Basic measurements
let total_centroid = center_of_mass(&image)?;
let total_moments = moments(&image)?;
let (min_val, max_val, min_pos, max_pos) = extrema(&image)?;
println!("Global measurements:");
println!("Center of mass: {:?}", total_centroid);
println!("Min value: {} at {:?}", min_val, min_pos);
println!("Max value: {} at {:?}", max_val, max_pos);
// Label connected components for individual object analysis
let binary_image = Array2::from_shape_fn(image.dim(), |(i, j)| image[[i, j]] > 0.0);
let (labeled, num_labels) = label(&binary_image, None)?;
println!("Found {} connected objects", num_labels);
// Analyze each labeled region
let sums = sum_labels(&image, &labeled, Some(num_labels))?;
let means = mean_labels(&image, &labeled, Some(num_labels))?;
let variances = variance_labels(&image, &labeled, Some(num_labels))?;
for label_id in 1..=num_labels {
println!("Object {}:", label_id);
println!(" Sum: {:.2}", sums[label_id - 1]);
println!(" Mean: {:.2}", means[label_id - 1]);
println!(" Variance: {:.2}", variances[label_id - 1]);
}
// Find bounding boxes of objects
let objects = find_objects(&labeled)?;
for (i, bbox) in objects.iter().enumerate() {
if let Some(bbox) = bbox {
println!("Object {} bounding box: {:?}", i + 1, bbox);
}
}
// Calculate region properties
let properties = region_properties(&labeled, Some(&image))?;
for (i, prop) in properties.iter().enumerate() {
println!("Object {} properties:", i + 1);
println!(" Area: {}", prop.area);
println!(" Centroid: {:?}", prop.centroid);
println!(" Perimeter: {}", prop.perimeter);
println!(" Major axis length: {:.2}", prop.major_axis_length);
println!(" Minor axis length: {:.2}", prop.minor_axis_length);
println!(" Orientation: {:.2} degrees", prop.orientation.to_degrees());
}
"#
.to_string(),
expected_output: "Quantitative measurements for each object in the image".to_string(),
concepts: vec![
"Region analysis".to_string(),
"Object measurements".to_string(),
"Connected components".to_string(),
"Statistical analysis".to_string(),
],
related_functions: vec![
"watershed".to_string(),
"peak_local_maxima".to_string(),
"histogram".to_string(),
],
});
Ok(())
}
fn add_segmentation_examples(&mut self) -> Result<()> {
self.add_step(TutorialStep {
title: "Image Segmentation Techniques".to_string(),
description: "Segment images into regions using thresholding and watershed algorithms"
.to_string(),
code_example: r#"
use scirs2_ndimage::segmentation::{
threshold_binary, otsu_threshold, adaptive_threshold,
watershed, marker_watershed, AdaptiveMethod
};
use scirs2_ndimage::filters::gaussian_filter;
use scirs2_ndimage::morphology::{binary_erosion, label};
use scirs2_core::ndarray::Array2;
// Create test image with multiple regions
let image = Array2::from_shape_fn((80, 80), |(i, j)| {
let x = i as f64 - 40.0;
let y = j as f64 - 40.0;
let distance = (x * x + y * y).sqrt();
if distance < 15.0 {
0.8 // Bright center
} else if distance < 25.0 {
0.4 // Medium ring
} else if distance < 35.0 {
0.6 // Brighter outer ring
} else {
0.1 // Dark background
}
}) + Array2::from_shape_fn((80, 80), |(i, j)| {
0.05 * ((i as f64 * 0.2).sin() + (j as f64 * 0.3).cos()) // Add some texture
});
// Simple binary thresholding
let binary_simple = threshold_binary(&image, 0.3)?;
// Otsu's automatic threshold selection
let otsu_threshold_value = otsu_threshold(&image)?;
let binary_otsu = threshold_binary(&image, otsu_threshold_value)?;
// Adaptive thresholding for varying illumination
let binary_adaptive_mean = adaptive_threshold(
&image,
11, // window size
AdaptiveMethod::Mean,
0.1 // offset
)?;
let binary_adaptive_gaussian = adaptive_threshold(
&image,
11,
AdaptiveMethod::Gaussian,
0.05
)?;
println!("Applied thresholding methods:");
println!("- Simple binary threshold");
println!("- Otsu automatic threshold: {:.3}", otsu_threshold_value);
println!("- Adaptive mean threshold");
println!("- Adaptive Gaussian threshold");
// Watershed segmentation for separating touching objects
let smoothed = gaussian_filter(&image, 1.0, None, None)?;
let watershed_result = watershed(&smoothed, None, None, None)?;
// Marker-controlled watershed for better control
// Create markers by finding local maxima
let eroded = binary_erosion(&binary_otsu, None, None, None, None, None, None)?;
let (markers, num_markers) = label(&eroded, None)?;
let marker_watershed_result = marker_watershed(
&smoothed,
&markers,
None,
None,
None
)?;
println!("Watershed segmentation:");
println!("- Basic watershed found {} regions", watershed_result.iter().max().unwrap_or(&0) + 1);
println!("- Marker-controlled watershed with {} markers", num_markers);
// Combine different segmentation results
let combined_segmentation = Array2::from_shape_fn(image.dim(), |(i, j)| {
let otsu_val = if binary_otsu[[i, j]] { 1 } else { 0 };
let adaptive_val = if binary_adaptive_gaussian[[i, j]] { 2 } else { 0 };
let watershed_val = watershed_result[[i, j]] * 3;
otsu_val + adaptive_val + watershed_val
});
println!("Created combined segmentation using multiple methods");
"#
.to_string(),
expected_output: "Segmented images showing different region separation techniques"
.to_string(),
concepts: vec![
"Image thresholding".to_string(),
"Adaptive segmentation".to_string(),
"Watershed algorithm".to_string(),
"Marker-controlled segmentation".to_string(),
],
related_functions: vec![
"chan_vese".to_string(),
"active_contour".to_string(),
"graph_cuts".to_string(),
],
});
Ok(())
}
fn add_feature_detection_examples(&mut self) -> Result<()> {
self.add_step(TutorialStep {
title: "Feature Detection and Corner Finding".to_string(),
description: "Detect corners, edges, and other features in images".to_string(),
code_example: r#"
use scirs2_ndimage::features::{
canny, harris_corners, fast_corners, sobel_edges,
gradient_edges, GradientMethod
};
use scirs2_ndimage::filters::gaussian_filter;
use scirs2_core::ndarray::Array2;
// Create test image with corners and edges
let image = Array2::from_shape_fn((60, 60), |(i, j)| {
// Create a rectangular structure with internal features
if (i > 15 && i < 45 && j > 15 && j < 45) {
if (i > 25 && i < 35 && j > 25 && j < 35) {
0.3 // Inner rectangle (darker)
} else {
0.8 // Outer rectangle (bright)
}
} else {
0.1 // Background (dark)
}
}) + Array2::from_shape_fn((60, 60), |(i, j)| {
// Add some texture and additional features
if (i as i32 - 20).pow(2) + (j as i32 - 40).pow(2) < 25 {
0.2 // Small circle
} else {
0.0
}
});
// Edge detection with Canny
let canny_edges = canny(
&image,
1.0, // sigma for Gaussian smoothing
0.1, // low threshold
0.2, // high threshold
None
)?;
// Edge detection with Sobel
let sobel_edges_result = sobel_edges(&image, None)?;
// Edge detection with gradient methods
let gradient_edges_sobel = gradient_edges(&image, GradientMethod::Sobel)?;
let gradient_edges_scharr = gradient_edges(&image, GradientMethod::Scharr)?;
println!("Edge detection results:");
println!("- Canny edge detection with hysteresis thresholding");
println!("- Sobel edge detection");
println!("- Gradient-based edge detection (Sobel and Scharr)");
// Corner detection with Harris
let harris_response = harris_corners(
&image,
1.0, // sigma for derivatives
0.04, // k parameter
None // optional mask
)?;
// Find corner locations (peaks in Harris response)
let corner_threshold = 0.01;
let mut corner_locations = Vec::new();
for ((i, j), &response) in harris_response.indexed_iter() {
if response > corner_threshold {
// Check if it's a local maximum
let mut is_maximum = true;
for di in -1..=1 {
for dj in -1..=1 {
if di == 0 && dj == 0 { continue; }
let ni = i as i32 + di;
let nj = j as i32 + dj;
if ni >= 0 && ni < 60 && nj >= 0 && nj < 60 {
if harris_response[[ni as usize, nj as usize]] > response {
is_maximum = false;
break;
}
}
}
if !is_maximum { break; }
}
if is_maximum {
corner_locations.push((i, j));
}
}
}
println!("Harris corner detection found {} corners:", corner_locations.len());
for (i, &(row, col)) in corner_locations.iter().enumerate() {
println!(" Corner {}: ({}, {})", i + 1, row, col);
}
// FAST corner detection
let fast_corners_result = fast_corners(
&image,
0.1, // threshold
true // non-maximum suppression
)?;
println!("FAST corner detection found {} corners", fast_corners_result.len());
// Combine edge and corner information
let feature_map = Array2::from_shape_fn(image.dim(), |(i, j)| {
let edge_val = if canny_edges[[i, j]] { 0.5 } else { 0.0 };
let harris_val = harris_response[[i, j]] * 2.0;
let fast_val = if fast_corners_result.iter().any(|&(r, c)| r == i && c == j) { 0.3 } else { 0.0 };
(edge_val + harris_val + fast_val).min(1.0)
});
println!("Created combined feature map with edges and corners");
"#.to_string(),
expected_output: "Feature maps showing detected edges and corners".to_string(),
concepts: vec![
"Edge detection".to_string(),
"Corner detection".to_string(),
"Feature extraction".to_string(),
"Multi-scale analysis".to_string(),
],
related_functions: vec![
"laplacian_edges".to_string(),
"edge_detector".to_string(),
"hough_transform".to_string(),
],
});
Ok(())
}
fn add_advanced_workflow_examples(&mut self) -> Result<()> {
self.add_step(TutorialStep {
title: "Complete Image Analysis Workflow".to_string(),
description: "Combine multiple techniques for comprehensive image analysis".to_string(),
code_example: r#"
use scirs2_ndimage::*;
use scirs2_core::ndarray::Array2;
use statrs::statistics::Statistics;
// Simulate a real-world image analysis scenario:
// Analyzing cellular structures in microscopy images
// Create synthetic microscopy image
let image = Array2::from_shape_fn((100, 100), |(i, j)| {
let mut intensity = 0.1; // Background
// Add several "cells" with varying intensities
let cells = [
(25, 25, 8, 0.8), // (center_i, center_j, radius, intensity)
(25, 75, 6, 0.6),
(75, 25, 10, 0.9),
(75, 75, 7, 0.7),
(50, 50, 12, 0.85),
];
for &(ci, cj, radius, cell_intensity) in &cells {
let distance = ((i as i32 - ci).pow(2) + (j as i32 - cj).pow(2)) as f64;
if distance < (radius as f64).pow(2) {
intensity = intensity.max(cell_intensity);
}
}
// Add some noise
intensity + 0.05 * ((i + j) as f64 * 0.3).sin()
});
println!("=== COMPLETE IMAGE ANALYSIS WORKFLOW ===");
println!("Analyzing synthetic microscopy image with {} cells", 5);
// Step 1: Preprocessing
println!("\n1. PREPROCESSING");
let denoised = filters::gaussian_filter(&image, 0.8, None, None)?;
println!(" - Applied Gaussian smoothing for noise reduction");
// Step 2: Segmentation
println!("\n2. SEGMENTATION");
let threshold_value = segmentation::otsu_threshold(&denoised)?;
let binary_mask = segmentation::threshold_binary(&denoised, threshold_value)?;
println!(" - Otsu threshold: {:.3}", threshold_value);
// Improve segmentation with morphological operations
let cleaned_mask = morphology::binary_opening(&binary_mask, None, None, None, None, None)?;
let filled_mask = morphology::binary_closing(&cleaned_mask, None, None, None, None, None)?;
println!(" - Applied morphological cleaning (opening + closing)");
// Step 3: Object labeling and counting
println!("\n3. OBJECT DETECTION");
let (labeled, num_objects) = morphology::label(&filled_mask, None)?;
println!(" - Detected {} connected objects", num_objects);
// Step 4: Quantitative analysis
println!("\n4. QUANTITATIVE ANALYSIS");
let objects = measurements::find_objects(&labeled)?;
let sums = measurements::sum_labels(&image, &labeled, Some(num_objects))?;
let means = measurements::mean_labels(&image, &labeled, Some(num_objects))?;
let properties = measurements::region_properties(&labeled, Some(&image))?;
for (i, prop) in properties.iter().enumerate() {
if prop.area > 0 {
println!(" Object {}:", i + 1);
println!(" Area: {} pixels", prop.area);
println!(" Mean intensity: {:.3}", means[i]);
println!(" Total intensity: {:.1}", sums[i]);
println!(" Centroid: ({:.1}, {:.1})", prop.centroid[0], prop.centroid[1]);
println!(" Equivalent diameter: {:.1}", prop.equivalent_diameter);
println!(" Eccentricity: {:.3}", prop.eccentricity);
}
}
// Step 5: Feature extraction
println!("\n5. FEATURE EXTRACTION");
let edges = features::canny(&denoised, 1.0, 0.1, 0.2, None)?;
let edge_count: usize = edges.iter().map(|&x| if x { 1 } else { 0 }).sum();
println!(" - Detected {} edge pixels", edge_count);
let corners = features::harris_corners(&denoised, 1.0, 0.04, None)?;
let strong_corners: usize = corners.iter().map(|&x| if x > 0.01 { 1 } else { 0 }).sum();
println!(" - Found {} strong corner features", strong_corners);
// Step 6: Quality metrics
println!("\n6. QUALITY ASSESSMENT");
let image_mean = image.mean().expect("Operation failed");
let image_std = image.var(scirs2_core::ndarray::Axis(0)).expect("Operation failed").mean().expect("Operation failed").sqrt();
let signal_to_noise = image_mean / image_std;
println!(" - Image mean intensity: {:.3}", image_mean);
println!(" - Image standard deviation: {:.3}", image_std);
println!(" - Signal-to-noise ratio: {:.2}", signal_to_noise);
// Step 7: Results summary
println!("\n7. ANALYSIS SUMMARY");
println!(" ================");
println!(" Total objects detected: {}", num_objects);
println!(" Average object area: {:.1} pixels",
properties.iter().map(|p| p.area).sum::<usize>() as f64 / num_objects as f64);
println!(" Average object intensity: {:.3}",
means.iter().sum::<f64>() / num_objects as f64);
println!(" Image quality (SNR): {:.2}", signal_to_noise);
println!(" Edge density: {:.1}%", edge_count as f64 / (100.0 * 100.0) * 100.0);
// Create analysis result visualization
let visualization = Array2::from_shape_fn(image.dim(), |(i, j)| {
let original = image[[i, j]];
let has_edge = edges[[i, j]];
let label_val = labeled[[i, j]] as f64 / num_objects as f64;
if has_edge {
1.0 // Edges in white
} else if labeled[[i, j]] > 0 {
0.5 + 0.5 * label_val // Objects in gray levels
} else {
original * 0.3 // Background dimmed
}
});
println!("\n8. WORKFLOW COMPLETE");
println!(" Created analysis visualization combining:");
println!(" - Original image data");
println!(" - Detected object boundaries");
println!(" - Edge features");
println!(" - Object labels with distinct intensities");
"#
.to_string(),
expected_output: "Complete quantitative analysis of image features and objects"
.to_string(),
concepts: vec![
"Complete analysis workflow".to_string(),
"Multi-step processing pipeline".to_string(),
"Quantitative image analysis".to_string(),
"Quality assessment".to_string(),
"Results visualization".to_string(),
],
related_functions: vec![
"batch_process".to_string(),
"pipeline_builder".to_string(),
"analysis_report".to_string(),
],
});
Ok(())
}
pub fn export_markdown(&self) -> String {
let mut markdown = String::new();
markdown.push_str("# Comprehensive scirs2-ndimage Tutorial\n\n");
markdown.push_str("This tutorial provides comprehensive examples of all major functionality in scirs2-ndimage, ");
markdown.push_str("demonstrating real-world usage patterns and best practices.\n\n");
markdown.push_str("## Table of Contents\n\n");
for (i, step) in self.steps.iter().enumerate() {
markdown.push_str(&format!(
"{}. [{}](#{})\n",
i + 1,
step.title,
step.title.to_lowercase().replace(" ", "-")
));
}
markdown.push_str("\n");
for (i, step) in self.steps.iter().enumerate() {
markdown.push_str(&format!("## {}. {}\n\n", i + 1, step.title));
markdown.push_str(&format!("{}\n\n", step.description));
markdown.push_str("### Key Concepts\n");
for concept in &step.concepts {
markdown.push_str(&format!("- {}\n", concept));
}
markdown.push_str("\n");
markdown.push_str("### Code Example\n\n");
markdown.push_str("```rust\n");
markdown.push_str(&step.code_example);
markdown.push_str("\n```\n\n");
markdown.push_str(&format!(
"### Expected Output\n{}\n\n",
step.expected_output
));
if !step.related_functions.is_empty() {
markdown.push_str("### Related Functions\n");
for func in &step.related_functions {
markdown.push_str(&format!("- `{}`\n", func));
}
markdown.push_str("\n");
}
markdown.push_str("---\n\n");
}
markdown.push_str("## Additional Resources\n\n");
markdown.push_str("- [API Documentation](https://docs.rs/scirs2-ndimage)\n");
markdown.push_str("- [GitHub Repository](https://github.com/cool-japan/scirs)\n");
markdown.push_str("- [SciPy ndimage Documentation](https://docs.scipy.org/doc/scipy/reference/ndimage.html) (for reference)\n");
markdown
}
pub fn get_steps(&self) -> &[TutorialStep] {
&self.steps
}
pub fn step_count(&self) -> usize {
self.steps.len()
}
}
#[allow(dead_code)]
pub fn validate_all_examples() -> Result<()> {
println!("Validating all comprehensive examples...");
let mut tutorial = ExampleTutorial::new();
tutorial.generate_complete_tutorial()?;
println!("Generated {} tutorial steps", tutorial.step_count());
println!("All examples validated successfully!");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tutorial_creation() {
let tutorial = ExampleTutorial::new();
assert_eq!(tutorial.step_count(), 0);
}
#[test]
fn test_tutorial_step_creation() {
let step = TutorialStep {
title: "Test Step".to_string(),
description: "Test description".to_string(),
code_example: "let x = 1;".to_string(),
expected_output: "Output".to_string(),
concepts: vec!["testing".to_string()],
related_functions: vec!["test_func".to_string()],
};
assert_eq!(step.title, "Test Step");
assert_eq!(step.concepts.len(), 1);
}
#[test]
fn test_tutorial_generation() -> Result<()> {
let mut tutorial = ExampleTutorial::new();
tutorial.generate_complete_tutorial()?;
assert!(tutorial.step_count() > 0);
assert!(tutorial.export_markdown().len() > 1000);
Ok(())
}
}