scirs2_ndimage/advanced_fusion_algorithms/
temporal_causality.rs

1//! # Temporal Causality Analysis Module
2//!
3//! This module provides advanced temporal causality analysis capabilities for image sequences,
4//! enabling the detection and analysis of causal relationships over time. It implements:
5//!
6//! ## Key Features
7//! - **Temporal Pattern Analysis**: Detection of temporal patterns in image sequences
8//! - **Granger Causality**: Classical statistical causality detection between temporal signals
9//! - **Transfer Entropy**: Information-theoretic causality measurement
10//! - **Causal Graph Construction**: Building and maintaining causal relationship graphs
11//! - **Multi-scale Temporal Analysis**: Analysis across different temporal windows
12//!
13//! ## Core Algorithms
14//! - Cross-correlation based causal strength measurement
15//! - Variance-based entropy approximation
16//! - Confidence scoring for causal relationships
17//! - Dynamic temporal memory management
18//!
19//! ## Applications
20//! - Video sequence analysis for motion causality
21//! - Time-series medical imaging analysis
22//! - Dynamic system monitoring in scientific imaging
23//! - Predictive analysis in image-based monitoring systems
24
25use scirs2_core::ndarray::{Array2, Array3, ArrayView2};
26use scirs2_core::numeric::{Float, FromPrimitive};
27use std::cmp::Ordering;
28use std::collections::{BTreeMap, VecDeque};
29
30use super::config::*;
31use crate::error::NdimageResult;
32
33/// Temporal-Causal Analysis
34///
35/// Analyzes temporal patterns and causal relationships in image sequences
36/// to understand the flow of information and causality over time.
37///
38/// This function implements a comprehensive temporal causality analysis system that:
39/// 1. Converts current image to temporal representation
40/// 2. Maintains a sliding window of temporal memory
41/// 3. Detects causal relationships using multiple algorithms
42/// 4. Builds and updates a causal graph
43/// 5. Calculates causal influence for each pixel
44///
45/// # Arguments
46/// * `image` - Current image frame to analyze
47/// * `advancedstate` - Mutable state containing temporal memory and causal graph
48/// * `config` - Configuration parameters for causality analysis
49///
50/// # Returns
51/// A 2D array representing the causal influence strength for each pixel
52#[allow(dead_code)]
53pub fn analyze_temporal_causality<T>(
54    image: &ArrayView2<T>,
55    advancedstate: &mut AdvancedState,
56    config: &AdvancedConfig,
57) -> NdimageResult<Array2<f64>>
58where
59    T: Float + FromPrimitive + Copy,
60{
61    let (height, width) = image.dim();
62    let mut causal_output = Array2::zeros((height, width));
63
64    // Convert current image to temporal representation
65    let current_temporal = image_to_temporal_representation(image)?;
66
67    // Add to temporal memory
68    advancedstate
69        .temporal_memory
70        .push_back(current_temporal.clone());
71
72    // Maintain temporal window size
73    while advancedstate.temporal_memory.len() > config.temporal_window {
74        advancedstate.temporal_memory.pop_front();
75    }
76
77    // Analyze causal relationships if we have sufficient temporal data
78    if advancedstate.temporal_memory.len() >= config.causal_depth {
79        for y in 0..height {
80            for x in 0..width {
81                let pixel_id = y * width + x;
82
83                // Extract temporal sequence for this pixel
84                let temporal_sequence =
85                    extract_pixel_temporal_sequence(&advancedstate.temporal_memory, (y, x))?;
86
87                // Detect causal relationships
88                let causal_relationships =
89                    detect_causal_relationships(&temporal_sequence, pixel_id, config)?;
90
91                // Update causal graph
92                advancedstate
93                    .causal_graph
94                    .insert(pixel_id, causal_relationships.clone());
95
96                // Calculate causal influence on current pixel
97                let causal_influence = calculate_causal_influence(
98                    &causal_relationships,
99                    &advancedstate.causal_graph,
100                    config,
101                )?;
102
103                causal_output[(y, x)] = causal_influence;
104            }
105        }
106    }
107
108    Ok(causal_output)
109}
110
111/// Converts a 2D image to temporal representation
112///
113/// Creates a 3D temporal representation of the input image by analyzing
114/// spatial gradients, edge patterns, and local statistics that can be
115/// tracked over time.
116///
117/// # Arguments
118/// * `image` - Input 2D image
119///
120/// # Returns
121/// A 3D array containing temporal features for the image
122#[allow(dead_code)]
123fn image_to_temporal_representation<T>(image: &ArrayView2<T>) -> NdimageResult<Array3<f64>>
124where
125    T: Float + FromPrimitive + Copy,
126{
127    let (height, width) = image.dim();
128    let mut temporal_features = Array3::zeros((height, width, 4));
129
130    for y in 0..height {
131        for x in 0..width {
132            let pixel_val = image[(y, x)].to_f64().unwrap_or(0.0);
133
134            // Basic intensity feature
135            temporal_features[(y, x, 0)] = pixel_val;
136
137            // Horizontal gradient
138            let h_grad = if x > 0 && x < width - 1 {
139                let left = image[(y, x - 1)].to_f64().unwrap_or(0.0);
140                let right = image[(y, x + 1)].to_f64().unwrap_or(0.0);
141                (right - left) / 2.0
142            } else {
143                0.0
144            };
145            temporal_features[(y, x, 1)] = h_grad;
146
147            // Vertical gradient
148            let v_grad = if y > 0 && y < height - 1 {
149                let up = image[(y - 1, x)].to_f64().unwrap_or(0.0);
150                let down = image[(y + 1, x)].to_f64().unwrap_or(0.0);
151                (down - up) / 2.0
152            } else {
153                0.0
154            };
155            temporal_features[(y, x, 2)] = v_grad;
156
157            // Local variance feature
158            let mut local_vals = Vec::new();
159            for dy in -1..=1i32 {
160                for dx in -1..=1i32 {
161                    let ny = y as i32 + dy;
162                    let nx = x as i32 + dx;
163                    if ny >= 0 && ny < height as i32 && nx >= 0 && nx < width as i32 {
164                        let val = image[(ny as usize, nx as usize)].to_f64().unwrap_or(0.0);
165                        local_vals.push(val);
166                    }
167                }
168            }
169
170            let local_variance = if local_vals.len() > 1 {
171                let mean = local_vals.iter().sum::<f64>() / local_vals.len() as f64;
172                local_vals.iter().map(|&v| (v - mean).powi(2)).sum::<f64>()
173                    / local_vals.len() as f64
174            } else {
175                0.0
176            };
177            temporal_features[(y, x, 3)] = local_variance;
178        }
179    }
180
181    Ok(temporal_features)
182}
183
184/// Extracts temporal sequence for a specific pixel from temporal memory
185///
186/// Retrieves the temporal evolution of features for a specific pixel position
187/// across all frames in the temporal memory.
188///
189/// # Arguments
190/// * `temporal_memory` - Queue of temporal feature arrays
191/// * `position` - (y, x) coordinates of the pixel to extract
192///
193/// # Returns
194/// Vector containing the temporal sequence of feature values for the pixel
195#[allow(dead_code)]
196fn extract_pixel_temporal_sequence(
197    temporal_memory: &VecDeque<Array3<f64>>,
198    position: (usize, usize),
199) -> NdimageResult<Vec<f64>> {
200    let (y, x) = position;
201    let mut sequence = Vec::new();
202
203    for frame in temporal_memory {
204        if y < frame.shape()[0] && x < frame.shape()[1] {
205            // Extract all temporal features for this pixel and combine them
206            let mut pixel_features = Vec::new();
207            for feature_idx in 0..frame.shape()[2] {
208                pixel_features.push(frame[(y, x, feature_idx)]);
209            }
210
211            // Combine features into a single temporal value (weighted average)
212            let combined_value = pixel_features
213                .iter()
214                .enumerate()
215                .map(|(i, &val)| val * (i as f64 + 1.0) / pixel_features.len() as f64)
216                .sum::<f64>();
217
218            sequence.push(combined_value);
219        }
220    }
221
222    // If sequence is too short, pad with zeros
223    while sequence.len() < 8 {
224        sequence.push(0.0);
225    }
226
227    Ok(sequence)
228}
229
230/// Detects causal relationships in temporal sequences
231///
232/// Implements multiple causality detection algorithms:
233/// 1. Granger causality using cross-correlation analysis
234/// 2. Transfer entropy approximation using variance-based entropy
235///
236/// # Arguments
237/// * `temporal_sequence` - Temporal sequence of feature values
238/// * `pixel_id` - Unique identifier for the source pixel
239/// * `config` - Configuration parameters for causality analysis
240///
241/// # Returns
242/// Vector of detected causal relationships with confidence scores
243#[allow(dead_code)]
244fn detect_causal_relationships(
245    temporal_sequence: &[f64],
246    pixel_id: usize,
247    config: &AdvancedConfig,
248) -> NdimageResult<Vec<CausalRelation>> {
249    let mut causal_relations = Vec::new();
250
251    if temporal_sequence.len() < config.causal_depth {
252        return Ok(causal_relations);
253    }
254
255    // Granger causality-inspired analysis
256    for delay in 1..config.causal_depth.min(temporal_sequence.len() / 2) {
257        let mut cause_values = Vec::new();
258        let mut effect_values = Vec::new();
259
260        for i in delay..temporal_sequence.len() {
261            cause_values.push(temporal_sequence[i - delay]);
262            effect_values.push(temporal_sequence[i]);
263        }
264
265        if cause_values.len() < 3 {
266            continue;
267        }
268
269        // Calculate correlation coefficient
270        let cause_mean = cause_values.iter().sum::<f64>() / cause_values.len() as f64;
271        let effect_mean = effect_values.iter().sum::<f64>() / effect_values.len() as f64;
272
273        let numerator: f64 = cause_values
274            .iter()
275            .zip(effect_values.iter())
276            .map(|(&c, &e)| (c - cause_mean) * (e - effect_mean))
277            .sum();
278
279        let cause_var: f64 = cause_values.iter().map(|&c| (c - cause_mean).powi(2)).sum();
280
281        let effect_var: f64 = effect_values
282            .iter()
283            .map(|&e| (e - effect_mean).powi(2))
284            .sum();
285
286        let denominator = (cause_var * effect_var).sqrt();
287
288        if denominator > 1e-10 {
289            let correlation = numerator / denominator;
290            let causal_strength = correlation.abs();
291
292            // Threshold for significant causal relationship
293            if causal_strength > 0.3 {
294                // Calculate confidence based on sample size and strength
295                let confidence =
296                    (causal_strength * (cause_values.len() as f64).ln() / 10.0).min(1.0);
297
298                // Determine target pixel (simplified for demonstration)
299                let target_id = if correlation > 0.0 {
300                    pixel_id + delay // Positive influence on neighboring pixel
301                } else {
302                    if pixel_id >= delay {
303                        pixel_id - delay
304                    } else {
305                        pixel_id
306                    } // Negative influence
307                };
308
309                causal_relations.push(CausalRelation {
310                    source: pixel_id,
311                    target: target_id,
312                    strength: causal_strength,
313                    delay,
314                    confidence,
315                });
316            }
317        }
318    }
319
320    // Transfer entropy-based causality detection
321    for window_size in 2..=(config.causal_depth / 2).min(temporal_sequence.len() / 4) {
322        if temporal_sequence.len() < window_size * 2 {
323            continue;
324        }
325
326        // Simplified transfer entropy calculation
327        let mut entropy_source = 0.0;
328        let mut entropy_target = 0.0;
329        let mut mutual_entropy = 0.0;
330
331        for i in window_size..temporal_sequence.len() - window_size {
332            let source_window = &temporal_sequence[i - window_size..i];
333            let target_window = &temporal_sequence[i..i + window_size];
334
335            // Simplified entropy calculation using variance
336            let source_var = calculate_window_variance(source_window);
337            let target_var = calculate_window_variance(target_window);
338
339            entropy_source += source_var;
340            entropy_target += target_var;
341
342            // Cross-correlation as proxy for mutual information
343            let cross_corr = source_window
344                .iter()
345                .zip(target_window.iter())
346                .map(|(&s, &t)| s * t)
347                .sum::<f64>()
348                / window_size as f64;
349
350            mutual_entropy += cross_corr.abs();
351        }
352
353        let n_windows = (temporal_sequence.len() - window_size * 2) as f64;
354        if n_windows > 0.0 {
355            entropy_source /= n_windows;
356            entropy_target /= n_windows;
357            mutual_entropy /= n_windows;
358
359            // Transfer entropy approximation
360            let transfer_entropy = mutual_entropy / (entropy_source + entropy_target + 1e-10);
361
362            if transfer_entropy > 0.2 {
363                let confidence = (transfer_entropy * n_windows.ln() / 5.0).min(1.0);
364
365                causal_relations.push(CausalRelation {
366                    source: pixel_id,
367                    target: pixel_id + window_size, // Simplified target determination
368                    strength: transfer_entropy,
369                    delay: window_size,
370                    confidence,
371                });
372            }
373        }
374    }
375
376    // Sort by strength and keep only the strongest relationships
377    causal_relations.sort_by(|a, b| {
378        b.strength
379            .partial_cmp(&a.strength)
380            .unwrap_or(Ordering::Equal)
381    });
382    causal_relations.truncate(config.causal_depth / 2);
383
384    Ok(causal_relations)
385}
386
387/// Calculates the overall causal influence on a pixel
388///
389/// Computes the aggregate causal influence on a pixel based on all detected
390/// causal relationships in the graph. Takes into account both direct and
391/// indirect causal pathways.
392///
393/// # Arguments
394/// * `relationships` - Direct causal relationships for the pixel
395/// * `causal_graph` - Complete causal relationship graph
396/// * `config` - Configuration parameters for influence calculation
397///
398/// # Returns
399/// Normalized causal influence score (0.0 to 1.0)
400#[allow(dead_code)]
401fn calculate_causal_influence(
402    relationships: &[CausalRelation],
403    causal_graph: &BTreeMap<usize, Vec<CausalRelation>>,
404    _config: &AdvancedConfig,
405) -> NdimageResult<f64> {
406    if relationships.is_empty() {
407        return Ok(0.0);
408    }
409
410    // Calculate direct influence
411    let direct_influence: f64 = relationships
412        .iter()
413        .map(|rel| rel.strength * rel.confidence)
414        .sum();
415
416    // Calculate indirect influence through the causal graph
417    let mut indirect_influence = 0.0;
418    for relation in relationships {
419        if let Some(target_relations) = causal_graph.get(&relation.target) {
420            for target_rel in target_relations {
421                // Weight indirect influence by distance and confidence
422                let distance_weight = 1.0 / (relation.delay as f64 + 1.0);
423                indirect_influence +=
424                    target_rel.strength * target_rel.confidence * distance_weight * 0.5;
425            }
426        }
427    }
428
429    // Combine direct and indirect influences
430    let total_influence = direct_influence + indirect_influence;
431
432    // Normalize to [0, 1] range using a sigmoid-like function
433    let normalized_influence = total_influence / (1.0 + total_influence);
434
435    Ok(normalized_influence)
436}
437
438/// Calculates variance of values in a temporal window
439///
440/// Computes the statistical variance of a window of temporal values,
441/// used as a proxy for entropy in causality analysis.
442///
443/// # Arguments
444/// * `window` - Slice of temporal values
445///
446/// # Returns
447/// Variance of the window values
448#[allow(dead_code)]
449fn calculate_window_variance(window: &[f64]) -> f64 {
450    if window.is_empty() {
451        return 0.0;
452    }
453
454    let mean = window.iter().sum::<f64>() / window.len() as f64;
455    let variance = window.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / window.len() as f64;
456
457    variance
458}
459
460/// Analyzes temporal causality patterns across multiple scales
461///
462/// Performs multi-scale temporal analysis to detect causal patterns
463/// at different temporal resolutions.
464///
465/// # Arguments
466/// * `temporal_memory` - Complete temporal memory buffer
467/// * `config` - Configuration parameters
468///
469/// # Returns
470/// Multi-scale causality analysis results
471#[allow(dead_code)]
472pub fn analyze_multiscale_temporal_causality(
473    temporal_memory: &VecDeque<Array3<f64>>,
474    config: &AdvancedConfig,
475) -> NdimageResult<Vec<Array2<f64>>> {
476    let mut scales_results = Vec::new();
477
478    if temporal_memory.is_empty() {
479        return Ok(scales_results);
480    }
481
482    let (height, width, _) = temporal_memory[0].dim();
483
484    // Analyze at different temporal scales
485    for scale in 1..=3 {
486        let mut scale_result = Array2::zeros((height, width));
487        let step_size = scale * 2;
488
489        // Sample temporal memory at different scales
490        let mut scaled_memory = VecDeque::new();
491        for (i, frame) in temporal_memory.iter().enumerate() {
492            if i % step_size == 0 {
493                scaled_memory.push_back(frame.clone());
494            }
495        }
496
497        if scaled_memory.len() >= config.causal_depth / scale {
498            for y in 0..height {
499                for x in 0..width {
500                    // Extract temporal sequence at this scale
501                    if let Ok(temporal_sequence) =
502                        extract_pixel_temporal_sequence(&scaled_memory, (y, x))
503                    {
504                        let pixel_id = y * width + x;
505
506                        // Detect causal relationships at this scale
507                        if let Ok(relationships) =
508                            detect_causal_relationships(&temporal_sequence, pixel_id, config)
509                        {
510                            let causal_strength: f64 = relationships
511                                .iter()
512                                .map(|rel| rel.strength * rel.confidence)
513                                .sum();
514
515                            scale_result[(y, x)] = causal_strength / (scale as f64);
516                        }
517                    }
518                }
519            }
520        }
521
522        scales_results.push(scale_result);
523    }
524
525    Ok(scales_results)
526}
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531    use scirs2_core::ndarray::Array2;
532
533    #[test]
534    fn test_calculate_window_variance() {
535        let window = vec![1.0, 2.0, 3.0, 4.0, 5.0];
536        let variance = calculate_window_variance(&window);
537        assert!(variance > 0.0);
538
539        // Test empty window
540        let empty_window = vec![];
541        let zero_variance = calculate_window_variance(&empty_window);
542        assert_eq!(zero_variance, 0.0);
543    }
544
545    #[test]
546    fn test_image_to_temporal_representation() {
547        let image = Array2::<f64>::zeros((10, 10));
548        let result = image_to_temporal_representation(&image.view());
549        assert!(result.is_ok());
550
551        let temporal_features = result.unwrap();
552        assert_eq!(temporal_features.dim(), (10, 10, 4));
553    }
554
555    #[test]
556    fn test_extract_pixel_temporal_sequence() {
557        let mut temporal_memory = VecDeque::new();
558        let frame1 = Array3::<f64>::zeros((5, 5, 4));
559        let frame2 = Array3::<f64>::ones((5, 5, 4));
560
561        temporal_memory.push_back(frame1);
562        temporal_memory.push_back(frame2);
563
564        let result = extract_pixel_temporal_sequence(&temporal_memory, (2, 2));
565        assert!(result.is_ok());
566
567        let sequence = result.unwrap();
568        assert!(!sequence.is_empty());
569    }
570}