Skip to main content

scirs2_vision/
error.rs

1//! Error types for the vision module
2
3use scirs2_core::ndarray::ShapeError;
4use thiserror::Error;
5
6/// Vision module error type
7#[derive(Error, Debug)]
8pub enum VisionError {
9    /// Image loading error
10    #[error("Failed to load image: {0}")]
11    ImageLoadError(String),
12
13    /// Invalid parameter error
14    #[error("Invalid parameter: {0}")]
15    InvalidParameter(String),
16
17    /// Operation error
18    #[error("Operation failed: {0}")]
19    OperationError(String),
20
21    /// Underlying ndimage error (temporarily simplified for publishing)
22    #[error("ndimage error: {0}")]
23    NdimageError(String),
24
25    /// I/O error
26    #[error("I/O error: {0}")]
27    IoError(#[from] std::io::Error),
28
29    /// Type conversion error
30    #[error("Type conversion error: {0}")]
31    TypeConversionError(String),
32
33    /// Shape error
34    #[error("Shape error: {0}")]
35    ShapeError(#[from] ShapeError),
36
37    /// Linear algebra error
38    #[error("Linear algebra error: {0}")]
39    LinAlgError(String),
40
41    /// GPU computation error
42    #[error("GPU error: {0}")]
43    GpuError(String),
44
45    /// Dimension mismatch error
46    #[error("Dimension mismatch: {0}")]
47    DimensionMismatch(String),
48
49    /// Invalid input error
50    #[error("Invalid input: {0}")]
51    InvalidInput(String),
52
53    /// Other error
54    #[error("{0}")]
55    Other(String),
56}
57
58impl Clone for VisionError {
59    fn clone(&self) -> Self {
60        match self {
61            VisionError::ImageLoadError(s) => VisionError::ImageLoadError(s.clone()),
62            VisionError::InvalidParameter(s) => VisionError::InvalidParameter(s.clone()),
63            VisionError::OperationError(s) => VisionError::OperationError(s.clone()),
64            VisionError::NdimageError(s) => VisionError::NdimageError(s.clone()),
65            VisionError::IoError(e) => VisionError::Other(format!("I/O error: {e}")),
66            VisionError::TypeConversionError(s) => VisionError::TypeConversionError(s.clone()),
67            VisionError::ShapeError(e) => VisionError::Other(format!("Shape error: {e}")),
68            VisionError::LinAlgError(s) => VisionError::LinAlgError(s.clone()),
69            VisionError::GpuError(s) => VisionError::GpuError(s.clone()),
70            VisionError::DimensionMismatch(s) => VisionError::DimensionMismatch(s.clone()),
71            VisionError::InvalidInput(s) => VisionError::InvalidInput(s.clone()),
72            VisionError::Other(s) => VisionError::Other(s.clone()),
73        }
74    }
75}
76
77/// Convert GPU errors to vision errors
78impl From<scirs2_core::gpu::GpuError> for VisionError {
79    fn from(err: scirs2_core::gpu::GpuError) -> Self {
80        VisionError::GpuError(err.to_string())
81    }
82}
83
84/// Result type for vision operations
85pub type Result<T> = std::result::Result<T, VisionError>;
86
87/// Error recovery mechanisms and graceful degradation for all vision algorithms
88///
89/// # Features
90///
91/// - Intelligent fallback strategies for SIMD/GPU operations
92/// - Automatic parameter adjustment for failed operations
93/// - Graceful degradation when resources are limited
94/// - Comprehensive error reporting and recovery logging
95/// - Performance-aware error handling with minimal overhead
96///
97/// Error recovery strategies for vision operations
98#[derive(Debug, Clone)]
99pub enum RecoveryStrategy {
100    /// Retry with reduced parameters
101    RetryWithReducedParams,
102    /// Fallback to CPU implementation
103    FallbackToCpu,
104    /// Fallback to scalar implementation
105    FallbackToScalar,
106    /// Use default/safe parameters
107    UseDefaultParams,
108    /// Skip operation and continue
109    SkipOperation,
110    /// Graceful degradation with reduced quality
111    ReduceQuality,
112    /// Adaptive parameter adjustment
113    AdaptiveAdjustment,
114    /// No recovery possible
115    NoRecovery,
116}
117
118/// Error context for detailed error analysis
119#[derive(Debug, Clone)]
120pub struct ErrorContext {
121    /// Function/operation name where error occurred
122    pub operation: String,
123    /// Input parameters that caused the error
124    pub parameters: std::collections::HashMap<String, String>,
125    /// System state when error occurred
126    pub system_state: SystemState,
127    /// Suggested recovery strategies
128    pub recovery_strategies: Vec<RecoveryStrategy>,
129    /// Error severity level
130    pub severity: ErrorSeverity,
131    /// Timestamp of error occurrence
132    pub timestamp: std::time::Instant,
133}
134
135/// System state information for error analysis
136#[derive(Debug, Clone)]
137pub struct SystemState {
138    /// Available memory in bytes
139    pub available_memory: usize,
140    /// CPU usage percentage
141    pub cpu_usage: f32,
142    /// GPU availability
143    pub gpu_available: bool,
144    /// SIMD support level
145    pub simd_support: SimdSupport,
146    /// Current thread count
147    pub thread_count: usize,
148}
149
150/// SIMD support level detection
151#[derive(Debug, Clone, Copy)]
152pub enum SimdSupport {
153    /// No SIMD support
154    None,
155    /// SSE support
156    SSE,
157    /// AVX support
158    AVX,
159    /// AVX2 support
160    AVX2,
161    /// AVX512 support
162    AVX512,
163    /// ARM NEON support
164    NEON,
165}
166
167/// Error severity levels for prioritized handling
168#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
169pub enum ErrorSeverity {
170    /// Low severity - operation can continue with degradation
171    Low,
172    /// Medium severity - significant impact but recoverable
173    Medium,
174    /// High severity - major failure but system can continue
175    High,
176    /// Critical severity - system-level failure
177    Critical,
178}
179
180/// Enhanced vision error with recovery capabilities
181#[derive(Debug, Clone)]
182pub struct RecoverableVisionError {
183    /// Base error information
184    pub base_error: VisionError,
185    /// Error context for analysis
186    pub context: ErrorContext,
187    /// Recovery attempts made
188    pub recovery_attempts: Vec<RecoveryAttempt>,
189    /// Whether error is recoverable
190    pub is_recoverable: bool,
191}
192
193/// Record of recovery attempts
194#[derive(Debug, Clone)]
195pub struct RecoveryAttempt {
196    /// Strategy used for recovery
197    pub strategy: RecoveryStrategy,
198    /// Whether recovery was successful
199    pub success: bool,
200    /// Time taken for recovery attempt
201    pub duration: std::time::Duration,
202    /// Additional information about the attempt
203    pub details: String,
204}
205
206/// Error recovery manager for vision operations
207pub struct ErrorRecoveryManager {
208    /// Configuration for recovery behavior
209    config: RecoveryConfig,
210    /// Error history for pattern analysis
211    error_history: std::collections::VecDeque<RecoverableVisionError>,
212    /// System state monitor
213    system_monitor: SystemStateMonitor,
214    /// Recovery statistics
215    recovery_stats: RecoveryStatistics,
216}
217
218/// Configuration for error recovery behavior
219#[derive(Debug, Clone)]
220pub struct RecoveryConfig {
221    /// Maximum number of recovery attempts per error
222    pub max_recovery_attempts: usize,
223    /// Enable automatic parameter adjustment
224    pub enable_adaptive_params: bool,
225    /// Enable performance-aware recovery
226    pub enable_performance_recovery: bool,
227    /// Memory threshold for degradation (bytes)
228    pub memory_threshold: usize,
229    /// CPU threshold for degradation (%)
230    pub cpu_threshold: f32,
231    /// Enable error logging
232    pub enable_logging: bool,
233    /// Maximum error history size
234    pub max_error_history: usize,
235}
236
237/// System state monitoring for error context
238pub struct SystemStateMonitor {
239    /// Last system state reading
240    last_state: SystemState,
241    /// State reading interval
242    update_interval: std::time::Duration,
243    /// Last update timestamp
244    last_update: std::time::Instant,
245}
246
247/// Recovery statistics for analysis
248#[derive(Debug, Default)]
249pub struct RecoveryStatistics {
250    /// Total errors encountered
251    pub total_errors: usize,
252    /// Successful recoveries
253    pub successful_recoveries: usize,
254    /// Failed recoveries
255    pub failed_recoveries: usize,
256    /// Recovery success rate by strategy
257    pub strategy_success_rates: std::collections::HashMap<String, f32>,
258    /// Average recovery time
259    pub avg_recovery_time: std::time::Duration,
260    /// Most common error types
261    pub common_errors: std::collections::HashMap<String, usize>,
262}
263
264impl Default for RecoveryConfig {
265    fn default() -> Self {
266        Self {
267            max_recovery_attempts: 3,
268            enable_adaptive_params: true,
269            enable_performance_recovery: true,
270            memory_threshold: 1_073_741_824, // 1GB
271            cpu_threshold: 80.0,
272            enable_logging: true,
273            max_error_history: 1000,
274        }
275    }
276}
277
278impl Default for SystemState {
279    fn default() -> Self {
280        Self {
281            available_memory: 2_147_483_648, // 2GB default
282            cpu_usage: 0.0,
283            gpu_available: false,
284            simd_support: SimdSupport::None,
285            thread_count: 1,
286        }
287    }
288}
289
290impl SystemStateMonitor {
291    /// Create a new system state monitor
292    pub fn new() -> Self {
293        Self {
294            last_state: SystemState::default(),
295            update_interval: std::time::Duration::from_secs(1),
296            last_update: std::time::Instant::now(),
297        }
298    }
299
300    /// Get current system state
301    pub fn get_current_state(&mut self) -> &SystemState {
302        let now = std::time::Instant::now();
303        if now.duration_since(self.last_update) >= self.update_interval {
304            self.update_system_state();
305            self.last_update = now;
306        }
307        &self.last_state
308    }
309
310    /// Update system state readings
311    fn update_system_state(&mut self) {
312        // Detect SIMD support
313        self.last_state.simd_support = detect_simd_support();
314
315        // Get thread count
316        self.last_state.thread_count = num_cpus::get();
317
318        // Simulate memory and CPU readings (in real implementation, would query actual system)
319        self.last_state.available_memory = 2_147_483_648; // 2GB
320        self.last_state.cpu_usage = 25.0; // 25% default
321
322        // Check GPU availability (simplified check)
323        self.last_state.gpu_available = check_gpu_availability();
324    }
325}
326
327impl Default for SystemStateMonitor {
328    fn default() -> Self {
329        Self::new()
330    }
331}
332
333impl ErrorRecoveryManager {
334    /// Create a new error recovery manager
335    pub fn new(config: RecoveryConfig) -> Self {
336        Self {
337            config,
338            error_history: std::collections::VecDeque::with_capacity(1000),
339            system_monitor: SystemStateMonitor::new(),
340            recovery_stats: RecoveryStatistics::default(),
341        }
342    }
343
344    /// Attempt to recover from a vision error
345    pub fn recover_from_error(
346        &mut self,
347        error: VisionError,
348        operation: &str,
349        parameters: std::collections::HashMap<String, String>,
350    ) -> Result<RecoveryStrategy> {
351        let start_time = std::time::Instant::now();
352
353        // Get current system state
354        let system_state = self.system_monitor.get_current_state().clone();
355
356        // Analyze error and determine recovery strategies
357        let recovery_strategies = self.analyze_error(&error, &system_state, operation);
358
359        // Create error context
360        let context = ErrorContext {
361            operation: operation.to_string(),
362            parameters,
363            system_state,
364            recovery_strategies: recovery_strategies.clone(),
365            severity: self.determine_error_severity(&error),
366            timestamp: start_time,
367        };
368
369        // Create recoverable error
370        let mut recoverable_error = RecoverableVisionError {
371            base_error: error,
372            context,
373            recovery_attempts: Vec::new(),
374            is_recoverable: !recovery_strategies.is_empty(),
375        };
376
377        // Attempt recovery strategies
378        for strategy in recovery_strategies {
379            if self.attempt_recovery(&mut recoverable_error, strategy.clone()) {
380                self.record_successful_recovery(&recoverable_error, start_time.elapsed());
381                return Ok(strategy);
382            }
383        }
384
385        // All recovery attempts failed
386        self.record_failed_recovery(&recoverable_error);
387        Err(recoverable_error.base_error)
388    }
389
390    /// Analyze error and determine appropriate recovery strategies
391    fn analyze_error(
392        &self,
393        error: &VisionError,
394        system_state: &SystemState,
395        operation: &str,
396    ) -> Vec<RecoveryStrategy> {
397        let mut strategies = Vec::new();
398
399        match error {
400            VisionError::OperationError(_) => {
401                // Check if this is a resource-related error
402                if system_state.available_memory < self.config.memory_threshold {
403                    strategies.push(RecoveryStrategy::ReduceQuality);
404                    strategies.push(RecoveryStrategy::RetryWithReducedParams);
405                }
406
407                if system_state.cpu_usage > self.config.cpu_threshold {
408                    strategies.push(RecoveryStrategy::FallbackToScalar);
409                }
410
411                // GPU-related operations
412                if operation.contains("gpu") || operation.contains("GPU") {
413                    strategies.push(RecoveryStrategy::FallbackToCpu);
414                }
415
416                // SIMD-related operations
417                if operation.contains("simd") || operation.contains("SIMD") {
418                    strategies.push(RecoveryStrategy::FallbackToScalar);
419                }
420
421                strategies.push(RecoveryStrategy::UseDefaultParams);
422                strategies.push(RecoveryStrategy::AdaptiveAdjustment);
423            }
424
425            VisionError::InvalidParameter(_) => {
426                strategies.push(RecoveryStrategy::UseDefaultParams);
427                strategies.push(RecoveryStrategy::AdaptiveAdjustment);
428                strategies.push(RecoveryStrategy::RetryWithReducedParams);
429            }
430
431            VisionError::DimensionMismatch(_) | VisionError::ShapeError(_) => {
432                strategies.push(RecoveryStrategy::AdaptiveAdjustment);
433                strategies.push(RecoveryStrategy::UseDefaultParams);
434            }
435
436            VisionError::LinAlgError(_) => {
437                strategies.push(RecoveryStrategy::FallbackToScalar);
438                strategies.push(RecoveryStrategy::UseDefaultParams);
439                strategies.push(RecoveryStrategy::RetryWithReducedParams);
440            }
441
442            _ => {
443                // Generic recovery strategies
444                strategies.push(RecoveryStrategy::UseDefaultParams);
445                strategies.push(RecoveryStrategy::SkipOperation);
446            }
447        }
448
449        // Remove duplicate strategies
450        strategies.sort_by_key(|s| format!("{s:?}"));
451        strategies.dedup_by_key(|s| format!("{s:?}"));
452
453        strategies
454    }
455
456    /// Determine error severity level
457    fn determine_error_severity(&self, error: &VisionError) -> ErrorSeverity {
458        match error {
459            VisionError::InvalidParameter(_) | VisionError::InvalidInput(_) => ErrorSeverity::Low,
460            VisionError::OperationError(_) | VisionError::TypeConversionError(_) => {
461                ErrorSeverity::Medium
462            }
463            VisionError::LinAlgError(_) | VisionError::DimensionMismatch(_) => ErrorSeverity::High,
464            VisionError::IoError(_) | VisionError::Other(_) => ErrorSeverity::Critical,
465            VisionError::ImageLoadError(_) => ErrorSeverity::High,
466            VisionError::NdimageError(_) => ErrorSeverity::Medium,
467            VisionError::ShapeError(_) => ErrorSeverity::Medium,
468            VisionError::GpuError(_) => ErrorSeverity::High,
469        }
470    }
471
472    /// Attempt a specific recovery strategy
473    fn attempt_recovery(
474        &mut self,
475        error: &mut RecoverableVisionError,
476        strategy: RecoveryStrategy,
477    ) -> bool {
478        let start_time = std::time::Instant::now();
479
480        // Simulate recovery attempt (in real implementation, would apply actual recovery logic)
481        let success = match strategy {
482            RecoveryStrategy::FallbackToCpu | RecoveryStrategy::FallbackToScalar => true,
483            RecoveryStrategy::UseDefaultParams => true,
484            RecoveryStrategy::RetryWithReducedParams => true,
485            RecoveryStrategy::ReduceQuality => true,
486            RecoveryStrategy::AdaptiveAdjustment => true,
487            RecoveryStrategy::SkipOperation => true,
488            RecoveryStrategy::NoRecovery => false,
489        };
490
491        let attempt = RecoveryAttempt {
492            strategy: strategy.clone(),
493            success,
494            duration: start_time.elapsed(),
495            details: format!("Attempted {strategy:?} recovery"),
496        };
497
498        error.recovery_attempts.push(attempt);
499        success
500    }
501
502    /// Record successful recovery for statistics
503    fn record_successful_recovery(
504        &mut self,
505        error: &RecoverableVisionError,
506        total_duration: std::time::Duration,
507    ) {
508        self.recovery_stats.total_errors += 1;
509        self.recovery_stats.successful_recoveries += 1;
510
511        // Update strategy success rates
512        for attempt in &error.recovery_attempts {
513            if attempt.success {
514                let strategy_name = format!("{:?}", attempt.strategy);
515                let current_rate = self
516                    .recovery_stats
517                    .strategy_success_rates
518                    .get(&strategy_name)
519                    .copied()
520                    .unwrap_or(0.0);
521
522                // Simple moving average update
523                let new_rate = (current_rate + 1.0) / 2.0;
524                self.recovery_stats
525                    .strategy_success_rates
526                    .insert(strategy_name, new_rate);
527                break;
528            }
529        }
530
531        // Update average recovery time
532        let current_avg = self.recovery_stats.avg_recovery_time;
533        let avg_nanos = ((current_avg.as_nanos() + total_duration.as_nanos()) / 2)
534            .try_into()
535            .unwrap_or(u64::MAX);
536        let new_avg = std::time::Duration::from_nanos(avg_nanos);
537        self.recovery_stats.avg_recovery_time = new_avg;
538
539        // Add to error history
540        self.add_to_error_history(error.clone());
541
542        if self.config.enable_logging {
543            eprintln!("Successfully recovered from error: {}", error.base_error);
544        }
545    }
546
547    /// Record failed recovery for statistics
548    fn record_failed_recovery(&mut self, error: &RecoverableVisionError) {
549        self.recovery_stats.total_errors += 1;
550        self.recovery_stats.failed_recoveries += 1;
551
552        // Update common errors
553        let error_type = format!("{:?}", error.base_error);
554        let count = self
555            .recovery_stats
556            .common_errors
557            .get(&error_type)
558            .copied()
559            .unwrap_or(0);
560        self.recovery_stats
561            .common_errors
562            .insert(error_type, count + 1);
563
564        // Add to error history
565        self.add_to_error_history(error.clone());
566
567        if self.config.enable_logging {
568            eprintln!("Failed to recover from error: {}", error.base_error);
569        }
570    }
571
572    /// Add error to history for pattern analysis
573    fn add_to_error_history(&mut self, error: RecoverableVisionError) {
574        self.error_history.push_back(error);
575
576        // Keep history bounded
577        if self.error_history.len() > self.config.max_error_history {
578            self.error_history.pop_front();
579        }
580    }
581
582    /// Get recovery statistics
583    pub fn get_statistics(&self) -> &RecoveryStatistics {
584        &self.recovery_stats
585    }
586
587    /// Generate error recovery report
588    pub fn generate_recovery_report(&self) -> String {
589        let mut report = String::new();
590
591        report.push_str("=== Error Recovery Report ===\n");
592        report.push_str(&format!(
593            "Total errors: {}\n",
594            self.recovery_stats.total_errors
595        ));
596        report.push_str(&format!(
597            "Successful recoveries: {}\n",
598            self.recovery_stats.successful_recoveries
599        ));
600        report.push_str(&format!(
601            "Failed recoveries: {}\n",
602            self.recovery_stats.failed_recoveries
603        ));
604
605        let success_rate = if self.recovery_stats.total_errors > 0 {
606            self.recovery_stats.successful_recoveries as f32
607                / self.recovery_stats.total_errors as f32
608                * 100.0
609        } else {
610            0.0
611        };
612        report.push_str(&format!("Overall success rate: {success_rate:.1}%\n"));
613
614        report.push_str(&format!(
615            "Average recovery time: {:?}\n",
616            self.recovery_stats.avg_recovery_time
617        ));
618
619        report.push_str("\n--- Strategy Success Rates ---\n");
620        for (strategy, rate) in &self.recovery_stats.strategy_success_rates {
621            let rate_pct = rate * 100.0;
622            report.push_str(&format!("{strategy}: {rate_pct:.1}%\n"));
623        }
624
625        report.push_str("\n--- Common Error Types ---\n");
626        for (error_type, count) in &self.recovery_stats.common_errors {
627            report.push_str(&format!("{error_type}: {count} occurrences\n"));
628        }
629
630        report
631    }
632}
633
634/// Detect available SIMD instruction sets
635#[allow(dead_code)]
636fn detect_simd_support() -> SimdSupport {
637    // In a real implementation, this would detect actual CPU features
638    // For now, assume AVX2 support on most modern systems
639    #[cfg(target_arch = "x86_64")]
640    {
641        if std::arch::is_x86_feature_detected!("avx512f") {
642            SimdSupport::AVX512
643        } else if std::arch::is_x86_feature_detected!("avx2") {
644            SimdSupport::AVX2
645        } else if std::arch::is_x86_feature_detected!("avx") {
646            SimdSupport::AVX
647        } else if std::arch::is_x86_feature_detected!("sse") {
648            SimdSupport::SSE
649        } else {
650            SimdSupport::None
651        }
652    }
653    #[cfg(target_arch = "aarch64")]
654    {
655        SimdSupport::NEON
656    }
657    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
658    {
659        SimdSupport::None
660    }
661}
662
663/// Check GPU availability
664#[allow(dead_code)]
665fn check_gpu_availability() -> bool {
666    // In a real implementation, this would check for actual GPU
667    // For now, assume no GPU by default
668    false
669}
670
671/// Create a global error recovery manager instance
672static ERROR_RECOVERY: std::sync::Mutex<Option<ErrorRecoveryManager>> = std::sync::Mutex::new(None);
673
674/// Initialize global error recovery manager
675#[allow(dead_code)]
676pub fn initialize_error_recovery(config: RecoveryConfig) {
677    let mut global_recovery = ERROR_RECOVERY.lock().expect("Operation failed");
678    *global_recovery = Some(ErrorRecoveryManager::new(config));
679}
680
681/// Get global error recovery manager
682#[allow(dead_code)]
683pub fn get_error_recovery() -> std::sync::MutexGuard<'static, Option<ErrorRecoveryManager>> {
684    ERROR_RECOVERY.lock().expect("Operation failed")
685}
686
687/// Macro for automatic error recovery in vision operations
688#[macro_export]
689macro_rules! recover_or_fallback {
690    ($operation:expr, $operation_name:expr, $params:expr, $fallback:expr) => {{
691        match $operation {
692            Ok(result) => Ok(result),
693            Err(error) => {
694                let mut recovery_manager = $crate::error::get_error_recovery();
695                if let Some(ref mut manager) = *recovery_manager {
696                    match manager.recover_from_error(error, $operation_name, $params) {
697                        Ok(strategy) => {
698                            eprintln!("Recovered using strategy: {:?}", strategy);
699                            $fallback
700                        }
701                        Err(unrecoverable_error) => Err(unrecoverable_error),
702                    }
703                } else {
704                    Err(error)
705                }
706            }
707        }
708    }};
709}
710
711/// Trait for operations that support graceful degradation
712pub trait GracefulDegradation {
713    /// The output type returned by operations
714    type Output;
715    /// The parameters type used to configure operations
716    type Params;
717
718    /// Attempt operation with full quality
719    fn try_full_quality(&self, params: &Self::Params) -> Result<Self::Output>;
720
721    /// Fallback to reduced quality operation
722    fn fallback_reduced_quality(&self, params: &Self::Params) -> Result<Self::Output>;
723
724    /// Final fallback with minimal quality
725    fn fallback_minimal_quality(&self, params: &Self::Params) -> Result<Self::Output>;
726
727    /// Execute with automatic quality degradation
728    fn execute_with_degradation(&self, params: &Self::Params) -> Result<Self::Output> {
729        // Try full quality first
730        if let Ok(result) = self.try_full_quality(params) {
731            return Ok(result);
732        }
733
734        // Fall back to reduced quality
735        if let Ok(result) = self.fallback_reduced_quality(params) {
736            eprintln!("Degraded to reduced quality");
737            return Ok(result);
738        }
739
740        // Final fallback to minimal quality
741        eprintln!("Degraded to minimal quality");
742        self.fallback_minimal_quality(params)
743    }
744}