feat: integrate ONNX model inference and logging enhancements, add contextual intelligence and forensic anomaly detection agents
Browse files- agents/ensemble_team.py +88 -106
- agents/ensemble_weights.py +48 -13
- agents/smart_agents.py +112 -51
- app.py +142 -41
- forensics/bitplane.py +8 -1
- forensics/gradient.py +12 -0
- forensics/wavelet.py +9 -1
- requirements.txt +2 -1
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 |
-
|
11 |
-
|
12 |
-
"response_times": {},
|
13 |
-
"confidence_distribution": {},
|
14 |
-
"consensus_rate": 0.0
|
15 |
-
}
|
16 |
self.alerts = []
|
17 |
|
18 |
-
def monitor_prediction(self, model_id,
|
19 |
-
"
|
20 |
-
if model_id not in self.performance_metrics
|
21 |
-
self.performance_metrics[
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
27 |
|
28 |
-
#
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
-
def
|
32 |
-
"
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
if
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
class WeightOptimizationAgent:
|
40 |
def __init__(self, weight_manager):
|
|
|
41 |
self.weight_manager = weight_manager
|
42 |
-
self.prediction_history = []
|
43 |
-
self.
|
44 |
-
self.min_history_for_optimization = 20 # Minimum samples before optimizing
|
45 |
|
46 |
-
def analyze_performance(self,
|
47 |
-
"
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
|
55 |
-
|
56 |
-
|
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 |
-
"
|
100 |
-
"
|
101 |
-
"
|
102 |
-
"error_rates": {}
|
103 |
}
|
104 |
|
105 |
def monitor_system_health(self):
|
106 |
-
"
|
107 |
-
self.
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
|
|
|
|
|
|
113 |
try:
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
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 |
-
"
|
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 |
-
|
|
|
|
|
81 |
for model in adjusted_weights:
|
82 |
adjusted_weights[model] *= self.situation_weights["consensus"]
|
|
|
83 |
|
84 |
# Check for conflicts
|
85 |
-
|
|
|
|
|
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 |
-
|
|
|
|
|
97 |
|
98 |
def _has_consensus(self, predictions):
|
99 |
"""Check if models agree on prediction"""
|
100 |
-
|
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 |
-
|
|
|
|
|
|
|
103 |
|
104 |
def _has_conflicts(self, predictions):
|
105 |
"""Check if models have conflicting predictions"""
|
106 |
-
|
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 |
-
|
|
|
|
|
|
|
109 |
|
110 |
def _normalize_weights(self, weights):
|
111 |
"""Normalize weights to sum to 1"""
|
|
|
112 |
total = sum(weights.values())
|
113 |
if total == 0:
|
114 |
-
|
115 |
-
#
|
116 |
-
|
117 |
-
|
118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
3 |
-
import
|
|
|
|
|
|
|
4 |
logger = logging.getLogger(__name__)
|
5 |
|
6 |
class ContextualIntelligenceAgent:
|
7 |
def __init__(self):
|
8 |
-
|
9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
#
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
-
|
20 |
-
|
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 |
-
|
36 |
-
|
|
|
|
|
|
|
|
|
37 |
|
38 |
def analyze_forensic_outputs(self, forensic_output_descriptions: list[str]) -> dict:
|
39 |
-
"
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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=
|
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="
|
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 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
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 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
|
|
|
|
555 |
|
556 |
-
|
557 |
-
|
|
|
|
|
|
|
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 |
-
|
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 |
-
"
|
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
|