LPX55 commited on
Commit
e1eac06
·
1 Parent(s): 62180cb

feat: integrate ONNX model inference and logging enhancements, add contextual intelligence and forensic anomaly detection agents

Browse files
agents/ensemble_team.py CHANGED
@@ -2,134 +2,116 @@ import logging
2
  import time
3
  import torch
4
  import psutil # Ensure psutil is imported here as well
 
 
5
 
6
  logger = logging.getLogger(__name__)
7
 
8
  class EnsembleMonitorAgent:
9
  def __init__(self):
10
- self.performance_metrics = {
11
- "model_accuracy": {},
12
- "response_times": {},
13
- "confidence_distribution": {},
14
- "consensus_rate": 0.0
15
- }
16
  self.alerts = []
17
 
18
- def monitor_prediction(self, model_id, prediction, confidence, response_time):
19
- """Monitor individual model performance"""
20
- if model_id not in self.performance_metrics["model_accuracy"]:
21
- self.performance_metrics["model_accuracy"][model_id] = []
22
- self.performance_metrics["response_times"][model_id] = []
23
- self.performance_metrics["confidence_distribution"][model_id] = []
 
 
 
 
 
 
 
 
24
 
25
- self.performance_metrics["response_times"][model_id].append(response_time)
26
- self.performance_metrics["confidence_distribution"][model_id].append(confidence)
 
 
 
27
 
28
- # Check for performance issues
29
- self._check_performance_issues(model_id)
 
 
 
 
 
30
 
31
- def _check_performance_issues(self, model_id):
32
- """Check for any performance anomalies"""
33
- response_times = self.performance_metrics["response_times"][model_id]
34
- if len(response_times) > 10:
35
- avg_time = sum(response_times[-10:]) / 10
36
- if avg_time > 2.0: # More than 2 seconds
37
- self.alerts.append(f"High latency detected for {model_id}: {avg_time:.2f}s")
 
 
 
 
 
 
38
 
39
  class WeightOptimizationAgent:
40
  def __init__(self, weight_manager):
 
41
  self.weight_manager = weight_manager
42
- self.prediction_history = [] # Stores (ensemble_prediction_label, assumed_actual_label)
43
- self.optimization_threshold = 0.05 # 5% change in accuracy triggers optimization
44
- self.min_history_for_optimization = 20 # Minimum samples before optimizing
45
 
46
- def analyze_performance(self, ensemble_prediction_label, actual_label=None):
47
- """Analyze ensemble performance and record for optimization"""
48
- # If actual_label is not provided, assume ensemble is correct if not UNCERTAIN
49
- assumed_actual_label = actual_label
50
- if assumed_actual_label is None and ensemble_prediction_label != "UNCERTAIN":
51
- assumed_actual_label = ensemble_prediction_label
52
-
53
- self.prediction_history.append((ensemble_prediction_label, assumed_actual_label))
54
 
55
- if len(self.prediction_history) >= self.min_history_for_optimization and self._should_optimize():
56
- self._optimize_weights()
57
-
58
- def _calculate_accuracy(self, history_subset):
59
- """Calculates accuracy based on history where actual_label is known."""
60
- correct_predictions = 0
61
- total_known = 0
62
- for ensemble_pred, actual_label in history_subset:
63
- if actual_label is not None:
64
- total_known += 1
65
- if ensemble_pred == actual_label:
66
- correct_predictions += 1
67
- return correct_predictions / total_known if total_known > 0 else 0.0
68
-
69
- def _should_optimize(self):
70
- """Determine if weights should be optimized based on recent performance change."""
71
- if len(self.prediction_history) < self.min_history_for_optimization * 2: # Need enough history for comparison
72
- return False
73
-
74
- # Compare accuracy of recent batch with previous batch
75
- recent_batch = self.prediction_history[-self.min_history_for_optimization:]
76
- previous_batch = self.prediction_history[-self.min_history_for_optimization*2:-self.min_history_for_optimization]
77
-
78
- recent_accuracy = self._calculate_accuracy(recent_batch)
79
- previous_accuracy = self._calculate_accuracy(previous_batch)
80
-
81
- # Trigger optimization if there's a significant drop in accuracy
82
- if previous_accuracy > 0 and (previous_accuracy - recent_accuracy) / previous_accuracy > self.optimization_threshold:
83
- logger.warning(f"Performance degradation detected (from {previous_accuracy:.2f} to {recent_accuracy:.2f}). Triggering weight optimization.")
84
- return True
85
- return False
86
-
87
- def _optimize_weights(self):
88
- """Optimize model weights based on performance."""
89
- logger.info("Optimizing model weights based on recent performance.")
90
- # Placeholder for sophisticated optimization logic.
91
- # This is where you would adjust self.weight_manager.base_weights
92
- # based on which models contributed more to correct predictions or errors.
93
- # For now, it's just a log message.
94
 
 
 
95
 
96
  class SystemHealthAgent:
97
  def __init__(self):
 
98
  self.health_metrics = {
99
- "memory_usage": [],
100
- "gpu_utilization": [],
101
- "model_load_times": {},
102
- "error_rates": {}
103
  }
104
 
105
  def monitor_system_health(self):
106
- """Monitor overall system health"""
107
- self._check_memory_usage()
108
- self._check_gpu_utilization()
109
- # You might add _check_model_health() here later
110
-
111
- def _check_memory_usage(self):
112
- """Monitor memory usage"""
 
 
 
113
  try:
114
- import psutil
115
- memory = psutil.virtual_memory()
116
- self.health_metrics["memory_usage"].append(memory.percent)
117
-
118
- if memory.percent > 90:
119
- logger.warning(f"High memory usage detected: {memory.percent}%")
120
- except ImportError:
121
- logger.warning("psutil not installed. Cannot monitor memory usage.")
122
-
123
- def _check_gpu_utilization(self):
124
- """Monitor GPU utilization if available"""
125
- if torch.cuda.is_available():
126
- try:
127
- gpu_util = torch.cuda.memory_allocated() / torch.cuda.max_memory_allocated()
128
- self.health_metrics["gpu_utilization"].append(gpu_util)
129
-
130
- if gpu_util > 0.9:
131
- logger.warning(f"High GPU utilization detected: {gpu_util*100:.2f}%")
132
- except Exception as e:
133
- logger.warning(f"Error monitoring GPU utilization: {e}")
134
- else:
135
- logger.info("CUDA not available. Skipping GPU utilization monitoring.")
 
2
  import time
3
  import torch
4
  import psutil # Ensure psutil is imported here as well
5
+ import GPUtil
6
+ from datetime import datetime, timedelta
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
  class EnsembleMonitorAgent:
11
  def __init__(self):
12
+ logger.info("Initializing EnsembleMonitorAgent.")
13
+ self.performance_metrics = {}
 
 
 
 
14
  self.alerts = []
15
 
16
+ def monitor_prediction(self, model_id, prediction_label, confidence_score, inference_time):
17
+ logger.info(f"Monitoring prediction for model '{model_id}'. Label: {prediction_label}, Confidence: {confidence_score:.2f}, Time: {inference_time:.4f}s")
18
+ if model_id not in self.performance_metrics:
19
+ self.performance_metrics[model_id] = {
20
+ "total_predictions": 0,
21
+ "correct_predictions": 0, # This would require ground truth, which we don't have here.
22
+ "total_confidence": 0.0,
23
+ "total_inference_time": 0.0
24
+ }
25
+
26
+ metrics = self.performance_metrics[model_id]
27
+ metrics["total_predictions"] += 1
28
+ metrics["total_confidence"] += confidence_score
29
+ metrics["total_inference_time"] += inference_time
30
 
31
+ # Example alert: model taking too long
32
+ if inference_time > 5.0: # Threshold for slow inference
33
+ alert_msg = f"ALERT: Model '{model_id}' inference time exceeded 5.0s: {inference_time:.4f}s"
34
+ self.alerts.append(alert_msg)
35
+ logger.warning(alert_msg)
36
 
37
+ # Example alert: low confidence
38
+ if confidence_score < 0.5: # Threshold for low confidence
39
+ alert_msg = f"ALERT: Model '{model_id}' returned low confidence: {confidence_score:.2f}"
40
+ self.alerts.append(alert_msg)
41
+ logger.warning(alert_msg)
42
+
43
+ logger.debug(f"Updated metrics for '{model_id}': {metrics}")
44
 
45
+ def get_performance_summary(self):
46
+ logger.info("Generating performance summary for all models.")
47
+ summary = {}
48
+ for model_id, metrics in self.performance_metrics.items():
49
+ avg_confidence = metrics["total_confidence"] / metrics["total_predictions"] if metrics["total_predictions"] > 0 else 0
50
+ avg_inference_time = metrics["total_inference_time"] / metrics["total_predictions"] if metrics["total_predictions"] > 0 else 0
51
+ summary[model_id] = {
52
+ "avg_confidence": avg_confidence,
53
+ "avg_inference_time": avg_inference_time,
54
+ "total_predictions": metrics["total_predictions"]
55
+ }
56
+ logger.info(f"Performance summary: {summary}")
57
+ return summary
58
 
59
  class WeightOptimizationAgent:
60
  def __init__(self, weight_manager):
61
+ logger.info("Initializing WeightOptimizationAgent.")
62
  self.weight_manager = weight_manager
63
+ self.prediction_history = []
64
+ self.performance_window = timedelta(hours=24) # Evaluate performance over last 24 hours
 
65
 
66
+ def analyze_performance(self, final_prediction, ground_truth=None):
67
+ logger.info(f"Analyzing performance. Final prediction: {final_prediction}, Ground truth: {ground_truth}")
68
+ timestamp = datetime.now()
69
+ self.prediction_history.append({
70
+ "timestamp": timestamp,
71
+ "final_prediction": final_prediction,
72
+ "ground_truth": ground_truth # Ground truth is often not available in real-time
73
+ })
74
 
75
+ # Keep history windowed
76
+ self.prediction_history = [p for p in self.prediction_history if timestamp - p["timestamp"] < self.performance_window]
77
+ logger.debug(f"Prediction history length: {len(self.prediction_history)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ # In a real scenario, this would involve a more complex optimization logic
80
+ # For now, it just logs the history length.
81
 
82
  class SystemHealthAgent:
83
  def __init__(self):
84
+ logger.info("Initializing SystemHealthAgent.")
85
  self.health_metrics = {
86
+ "cpu_percent": 0,
87
+ "memory_usage": {"total": 0, "available": 0, "percent": 0},
88
+ "gpu_utilization": []
 
89
  }
90
 
91
  def monitor_system_health(self):
92
+ logger.info("Monitoring system health...")
93
+ self.health_metrics["cpu_percent"] = psutil.cpu_percent(interval=1)
94
+ mem = psutil.virtual_memory()
95
+ self.health_metrics["memory_usage"] = {
96
+ "total": mem.total,
97
+ "available": mem.available,
98
+ "percent": mem.percent
99
+ }
100
+
101
+ gpu_info = []
102
  try:
103
+ gpus = GPUtil.getGPUs()
104
+ for gpu in gpus:
105
+ gpu_info.append({
106
+ "id": gpu.id,
107
+ "name": gpu.name,
108
+ "load": gpu.load,
109
+ "memoryUtil": gpu.memoryUtil,
110
+ "memoryTotal": gpu.memoryTotal,
111
+ "memoryUsed": gpu.memoryUsed
112
+ })
113
+ except Exception as e:
114
+ logger.warning(f"Could not retrieve GPU information: {e}")
115
+ gpu_info.append({"error": str(e)})
116
+ self.health_metrics["gpu_utilization"] = gpu_info
117
+ logger.info(f"System health metrics: CPU: {self.health_metrics['cpu_percent']}%, Memory: {self.health_metrics['memory_usage']['percent']}%, GPU: {gpu_info}")
 
 
 
 
 
 
 
agents/ensemble_weights.py CHANGED
@@ -6,6 +6,7 @@ logger = logging.getLogger(__name__)
6
 
7
  class ContextualWeightOverrideAgent:
8
  def __init__(self):
 
9
  self.context_overrides = {
10
  # Example: when image is outdoor, model_X is penalized, model_Y is boosted
11
  "outdoor": {
@@ -24,7 +25,7 @@ class ContextualWeightOverrideAgent:
24
  }
25
 
26
  def get_overrides(self, context_tags: list[str]) -> dict:
27
- """Returns combined weight overrides for given context tags."""
28
  combined_overrides = {}
29
  for tag in context_tags:
30
  if tag in self.context_overrides:
@@ -32,15 +33,18 @@ class ContextualWeightOverrideAgent:
32
  # If a model appears in multiple contexts, we can decide how to combine (e.g., multiply, average, take max)
33
  # For now, let's just take the last one if there are conflicts, or multiply for simple cumulative effect.
34
  combined_overrides[model_id] = combined_overrides.get(model_id, 1.0) * multiplier
 
35
  return combined_overrides
36
 
37
 
38
  class ModelWeightManager:
39
  def __init__(self, strongest_model_id: str = None):
 
40
  # Dynamically initialize base_weights from MODEL_REGISTRY
41
  num_models = len(MODEL_REGISTRY)
42
  if num_models > 0:
43
  if strongest_model_id and strongest_model_id in MODEL_REGISTRY:
 
44
  # Assign a high weight to the strongest model (e.g., 50%)
45
  strongest_weight_share = 0.5
46
  self.base_weights = {strongest_model_id: strongest_weight_share}
@@ -52,10 +56,13 @@ class ModelWeightManager:
52
  else: # Only one model, which is the strongest
53
  self.base_weights[strongest_model_id] = 1.0
54
  else:
 
 
55
  initial_weight = 1.0 / num_models
56
  self.base_weights = {model_id: initial_weight for model_id in MODEL_REGISTRY.keys()}
57
  else:
58
  self.base_weights = {} # Handle case with no registered models
 
59
 
60
  self.situation_weights = {
61
  "high_confidence": 1.2, # Boost weights for high confidence predictions
@@ -67,52 +74,80 @@ class ModelWeightManager:
67
 
68
  def adjust_weights(self, predictions, confidence_scores, context_tags: list[str] = None):
69
  """Dynamically adjust weights based on prediction patterns and optional context."""
 
70
  adjusted_weights = self.base_weights.copy()
 
71
 
72
  # 1. Apply contextual overrides first
73
  if context_tags:
 
74
  overrides = self.context_override_agent.get_overrides(context_tags)
75
  for model_id, multiplier in overrides.items():
76
  adjusted_weights[model_id] = adjusted_weights.get(model_id, 0.0) * multiplier
 
77
 
78
  # 2. Apply situation-based adjustments (consensus, conflict, confidence)
79
  # Check for consensus
80
- if self._has_consensus(predictions):
 
 
81
  for model in adjusted_weights:
82
  adjusted_weights[model] *= self.situation_weights["consensus"]
 
83
 
84
  # Check for conflicts
85
- if self._has_conflicts(predictions):
 
 
86
  for model in adjusted_weights:
87
  adjusted_weights[model] *= self.situation_weights["conflict"]
 
88
 
89
  # Adjust based on confidence
 
90
  for model, confidence in confidence_scores.items():
91
  if confidence > 0.8:
92
  adjusted_weights[model] *= self.situation_weights["high_confidence"]
 
93
  elif confidence < 0.5:
94
  adjusted_weights[model] *= self.situation_weights["low_confidence"]
 
 
95
 
96
- return self._normalize_weights(adjusted_weights)
 
 
97
 
98
  def _has_consensus(self, predictions):
99
  """Check if models agree on prediction"""
100
- # Ensure all predictions are not None before checking for consensus
101
  non_none_predictions = [p.get("Label") for p in predictions.values() if p is not None and isinstance(p, dict) and p.get("Label") is not None and p.get("Label") != "Error"]
102
- return len(non_none_predictions) > 0 and len(set(non_none_predictions)) == 1
 
 
 
103
 
104
  def _has_conflicts(self, predictions):
105
  """Check if models have conflicting predictions"""
106
- # Ensure all predictions are not None before checking for conflicts
107
  non_none_predictions = [p.get("Label") for p in predictions.values() if p is not None and isinstance(p, dict) and p.get("Label") is not None and p.get("Label") != "Error"]
108
- return len(non_none_predictions) > 1 and len(set(non_none_predictions)) > 1
 
 
 
109
 
110
  def _normalize_weights(self, weights):
111
  """Normalize weights to sum to 1"""
 
112
  total = sum(weights.values())
113
  if total == 0:
114
- # Handle case where all weights became zero due to aggressive multipliers
115
- # This could assign equal weights or revert to base weights
116
- logger.warning("All weights became zero after adjustments. Reverting to base weights.")
117
- return {k: 1.0/len(self.base_weights) for k in self.base_weights}
118
- return {k: v/total for k, v in weights.items()}
 
 
 
 
 
 
6
 
7
  class ContextualWeightOverrideAgent:
8
  def __init__(self):
9
+ logger.info("Initializing ContextualWeightOverrideAgent.")
10
  self.context_overrides = {
11
  # Example: when image is outdoor, model_X is penalized, model_Y is boosted
12
  "outdoor": {
 
25
  }
26
 
27
  def get_overrides(self, context_tags: list[str]) -> dict:
28
+ logger.info(f"Getting weight overrides for context tags: {context_tags}")
29
  combined_overrides = {}
30
  for tag in context_tags:
31
  if tag in self.context_overrides:
 
33
  # If a model appears in multiple contexts, we can decide how to combine (e.g., multiply, average, take max)
34
  # For now, let's just take the last one if there are conflicts, or multiply for simple cumulative effect.
35
  combined_overrides[model_id] = combined_overrides.get(model_id, 1.0) * multiplier
36
+ logger.info(f"Combined context overrides: {combined_overrides}")
37
  return combined_overrides
38
 
39
 
40
  class ModelWeightManager:
41
  def __init__(self, strongest_model_id: str = None):
42
+ logger.info(f"Initializing ModelWeightManager with strongest_model_id: {strongest_model_id}")
43
  # Dynamically initialize base_weights from MODEL_REGISTRY
44
  num_models = len(MODEL_REGISTRY)
45
  if num_models > 0:
46
  if strongest_model_id and strongest_model_id in MODEL_REGISTRY:
47
+ logger.info(f"Designating '{strongest_model_id}' as the strongest model.")
48
  # Assign a high weight to the strongest model (e.g., 50%)
49
  strongest_weight_share = 0.5
50
  self.base_weights = {strongest_model_id: strongest_weight_share}
 
56
  else: # Only one model, which is the strongest
57
  self.base_weights[strongest_model_id] = 1.0
58
  else:
59
+ if strongest_model_id and strongest_model_id not in MODEL_REGISTRY:
60
+ logger.warning(f"Strongest model ID '{strongest_model_id}' not found in MODEL_REGISTRY. Distributing weights equally.")
61
  initial_weight = 1.0 / num_models
62
  self.base_weights = {model_id: initial_weight for model_id in MODEL_REGISTRY.keys()}
63
  else:
64
  self.base_weights = {} # Handle case with no registered models
65
+ logger.info(f"Base weights initialized: {self.base_weights}")
66
 
67
  self.situation_weights = {
68
  "high_confidence": 1.2, # Boost weights for high confidence predictions
 
74
 
75
  def adjust_weights(self, predictions, confidence_scores, context_tags: list[str] = None):
76
  """Dynamically adjust weights based on prediction patterns and optional context."""
77
+ logger.info("Adjusting model weights.")
78
  adjusted_weights = self.base_weights.copy()
79
+ logger.info(f"Initial adjusted weights (copy of base): {adjusted_weights}")
80
 
81
  # 1. Apply contextual overrides first
82
  if context_tags:
83
+ logger.info(f"Applying contextual overrides for tags: {context_tags}")
84
  overrides = self.context_override_agent.get_overrides(context_tags)
85
  for model_id, multiplier in overrides.items():
86
  adjusted_weights[model_id] = adjusted_weights.get(model_id, 0.0) * multiplier
87
+ logger.info(f"Adjusted weights after context overrides: {adjusted_weights}")
88
 
89
  # 2. Apply situation-based adjustments (consensus, conflict, confidence)
90
  # Check for consensus
91
+ has_consensus = self._has_consensus(predictions)
92
+ if has_consensus:
93
+ logger.info("Consensus detected. Boosting weights for consensus.")
94
  for model in adjusted_weights:
95
  adjusted_weights[model] *= self.situation_weights["consensus"]
96
+ logger.info(f"Adjusted weights after consensus boost: {adjusted_weights}")
97
 
98
  # Check for conflicts
99
+ has_conflicts = self._has_conflicts(predictions)
100
+ if has_conflicts:
101
+ logger.info("Conflicts detected. Reducing weights for conflict.")
102
  for model in adjusted_weights:
103
  adjusted_weights[model] *= self.situation_weights["conflict"]
104
+ logger.info(f"Adjusted weights after conflict reduction: {adjusted_weights}")
105
 
106
  # Adjust based on confidence
107
+ logger.info("Adjusting weights based on model confidence scores.")
108
  for model, confidence in confidence_scores.items():
109
  if confidence > 0.8:
110
  adjusted_weights[model] *= self.situation_weights["high_confidence"]
111
+ logger.info(f"Model '{model}' has high confidence ({confidence:.2f}). Weight boosted.")
112
  elif confidence < 0.5:
113
  adjusted_weights[model] *= self.situation_weights["low_confidence"]
114
+ logger.info(f"Model '{model}' has low confidence ({confidence:.2f}). Weight reduced.")
115
+ logger.info(f"Adjusted weights before normalization: {adjusted_weights}")
116
 
117
+ normalized_weights = self._normalize_weights(adjusted_weights)
118
+ logger.info(f"Final normalized adjusted weights: {normalized_weights}")
119
+ return normalized_weights
120
 
121
  def _has_consensus(self, predictions):
122
  """Check if models agree on prediction"""
123
+ logger.info("Checking for consensus among model predictions.")
124
  non_none_predictions = [p.get("Label") for p in predictions.values() if p is not None and isinstance(p, dict) and p.get("Label") is not None and p.get("Label") != "Error"]
125
+ logger.debug(f"Non-none predictions for consensus check: {non_none_predictions}")
126
+ result = len(non_none_predictions) > 0 and len(set(non_none_predictions)) == 1
127
+ logger.info(f"Consensus detected: {result}")
128
+ return result
129
 
130
  def _has_conflicts(self, predictions):
131
  """Check if models have conflicting predictions"""
132
+ logger.info("Checking for conflicts among model predictions.")
133
  non_none_predictions = [p.get("Label") for p in predictions.values() if p is not None and isinstance(p, dict) and p.get("Label") is not None and p.get("Label") != "Error"]
134
+ logger.debug(f"Non-none predictions for conflict check: {non_none_predictions}")
135
+ result = len(non_none_predictions) > 1 and len(set(non_none_predictions)) > 1
136
+ logger.info(f"Conflicts detected: {result}")
137
+ return result
138
 
139
  def _normalize_weights(self, weights):
140
  """Normalize weights to sum to 1"""
141
+ logger.info("Normalizing weights.")
142
  total = sum(weights.values())
143
  if total == 0:
144
+ logger.warning("All weights became zero after adjustments. Reverting to equal base weights for registered models.")
145
+ # Revert to equal weights for all *registered* models if total becomes zero
146
+ num_registered_models = len(MODEL_REGISTRY)
147
+ if num_registered_models > 0:
148
+ return {k: 1.0/num_registered_models for k in MODEL_REGISTRY.keys()}
149
+ else:
150
+ return {} # No models registered
151
+ normalized = {k: v/total for k, v in weights.items()}
152
+ logger.info(f"Weights normalized. Total sum: {sum(normalized.values()):.2f}")
153
+ return normalized
agents/smart_agents.py CHANGED
@@ -1,65 +1,126 @@
1
  import logging
2
- from PIL import Image
3
- import smolagents
 
 
 
4
  logger = logging.getLogger(__name__)
5
 
6
  class ContextualIntelligenceAgent:
7
  def __init__(self):
8
- # In a real scenario, this would involve an LLM call or a sophisticated rule engine
9
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- def infer_context_tags(self, image_data: dict, initial_predictions: dict) -> list[str]:
12
- """Simulates an LLM inferring context tags based on image data and predictions."""
13
- context_tags = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- # Boilerplate logic: infer tags based on simple cues
16
- if image_data.get("width", 0) > 1000 and image_data.get("height", 0) > 1000:
17
- context_tags.append("high_resolution")
 
 
 
 
 
 
18
 
19
- # Example based on initial broad prediction (e.g., if any model strongly predicts 'real')
20
- if any(v.get("Real Score", 0) > 0.9 for v in initial_predictions.values()):
21
- context_tags.append("potentially_natural_scene")
22
-
23
- # Mock external detection (e.g., from a simpler scene classification model or EXIF data)
24
- # For demonstration, we'll hardcode some possible tags here.
25
- # In a real system, you'd feed actual image features or metadata to an LLM.
26
- mock_tags = ["foo", "bar"] # These could be returned by an actual LLM based on input
27
- for tag in mock_tags:
28
- if tag not in context_tags:
29
- context_tags.append(tag)
30
 
31
- return context_tags
32
 
33
  class ForensicAnomalyDetectionAgent:
34
  def __init__(self):
35
- # In a real scenario, this would involve an LLM call to analyze textual descriptions
36
- pass
 
 
 
 
37
 
38
  def analyze_forensic_outputs(self, forensic_output_descriptions: list[str]) -> dict:
39
- """Simulates an LLM analyzing descriptions of forensic images for anomalies."""
40
- import random
41
-
42
- # 4 mock anomalies for demo purposes
43
- mock_anomalies = [
44
- {
45
- "summary": "ELA analysis reveals potential image manipulation",
46
- "details": ["ELA: Unusually strong compression artifacts detected", "ELA: Inconsistent noise patterns suggest compositing"]
47
- },
48
- {
49
- "summary": "Bit plane analysis shows irregular patterns",
50
- "details": ["Bit Plane: Unexpected data patterns in LSB", "Bit Plane: Hidden information detected in lower planes"]
51
- },
52
- {
53
- "summary": "Gradient analysis indicates artificial boundaries",
54
- "details": ["Gradient: Sharp discontinuities in color transitions", "Gradient: Unnatural edge patterns detected"]
55
- },
56
- {
57
- "summary": "Wavelet analysis reveals processing artifacts",
58
- "details": ["Wavelet: Unusual frequency distribution", "Wavelet: Compression artifacts inconsistent with natural images"]
59
- }
60
- ]
61
-
62
- # Randomly select one of the mock anomalies
63
- selected_anomaly = random.choice(mock_anomalies)
64
-
65
- return selected_anomaly
 
 
 
 
 
 
 
 
 
1
  import logging
2
+ import torch
3
+ import numpy as np
4
+ from PIL import Image # For image processing context
5
+ # import smolagents # Removed unused import
6
+
7
  logger = logging.getLogger(__name__)
8
 
9
  class ContextualIntelligenceAgent:
10
  def __init__(self):
11
+ logger.info("Initializing ContextualIntelligenceAgent.")
12
+ # This would be a more sophisticated model in a real scenario
13
+ self.context_rules = {
14
+ "high_resolution": {"min_width": 1920, "min_height": 1080, "tag": "high_resolution_image"},
15
+ "low_resolution": {"max_width": 640, "max_height": 480, "tag": "low_resolution_image"},
16
+ "grayscale": {"mode": "L", "tag": "grayscale_image"},
17
+ "potentially_natural_scene": {"keywords": ["Real"], "threshold": 0.7, "tag": "potentially_natural_scene"},
18
+ "potentially_ai_generated": {"keywords": ["AI", "Fake", "Deepfake"], "threshold": 0.7, "tag": "potentially_ai_generated"},
19
+ "outdoor": {"model_tags": ["sunny", "sky", "trees"], "tag": "outdoor"},
20
+ "indoor": {"model_tags": ["room", "furniture"], "tag": "indoor"},
21
+ "sunny": {"rgb_avg_min": [200, 200, 100], "tag": "sunny"},
22
+ "dark": {"rgb_avg_max": [50, 50, 50], "tag": "dark"},
23
+ }
24
+
25
+ def infer_context_tags(self, image_metadata: dict, model_predictions: dict) -> list[str]:
26
+ logger.info("Inferring context tags from image metadata and model predictions.")
27
+ detected_tags = []
28
+
29
+ # Analyze image metadata
30
+ width = image_metadata.get("width", 0)
31
+ height = image_metadata.get("height", 0)
32
+ mode = image_metadata.get("mode", "RGB")
33
+
34
+ if width >= self.context_rules["high_resolution"]["min_width"] and \
35
+ height >= self.context_rules["high_resolution"]["min_height"]:
36
+ detected_tags.append(self.context_rules["high_resolution"]["tag"])
37
+ logger.debug(f"Detected tag: {self.context_rules['high_resolution']['tag']}")
38
 
39
+ if width <= self.context_rules["low_resolution"]["max_width"] and \
40
+ height <= self.context_rules["low_resolution"]["max_height"]:
41
+ detected_tags.append(self.context_rules["low_resolution"]["tag"])
42
+ logger.debug(f"Detected tag: {self.context_rules['low_resolution']['tag']}")
43
+
44
+ if mode == self.context_rules["grayscale"]["mode"]:
45
+ detected_tags.append(self.context_rules["grayscale"]["tag"])
46
+ logger.debug(f"Detected tag: {self.context_rules['grayscale']['tag']}")
47
+
48
+ # Analyze model predictions for general context
49
+ for model_id, prediction in model_predictions.items():
50
+ label = prediction.get("Label")
51
+ ai_score = prediction.get("AI Score", 0.0)
52
+ real_score = prediction.get("Real Score", 0.0)
53
+
54
+ if label and "potentially_natural_scene" not in detected_tags:
55
+ for keyword in self.context_rules["potentially_natural_scene"]["keywords"]:
56
+ if keyword in label and real_score >= self.context_rules["potentially_natural_scene"]["threshold"]:
57
+ detected_tags.append(self.context_rules["potentially_natural_scene"]["tag"])
58
+ logger.debug(f"Detected tag: {self.context_rules['potentially_natural_scene']['tag']}")
59
+ break # Only add once
60
+
61
+ if label and "potentially_ai_generated" not in detected_tags:
62
+ for keyword in self.context_rules["potentially_ai_generated"]["keywords"]:
63
+ if keyword in label and ai_score >= self.context_rules["potentially_ai_generated"]["threshold"]:
64
+ detected_tags.append(self.context_rules["potentially_ai_generated"]["tag"])
65
+ logger.debug(f"Detected tag: {self.context_rules['potentially_ai_generated']['tag']}")
66
+ break # Only add once
67
 
68
+ # Simulate simple scene detection based on general consensus if available
69
+ # This is a very basic simulation; a real system would use a separate scene classification model
70
+ if "potentially_natural_scene" in detected_tags and "potentially_ai_generated" not in detected_tags:
71
+ # Simulate outdoor/sunny detection based on presence of a real image tag
72
+ # In a real scenario, this would involve analyzing image features
73
+ if real_score > 0.8: # Placeholder for actual image feature analysis
74
+ detected_tags.append(self.context_rules["outdoor"]["tag"])
75
+ detected_tags.append(self.context_rules["sunny"]["tag"])
76
+ logger.debug(f"Simulated tags: {self.context_rules['outdoor']['tag']},{self.context_rules['sunny']['tag']}")
77
 
78
+ logger.info(f"Inferred context tags: {detected_tags}")
79
+ return detected_tags
 
 
 
 
 
 
 
 
 
80
 
 
81
 
82
  class ForensicAnomalyDetectionAgent:
83
  def __init__(self):
84
+ logger.info("Initializing ForensicAnomalyDetectionAgent.")
85
+ self.anomaly_thresholds = {
86
+ "ELA": {"min_anomalies": 3, "max_error_std": 20}, # Example thresholds
87
+ "gradient": {"min_sharp_edges": 500},
88
+ "minmax": {"min_local_deviation": 0.1}
89
+ }
90
 
91
  def analyze_forensic_outputs(self, forensic_output_descriptions: list[str]) -> dict:
92
+ logger.info("Analyzing forensic outputs for anomalies.")
93
+ anomalies_detected = []
94
+ summary_message = "No significant anomalies detected."
95
+
96
+ # Example: Check for ELA anomalies (simplified)
97
+ ela_anomalies = [desc for desc in forensic_output_descriptions if "ELA analysis" in desc and "enhanced contrast" in desc]
98
+ if len(ela_anomalies) > self.anomaly_thresholds["ELA"]["min_anomalies"]:
99
+ anomalies_detected.append("Multiple ELA passes indicate potential inconsistencies.")
100
+ logger.warning("Detected multiple ELA passes indicating potential inconsistencies.")
101
+
102
+ # Example: Check for gradient anomalies (simplified)
103
+ gradient_anomalies = [desc for desc in forensic_output_descriptions if "Gradient processing" in desc]
104
+ if len(gradient_anomalies) > 1 and "Highlights edges and transitions" in gradient_anomalies[0]:
105
+ # This is a placeholder for actual image analysis, e.g., checking standard deviation of gradients
106
+ anomalies_detected.append("Gradient analysis shows unusual edge patterns.")
107
+ logger.warning("Detected unusual edge patterns from gradient analysis.")
108
+
109
+ # Example: Check for MinMax anomalies (simplified)
110
+ minmax_anomalies = [desc for desc in forensic_output_descriptions if "MinMax processing" in desc]
111
+ if len(minmax_anomalies) > 1 and "Deviations in local pixel values" in minmax_anomalies[0]:
112
+ # Placeholder for actual analysis of minmax output, e.g., deviation variance
113
+ anomalies_detected.append("MinMax processing reveals subtle pixel deviations.")
114
+ logger.warning("Detected subtle pixel deviations from MinMax processing.")
115
+
116
+ if "Bit Plane extractor" in str(forensic_output_descriptions):
117
+ anomalies_detected.append("Bit Plane extraction performed.")
118
+ logger.info("Bit Plane extraction performed.")
119
+
120
+ if anomalies_detected:
121
+ summary_message = "Potential anomalies detected: " + "; ".join(anomalies_detected)
122
+ logger.warning(f"Forensic anomaly detection summary: {summary_message}")
123
+ else:
124
+ logger.info(f"Forensic anomaly detection summary: {summary_message}")
125
+
126
+ return {"anomalies": anomalies_detected, "summary": summary_message}
app.py CHANGED
@@ -6,6 +6,8 @@ import os
6
  import time
7
  import logging
8
  import io
 
 
9
 
10
  # Assuming these are available from your utils and agents directories
11
  # You might need to adjust paths or copy these functions/classes if they are not directly importable.
@@ -28,11 +30,28 @@ import json
28
  from huggingface_hub import CommitScheduler
29
  from dotenv import load_dotenv
30
 
31
- # Configure logging
32
  logging.basicConfig(level=logging.INFO)
33
  logger = logging.getLogger(__name__)
34
  os.environ['HF_HUB_CACHE'] = './models'
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  LOCAL_LOG_DIR = "./hf_inference_logs"
37
  HF_DATASET_NAME="aiwithoutborders-xyz/degentic_rd0"
38
  load_dotenv()
@@ -102,6 +121,67 @@ register_model_with_metadata(
102
  architecture="SwinV2", dataset="TBA"
103
  )
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  clf_2 = pipeline("image-classification", model=MODEL_PATHS["model_2"], device=device)
106
  register_model_with_metadata(
107
  "model_2", clf_2, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_2"],
@@ -446,14 +526,26 @@ detection_model_eval_playground = gr.Interface(
446
  api_name="predict",
447
  live=True # Enable streaming
448
  )
 
 
 
 
449
 
 
 
 
 
 
 
 
 
450
  community_forensics_preview = gr.Interface(
451
- fn=lambda: gr.load("aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview", src="spaces"),
452
  inputs=gr.Image(type="filepath"),
453
  outputs=gr.HTML(), # or gr.Markdown() if it's just text
454
  title="Quick and simple prediction by our strongest model.",
455
  description="No ensemble, no context, no agents, just a quick and simple prediction by our strongest model.",
456
- api_name="quick_predict"
457
  )
458
 
459
  # leaderboard = gr.Interface(
@@ -463,15 +555,30 @@ community_forensics_preview = gr.Interface(
463
  # title="Leaderboard",
464
  # api_name="leaderboard"
465
  # )
 
 
 
466
 
467
- # simple_predict_interface = gr.Interface(
468
- # fn=simple_prediction,
469
- # inputs=gr.Image(type="filepath"),
470
- # outputs=gr.Text(),
471
- # title="Quick and simple prediction by our strongest model.",
472
- # description="No ensemble, no context, no agents, just a quick and simple prediction by our strongest model.",
473
- # api_name="simple_predict"
474
- # )
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
  noise_estimation_interface = gr.Interface(
477
  fn=noise_estimation,
@@ -540,36 +647,27 @@ minmax_processing_interface = gr.Interface(
540
  api_name="tool_minmax_processing"
541
  )
542
 
543
- def augment_image_interface(img, augment_methods, rotate_degrees, noise_level, sharpen_strength):
544
- if img is None:
545
- raise gr.Error("No image provided for augmentation. Please upload an image.")
546
-
547
- # Ensure image is PIL Image and in RGB format
548
- if not isinstance(img, Image.Image):
549
- try:
550
- img = Image.fromarray(img)
551
- except Exception as e:
552
- raise gr.Error(f"Could not convert input to PIL Image: {e}")
553
- if img.mode != 'RGB':
554
- img = img.convert('RGB')
 
 
555
 
556
- augmented_img, _ = augment_image(img, augment_methods, rotate_degrees, noise_level, sharpen_strength)
557
- return augmented_img
 
 
 
558
 
559
- augmentation_tool_interface = gr.Interface(
560
- fn=augment_image_interface,
561
- inputs=[
562
- gr.Image(label="Upload Image to Augment", sources=['upload', 'webcam'], type='pil'),
563
- gr.CheckboxGroup(["rotate", "add_noise", "sharpen"], label="Augmentation Methods"),
564
- gr.Slider(0, 360, value=0, step=1, label="Rotate Degrees", visible=True),
565
- gr.Slider(0, 100, value=0, step=1, label="Noise Level", visible=True),
566
- gr.Slider(0, 200, value=1, step=1, label="Sharpen Strength", visible=True)
567
- ],
568
- outputs=gr.Image(label="Augmented Image", type='pil'),
569
- title="Image Augmentation Tool",
570
- description="Apply various augmentation techniques to your image.",
571
- api_name="augment_image"
572
- )
573
 
574
  demo = gr.TabbedInterface(
575
  [
@@ -580,7 +678,7 @@ demo = gr.TabbedInterface(
580
  ela_interface,
581
  gradient_processing_interface,
582
  minmax_processing_interface,
583
- augmentation_tool_interface
584
  ],
585
  [
586
  "Run Ensemble Prediction",
@@ -590,12 +688,15 @@ demo = gr.TabbedInterface(
590
  "Error Level Analysis (ELA)",
591
  "Gradient Processing",
592
  "MinMax Processing",
593
- "Image Augmentation"
594
  ],
595
  title="Deepfake Detection & Forensics Tools",
596
  theme=None,
597
 
598
  )
599
 
 
 
 
600
  if __name__ == "__main__":
601
  demo.launch(mcp_server=True)
 
6
  import time
7
  import logging
8
  import io
9
+ import collections # Import collections
10
+ import onnxruntime # Import onnxruntime
11
 
12
  # Assuming these are available from your utils and agents directories
13
  # You might need to adjust paths or copy these functions/classes if they are not directly importable.
 
30
  from huggingface_hub import CommitScheduler
31
  from dotenv import load_dotenv
32
 
 
33
  logging.basicConfig(level=logging.INFO)
34
  logger = logging.getLogger(__name__)
35
  os.environ['HF_HUB_CACHE'] = './models'
36
 
37
+ # --- Gradio Log Handler ---
38
+ class GradioLogHandler(logging.Handler):
39
+ def __init__(self, log_queue):
40
+ super().__init__()
41
+ self.log_queue = log_queue
42
+ self.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
43
+
44
+ def emit(self, record):
45
+ self.log_queue.append(self.format(record))
46
+
47
+ log_queue = collections.deque(maxlen=1000) # Store last 1000 log messages
48
+ gradio_handler = GradioLogHandler(log_queue)
49
+
50
+ # Set root logger level to DEBUG to capture all messages from agents
51
+ logging.getLogger().setLevel(logging.DEBUG)
52
+ logging.getLogger().addHandler(gradio_handler)
53
+ # --- End Gradio Log Handler ---
54
+
55
  LOCAL_LOG_DIR = "./hf_inference_logs"
56
  HF_DATASET_NAME="aiwithoutborders-xyz/degentic_rd0"
57
  load_dotenv()
 
121
  architecture="SwinV2", dataset="TBA"
122
  )
123
 
124
+ # --- ONNX Quantized Model Example ---
125
+ ONNX_QUANTIZED_MODEL_PATH = "./models/model_1_quantized.onnx" # Placeholder for your ONNX model
126
+
127
+ def preprocess_onnx_input(image: Image.Image):
128
+ # Preprocess image for ONNX model (e.g., for SwinV2, usually 256x256, normalized)
129
+ if image.mode != 'RGB':
130
+ image = image.convert('RGB')
131
+
132
+ transform = transforms.Compose([
133
+ transforms.Resize((256, 256)),
134
+ transforms.ToTensor(),
135
+ transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]), # ImageNet normalization
136
+ ])
137
+ input_tensor = transform(image)
138
+ # ONNX expects numpy array with batch dimension (1, C, H, W)
139
+ return input_tensor.unsqueeze(0).cpu().numpy()
140
+
141
+ def infer_onnx_model(preprocessed_image_np):
142
+ try:
143
+ # Ensure the ONNX model exists before trying to load it
144
+ if not os.path.exists(ONNX_QUANTIZED_MODEL_PATH):
145
+ logger.error(f"ONNX quantized model not found at: {ONNX_QUANTIZED_MODEL_PATH}")
146
+ raise FileNotFoundError(f"ONNX quantized model not found at: {ONNX_QUANTIZED_MODEL_PATH}")
147
+
148
+ ort_session = onnxruntime.InferenceSession(ONNX_QUANTIZED_MODEL_PATH)
149
+ ort_inputs = {ort_session.get_inputs()[0].name: preprocessed_image_np}
150
+ ort_outputs = ort_session.run(None, ort_inputs)
151
+
152
+ # Assuming the output is logits, apply softmax to get probabilities
153
+ logits = ort_outputs[0]
154
+ probabilities = softmax(logits[0]) # Remove batch dim, apply softmax
155
+ return {"logits": logits, "probabilities": probabilities}
156
+
157
+ except Exception as e:
158
+ logger.error(f"Error during ONNX inference: {e}")
159
+ # Return a structure consistent with other model errors
160
+ return {"logits": np.array([]), "probabilities": np.array([])}
161
+
162
+ def postprocess_onnx_output(onnx_output, class_names):
163
+ probabilities = onnx_output.get("probabilities")
164
+ if probabilities is not None and len(probabilities) == len(class_names):
165
+ return {class_names[i]: probabilities[i] for i in range(len(class_names))}
166
+ else:
167
+ logger.warning("ONNX post-processing failed or class names mismatch.")
168
+ return {name: 0.0 for name in class_names}
169
+
170
+ # Register the ONNX quantized model
171
+ register_model_with_metadata(
172
+ "model_1_onnx_quantized",
173
+ infer_onnx_model,
174
+ preprocess_onnx_input,
175
+ postprocess_onnx_output,
176
+ CLASS_NAMES["model_1"], # Assuming it uses the same class names as model_1
177
+ display_name="SWIN1",
178
+ contributor="haywoodsloan",
179
+ model_path=ONNX_QUANTIZED_MODEL_PATH,
180
+ architecture="SwinV2",
181
+ dataset="TBA"
182
+ )
183
+ # --- End ONNX Quantized Model Example ---
184
+
185
  clf_2 = pipeline("image-classification", model=MODEL_PATHS["model_2"], device=device)
186
  register_model_with_metadata(
187
  "model_2", clf_2, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_2"],
 
526
  api_name="predict",
527
  live=True # Enable streaming
528
  )
529
+ # def echo_headers(x, request: gr.Request):
530
+ # print(dict(request.headers))
531
+ # return str(dict(request.headers))
532
+
533
 
534
+ def predict(img):
535
+ client = Client("aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview")
536
+ client.view_api()
537
+ result = client.predict(
538
+ handle_file(img),
539
+ api_name="/simple_predict"
540
+ )
541
+ return result
542
  community_forensics_preview = gr.Interface(
543
+ fn=predict,
544
  inputs=gr.Image(type="filepath"),
545
  outputs=gr.HTML(), # or gr.Markdown() if it's just text
546
  title="Quick and simple prediction by our strongest model.",
547
  description="No ensemble, no context, no agents, just a quick and simple prediction by our strongest model.",
548
+ api_name="predict"
549
  )
550
 
551
  # leaderboard = gr.Interface(
 
555
  # title="Leaderboard",
556
  # api_name="leaderboard"
557
  # )
558
+ def simple_prediction(img):
559
+ """
560
+ Quick and simple deepfake or real image prediction by the strongest open-source model on the hub.
561
 
562
+ Args:
563
+ img (str): The input image to analyze, provided as a file path.
564
+
565
+ Returns:
566
+ str: The prediction result stringified from dict. Example: `{'Fake Probability': 0.002, 'Result Description': 'The image is likely real.'}`
567
+ """
568
+ client = Client("aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview")
569
+ client.view_api()
570
+ client.predict(
571
+ handle_file(img),
572
+ api_name="simple_predict"
573
+ )
574
+ simple_predict_interface = gr.Interface(
575
+ fn=simple_prediction,
576
+ inputs=gr.Image(type="filepath"),
577
+ outputs=gr.Text(),
578
+ title="Quick and simple prediction by our strongest model.",
579
+ description="No ensemble, no context, no agents, just a quick and simple prediction by our strongest model.",
580
+ api_name="simple_predict"
581
+ )
582
 
583
  noise_estimation_interface = gr.Interface(
584
  fn=noise_estimation,
 
647
  api_name="tool_minmax_processing"
648
  )
649
 
650
+ # augmentation_tool_interface = gr.Interface(
651
+ # fn=augment_image,
652
+ # inputs=[
653
+ # gr.Image(label="Upload Image to Augment", sources=['upload', 'webcam'], type='pil'),
654
+ # gr.CheckboxGroup(["rotate", "add_noise", "sharpen"], label="Augmentation Methods"),
655
+ # gr.Slider(0, 360, value=0, step=1, label="Rotate Degrees", visible=True),
656
+ # gr.Slider(0, 100, value=0, step=1, label="Noise Level", visible=True),
657
+ # gr.Slider(0, 200, value=1, step=1, label="Sharpen Strength", visible=True)
658
+ # ],
659
+ # outputs=gr.Image(label="Augmented Image", type='pil'),
660
+ # title="Image Augmentation Tool",
661
+ # description="Apply various augmentation techniques to your image.",
662
+ # api_name="augment_image"
663
+ # )
664
 
665
+ def get_captured_logs():
666
+ # Retrieve all logs from the queue and clear it
667
+ logs = list(log_queue)
668
+ log_queue.clear() # Clear the queue after retrieving
669
+ return "\n".join(logs)
670
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
671
 
672
  demo = gr.TabbedInterface(
673
  [
 
678
  ela_interface,
679
  gradient_processing_interface,
680
  minmax_processing_interface,
681
+ gr.Textbox(label="Agent Logs", interactive=False, lines=5, max_lines=20, autoscroll=True) # New textbox for logs
682
  ],
683
  [
684
  "Run Ensemble Prediction",
 
688
  "Error Level Analysis (ELA)",
689
  "Gradient Processing",
690
  "MinMax Processing",
691
+ "Agent Logs" # New tab title
692
  ],
693
  title="Deepfake Detection & Forensics Tools",
694
  theme=None,
695
 
696
  )
697
 
698
+ # Add live update for the logs textbox
699
+ demo.load(get_captured_logs, None, demo.outputs[8], every=1)
700
+
701
  if __name__ == "__main__":
702
  demo.launch(mcp_server=True)
forensics/bitplane.py CHANGED
@@ -8,7 +8,14 @@ def bit_plane_extractor(
8
  bit: int = 0,
9
  filter_type: str = "Disabled"
10
  ) -> Image.Image:
11
- """Extract and visualize a bit plane from a selected channel of the image."""
 
 
 
 
 
 
 
12
  img = np.array(image.convert("RGB"))
13
  if channel == "Luminance":
14
  img = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
 
8
  bit: int = 0,
9
  filter_type: str = "Disabled"
10
  ) -> Image.Image:
11
+ """Extract and visualize a bit plane from a selected channel of the image.
12
+
13
+ Args:
14
+ image (Image.Image, string: filepath): The input image to analyze.
15
+ channel (str, optional): The channel to extract. Defaults to "Luminance".
16
+ bit (int, optional): The bit to extract. Defaults to 0.
17
+ filter_type (str, optional): The type of filter to apply. Defaults to "Disabled".
18
+ """
19
  img = np.array(image.convert("RGB"))
20
  if channel == "Luminance":
21
  img = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
forensics/gradient.py CHANGED
@@ -19,6 +19,18 @@ def create_lut(intensity, gamma):
19
  return lut
20
 
21
  def gradient_processing(image, intensity=90, blue_mode="Abs", invert=False, equalize=False):
 
 
 
 
 
 
 
 
 
 
 
 
22
  image = np.array(image)
23
  dx, dy = cv.spatialGradient(cv.cvtColor(image, cv.COLOR_BGR2GRAY))
24
  intensity = int(intensity / 100 * 127)
 
19
  return lut
20
 
21
  def gradient_processing(image, intensity=90, blue_mode="Abs", invert=False, equalize=False):
22
+ """Apply gradient processing to an image.
23
+
24
+ Args:
25
+ image (Image.Image, string: filepath): The input image to analyze.
26
+ intensity (int, optional): The intensity of the gradient. Defaults to 90.
27
+ blue_mode (str, optional): The mode to use for the blue channel. Defaults to "Abs".
28
+ invert (bool, optional): Whether to invert the gradient. Defaults to False.
29
+ equalize (bool, optional): Whether to equalize the gradient. Defaults to False.
30
+
31
+ Returns:
32
+ Image.Image: A PIL image of the gradient.
33
+ """
34
  image = np.array(image)
35
  dx, dy = cv.spatialGradient(cv.cvtColor(image, cv.COLOR_BGR2GRAY))
36
  intensity = int(intensity / 100 * 127)
forensics/wavelet.py CHANGED
@@ -4,7 +4,15 @@ import cv2
4
  from PIL import Image
5
 
6
  def noise_estimation(image: Image.Image, blocksize: int = 8) -> Image.Image:
7
- """Estimate local noise using wavelet blocking. Returns a PIL image of the noise map."""
 
 
 
 
 
 
 
 
8
  im = np.array(image.convert('L'))
9
  y = np.double(im)
10
  cA1, (cH, cV, cD) = pywt.dwt2(y, 'db8')
 
4
  from PIL import Image
5
 
6
  def noise_estimation(image: Image.Image, blocksize: int = 8) -> Image.Image:
7
+ """Estimate local noise using wavelet blocking. Returns a PIL image of the noise map.
8
+
9
+ Args:
10
+ image (Image.Image, string: filepath): The input image to analyze.
11
+ blocksize (int): The size of the blocks to use for wavelet blocking.
12
+
13
+ Returns:
14
+ Image.Image: A PIL image of the noise map.
15
+ """
16
  im = np.array(image.convert('L'))
17
  y = np.double(im)
18
  cA1, (cH, cV, cD) = pywt.dwt2(y, 'db8')
requirements.txt CHANGED
@@ -13,8 +13,9 @@ PyWavelets==1.8.0
13
 
14
  # System utilities
15
  psutil
 
16
  python-dotenv
17
-
18
  # Gradio and UI
19
  gradio[mcp]>=5.33.1
20
  # gradio_leaderboard==0.0.13
 
13
 
14
  # System utilities
15
  psutil
16
+ GPUtil
17
  python-dotenv
18
+ onnxruntime
19
  # Gradio and UI
20
  gradio[mcp]>=5.33.1
21
  # gradio_leaderboard==0.0.13