LPX55 commited on
Commit
a9e9791
·
verified ·
2 Parent(s): 968c2f2 103c27f

Merge branch #buildborderless/OpenSight-Deepfake-Detection-Models-Playground' into 'LPX55/Deepfake-Detection-Playground-Dev'

Browse files
.github/instructions/copilot-instructions.md CHANGED
@@ -1,34 +1,50 @@
1
 
2
- # Copilot Instructions for OpenSight-Deepfake-Detection-Models-Playground
 
3
 
4
  ## Project Overview
5
- This project is a modular, agent-driven toolkit for deepfake detection and digital forensics. It uses an ensemble of models, advanced forensic tools, and smart agents to provide explainable, extensible, and robust detectionoptimized for integration with vision LLMs and multimodal AI agents.
6
 
7
- ## Architecture & Key Components
8
- - **Entrypoint:** `app.py` (Gradio app + MCP server).
9
- - **Forensics:** `forensics/` (e.g., `ela.py`, `gradient.py`, `minmax.py`, `bitplane.py`, `wavelet.py`). Each file implements a forensic technique, callable via LLM/MCP.
10
- - **Agents:** `agents/` (e.g., `EnsembleMonitorAgent`, `ModelWeightManager`, `ContextualIntelligenceAgent`, `ForensicAnomalyDetectionAgent`). Agents coordinate model weighting, context inference, and anomaly detection.
11
- - **Model Management:** Models are registered in `utils/registry.py` and managed as an ensemble with dynamic, context-aware weighting.
12
- - **Utilities:** `utils/` (logging, augmentation, registry, health checks).
 
 
13
 
14
  ## Data Flow & Prediction Pipeline
15
- 1. **Image Preprocessing:** Normalize to PIL RGB; optionally augment (rotate, noise, sharpen).
16
- 2. **Agent Initialization:** Monitoring, optimization, and context agents are set up.
17
- 3. **Model Inference:** Each model predicts independently; results tracked by agents.
18
- 4. **Consensus:** Model weights are dynamically adjusted based on context and agent feedback.
19
- 5. **Forensic Analysis:** Multiple forensic tools run in parallel; outputs analyzed for anomalies.
20
- 6. **Logging:** All results (images, predictions, agent data) are logged to Hugging Face datasets.
21
-
22
- ## Developer Workflows
23
- - **Run the App:**
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  ```bash
25
- python app_optimized.py
 
26
  ```
27
- - **Dependencies:** See `requirements.txt` (notably: gradio, PIL, numpy, torch, smolagents, etc.).
28
  - **Extending Forensics/Agents:** Add new tools in `forensics/`, new agents in `agents/`, and register in the main app.
29
  - **Testing:** Unit tests for agents/models are planned (see roadmap in `README.md`).
30
 
31
- ## Project-Specific Patterns & Conventions
32
  - **Forensic Tool Naming:** Use `tool_*` or descriptive names (e.g., `tool_ela`, `tool_waveletnoise`).
33
  - **Agent Classes:** Use `*Agent` suffix (e.g., `EnsembleMonitorAgent`).
34
  - **API Exposure:** Functions are exposed for LLM/MCP calls with clear parameter/return docs (see `README.md`).
@@ -38,7 +54,7 @@ This project is a modular, agent-driven toolkit for deepfake detection and digit
38
  ## Integration & Extension
39
  - **Add Models:** Update ensemble logic and register in agent system.
40
  - **Add Forensic Tools:** Implement in `forensics/`, expose via main app, and document parameters/returns.
41
- - **LLM/Multimodal Integration:** Hybrid input strategies (e.g., ELA+RGB, metadata+image) are encouraged; see `README.md` for detailed tables and guidance.
42
 
43
  ## References
44
  - **Forensic Techniques:** See `forensics/` for implementation details.
@@ -47,4 +63,4 @@ This project is a modular, agent-driven toolkit for deepfake detection and digit
47
  - **Roadmap:** Ongoing and planned features are tracked in `README.md`.
48
 
49
  ---
50
- **Tip:** When extending or debugging, always check agent logic and consensus weighting, as these are central to system behavior.
 
1
 
2
+
3
+ # Copilot Instructions for OpenSight-Deepfake-Detection-Models-Playground (2025)
4
 
5
  ## Project Overview
6
+ OpenSight is a modular, agent-driven toolkit for deepfake detection and digital forensics. It leverages an ensemble of models, advanced forensic tools, and smart agents for explainable, extensible, and robust detection. The system is optimized for integration with vision LLMs and multimodal AI agents, and supports logging to Hugging Face datasets.
7
 
8
+ ## Key Technologies
9
+ - **Gradio**: Main UI and API server (`app.py`).
10
+ - **Hugging Face Hub**: Model and dataset management, logging, and deployment.
11
+ - **Git LFS**: Required for storing binary files (e.g., PNGs) in the repo. See `.gitattributes` for tracked types.
12
+ - **Agents**: Smart agents for ensemble monitoring, weight optimization, system health, context intelligence, and anomaly detection (`agents/`).
13
+ - **Forensic Tools**: Modular forensic techniques in `forensics/` (ELA, gradient, minmax, bitplane, wavelet, exif, etc.).
14
+ - **Model Management**: Models are registered and managed in `utils/registry.py`, loaded via ONNX/Hugging Face/Gradio API.
15
+ - **Utilities**: Logging, augmentation, health checks, and more in `utils/`.
16
 
17
  ## Data Flow & Prediction Pipeline
18
+ 1. **Image Preprocessing**: Normalize to PIL RGB, optional augmentation (rotate, noise, sharpen).
19
+ 2. **Agent Initialization**: Monitoring, optimization, and context agents setup.
20
+ 3. **Model Inference**: Each model predicts independently; results tracked by agents.
21
+ 4. **Consensus**: Model weights dynamically adjusted based on context and agent feedback.
22
+ 5. **Forensic Analysis**: Multiple forensic tools run in parallel; outputs analyzed for anomalies.
23
+ 6. **Logging**: All results (images, predictions, agent data) are logged to Hugging Face datasets (`hf_logger.py`).
24
+
25
+ ## Developer Workflow
26
+ - **Run the App:**
27
+ ```bash
28
+ python app.py
29
+ ```
30
+ - **Dependencies:** See `requirements.txt` (gradio, PIL, numpy, torch, huggingface_hub, etc.).
31
+ - **Binary Files:** All PNGs and other binaries must be tracked with Git LFS. If you see a push error, run:
32
+ ```bash
33
+ git lfs track "*.png"
34
+ git add .gitattributes
35
+ git add <yourfile.png>
36
+ git commit -m "Track PNG files with Git LFS"
37
+ git push origin main
38
+ ```
39
+ If the file is already in history, use:
40
  ```bash
41
+ git lfs migrate import --include="*.png"
42
+ git push origin main
43
  ```
 
44
  - **Extending Forensics/Agents:** Add new tools in `forensics/`, new agents in `agents/`, and register in the main app.
45
  - **Testing:** Unit tests for agents/models are planned (see roadmap in `README.md`).
46
 
47
+ ## Project Patterns & Conventions
48
  - **Forensic Tool Naming:** Use `tool_*` or descriptive names (e.g., `tool_ela`, `tool_waveletnoise`).
49
  - **Agent Classes:** Use `*Agent` suffix (e.g., `EnsembleMonitorAgent`).
50
  - **API Exposure:** Functions are exposed for LLM/MCP calls with clear parameter/return docs (see `README.md`).
 
54
  ## Integration & Extension
55
  - **Add Models:** Update ensemble logic and register in agent system.
56
  - **Add Forensic Tools:** Implement in `forensics/`, expose via main app, and document parameters/returns.
57
+ - **LLM/Multimodal Integration:** Hybrid input strategies (e.g., ELA+RGB, metadata+image) are encouraged; see `README.md` for details.
58
 
59
  ## References
60
  - **Forensic Techniques:** See `forensics/` for implementation details.
 
63
  - **Roadmap:** Ongoing and planned features are tracked in `README.md`.
64
 
65
  ---
66
+ **Tip:** Always check agent logic and consensus weighting when extending or debugging, as these are central to system behavior. For binary file push errors, ensure Git LFS is set up and files are tracked correctly.
.python-version DELETED
@@ -1 +0,0 @@
1
- 3.11
 
 
README.md CHANGED
@@ -1,25 +1,26 @@
1
  ---
2
  title: MCP Toolkit - Re-Thinking Deepfake Detection & Forensics
3
- description: MCP Server for Project OpenSight's Deepfake Detection & Digital Forensics Tools
 
 
4
  emoji: 🚑
5
  colorFrom: yellow
6
  colorTo: yellow
7
  sdk: gradio
8
- sdk_version: 5.33.0
9
  app_file: app.py
10
  pinned: true
11
  models:
12
  - aiwithoutborders-xyz/OpenSight-CommunityForensics-Deepfake-ViT
13
  license: mit
14
  tags:
15
- - deepfake-detection
16
- - ai-agents
17
- - leaderboards
18
- - deepfake
19
- - detection
20
- - ensemble
21
- - forensics
22
-
23
  ---
24
 
25
  # The Detection Dilemma: The Degentic Games
 
1
  ---
2
  title: MCP Toolkit - Re-Thinking Deepfake Detection & Forensics
3
+ description: >-
4
+ MCP Server for Project OpenSight's Deepfake Detection & Digital Forensics
5
+ Tools
6
  emoji: 🚑
7
  colorFrom: yellow
8
  colorTo: yellow
9
  sdk: gradio
10
+ sdk_version: 5.49.1
11
  app_file: app.py
12
  pinned: true
13
  models:
14
  - aiwithoutborders-xyz/OpenSight-CommunityForensics-Deepfake-ViT
15
  license: mit
16
  tags:
17
+ - deepfake-detection
18
+ - ai-agents
19
+ - leaderboards
20
+ - deepfake
21
+ - detection
22
+ - ensemble
23
+ - forensics
 
24
  ---
25
 
26
  # The Detection Dilemma: The Degentic Games
agents/ensemble_weights.py CHANGED
@@ -38,8 +38,6 @@ class ContextualWeightOverrideAgent:
38
  agent_logger.log("weight_optimization", "info", f"Combined context overrides: {combined_overrides}")
39
  return combined_overrides
40
 
41
-
42
-
43
  class ModelWeightManager:
44
  def __init__(self, strongest_model_id: str = None):
45
  agent_logger = AgentLogger()
@@ -49,8 +47,8 @@ class ModelWeightManager:
49
  if num_models > 0:
50
  if strongest_model_id and strongest_model_id in MODEL_REGISTRY:
51
  agent_logger.log("weight_optimization", "info", f"Designating '{strongest_model_id}' as the strongest model.")
52
- # Assign a high weight to the strongest model (e.g., 50%)
53
- strongest_weight_share = 0.5
54
  self.base_weights = {strongest_model_id: strongest_weight_share}
55
  remaining_models = [mid for mid in MODEL_REGISTRY.keys() if mid != strongest_model_id]
56
  if remaining_models:
@@ -126,7 +124,7 @@ class ModelWeightManager:
126
  """Check if models agree on prediction"""
127
  agent_logger.log("weight_optimization", "info", "Checking for consensus among model predictions.")
128
  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"]
129
- agent_logger.log("weight_optimization", "debug", f"Non-none predictions for consensus check: {non_none_predictions}")
130
  result = len(non_none_predictions) > 0 and len(set(non_none_predictions)) == 1
131
  agent_logger.log("weight_optimization", "info", f"Consensus detected: {result}")
132
  return result
@@ -135,7 +133,7 @@ class ModelWeightManager:
135
  """Check if models have conflicting predictions"""
136
  agent_logger.log("weight_optimization", "info", "Checking for conflicts among model predictions.")
137
  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"]
138
- agent_logger.log("weight_optimization", "debug", f"Non-none predictions for conflict check: {non_none_predictions}")
139
  result = len(non_none_predictions) > 1 and len(set(non_none_predictions)) > 1
140
  agent_logger.log("weight_optimization", "info", f"Conflicts detected: {result}")
141
  return result
@@ -154,4 +152,4 @@ class ModelWeightManager:
154
  return {} # No models registered
155
  normalized = {k: v/total for k, v in weights.items()}
156
  agent_logger.log("weight_optimization", "info", f"Weights normalized. Total sum: {sum(normalized.values()):.2f}")
157
- return normalized
 
38
  agent_logger.log("weight_optimization", "info", f"Combined context overrides: {combined_overrides}")
39
  return combined_overrides
40
 
 
 
41
  class ModelWeightManager:
42
  def __init__(self, strongest_model_id: str = None):
43
  agent_logger = AgentLogger()
 
47
  if num_models > 0:
48
  if strongest_model_id and strongest_model_id in MODEL_REGISTRY:
49
  agent_logger.log("weight_optimization", "info", f"Designating '{strongest_model_id}' as the strongest model.")
50
+ # Assign a high weight to the strongest model (e.g., 40%)
51
+ strongest_weight_share = 0.4
52
  self.base_weights = {strongest_model_id: strongest_weight_share}
53
  remaining_models = [mid for mid in MODEL_REGISTRY.keys() if mid != strongest_model_id]
54
  if remaining_models:
 
124
  """Check if models agree on prediction"""
125
  agent_logger.log("weight_optimization", "info", "Checking for consensus among model predictions.")
126
  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"]
127
+ agent_logger.log("weight_optimization", "info", f"Non-none predictions for consensus check: {non_none_predictions}")
128
  result = len(non_none_predictions) > 0 and len(set(non_none_predictions)) == 1
129
  agent_logger.log("weight_optimization", "info", f"Consensus detected: {result}")
130
  return result
 
133
  """Check if models have conflicting predictions"""
134
  agent_logger.log("weight_optimization", "info", "Checking for conflicts among model predictions.")
135
  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"]
136
+ agent_logger.log("weight_optimization", "info", f"Non-none predictions for conflict check: {non_none_predictions}")
137
  result = len(non_none_predictions) > 1 and len(set(non_none_predictions)) > 1
138
  agent_logger.log("weight_optimization", "info", f"Conflicts detected: {result}")
139
  return result
 
152
  return {} # No models registered
153
  normalized = {k: v/total for k, v in weights.items()}
154
  agent_logger.log("weight_optimization", "info", f"Weights normalized. Total sum: {sum(normalized.values()):.2f}")
155
+ return normalized
forensics/__init__.py CHANGED
@@ -4,6 +4,7 @@ from .ela import ELA
4
  from .gradient import gradient_processing
5
  from .minmax import minmax_process
6
  from .wavelet import noise_estimation
 
7
 
8
  __all__ = [
9
  'bit_plane_extractor',
@@ -11,5 +12,6 @@ __all__ = [
11
  # 'exif_full_dump',
12
  'gradient_processing',
13
  'minmax_process',
14
- 'noise_estimation'
15
- ]
 
 
4
  from .gradient import gradient_processing
5
  from .minmax import minmax_process
6
  from .wavelet import noise_estimation
7
+ from .jpeg_compression import estimate_qf
8
 
9
  __all__ = [
10
  'bit_plane_extractor',
 
12
  # 'exif_full_dump',
13
  'gradient_processing',
14
  'minmax_process',
15
+ 'noise_estimation',
16
+ 'estimate_qf'
17
+ ]
forensics/jpeg_compression.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2 as cv
2
+ import numpy as np
3
+ from PIL import Image
4
+
5
+ DCT_SIZE = 8
6
+ TABLE_SIZE = DCT_SIZE ** 2
7
+ ZIG_ZAG = [
8
+ [0, 0],
9
+ [0, 1],
10
+ [1, 0],
11
+ [2, 0],
12
+ [1, 1],
13
+ [0, 2],
14
+ [0, 3],
15
+ [1, 2],
16
+ [2, 1],
17
+ [3, 0],
18
+ [4, 0],
19
+ [3, 1],
20
+ [2, 2],
21
+ [1, 3],
22
+ [0, 4],
23
+ [0, 5],
24
+ [1, 4],
25
+ [2, 3],
26
+ [3, 2],
27
+ [4, 1],
28
+ [5, 0],
29
+ [6, 0],
30
+ [5, 1],
31
+ [4, 2],
32
+ [3, 3],
33
+ [2, 4],
34
+ [1, 5],
35
+ [0, 6],
36
+ [0, 7],
37
+ [1, 6],
38
+ [2, 5],
39
+ [3, 4],
40
+ [4, 4],
41
+ [5, 3],
42
+ [6, 2],
43
+ [7, 1],
44
+ [7, 2],
45
+ [6, 3],
46
+ [5, 4],
47
+ [4, 5],
48
+ [3, 5],
49
+ [2, 6],
50
+ [1, 7],
51
+ [2, 7],
52
+ [3, 6],
53
+ [4, 5],
54
+ [5, 4],
55
+ [6, 3],
56
+ [7, 2],
57
+ [7, 3],
58
+ [6, 4],
59
+ [5, 5],
60
+ [4, 6],
61
+ [3, 7],
62
+ [4, 7],
63
+ [5, 6],
64
+ [6, 5],
65
+ [7, 4],
66
+ [7, 5],
67
+ [6, 6],
68
+ [5, 7],
69
+ [6, 7],
70
+ [7, 6],
71
+ [7, 7],
72
+ ]
73
+
74
+
75
+ def compress_jpg(image: Image.Image, quality, color=True):
76
+ """Compress a PIL image to JPEG format with specified quality.
77
+
78
+ Args:
79
+ image: Input PIL image (RGB format)
80
+ quality: JPEG compression quality (1-100)
81
+ color: Whether to preserve color (BGR format)
82
+
83
+ Returns:
84
+ np.ndarray: Decompressed image in BGR or grayscale format
85
+ """
86
+ # Convert PIL image to OpenCV BGR format
87
+ img_np = np.array(image)
88
+ if color:
89
+ img_np = cv.cvtColor(img_np, cv.COLOR_RGB2BGR)
90
+
91
+ _, buffer = cv.imencode(".jpg", img_np, [cv.IMWRITE_JPEG_QUALITY, quality])
92
+ return cv.imdecode(buffer, cv.IMREAD_COLOR if color else cv.IMREAD_GRAYSCALE)
93
+
94
+
95
+ def loss_curve(image: Image.Image, qualities=tuple(range(1, 101)), normalize=True):
96
+ """Calculate JPEG compression loss curve for quality estimation.
97
+
98
+ Args:
99
+ image: Input PIL image (RGB format)
100
+ qualities: Quality values to test (1-100)
101
+ normalize: Whether to normalize the output curve
102
+
103
+ Returns:
104
+ np.ndarray: Mean absolute difference values across quality levels
105
+ """
106
+ # Convert input image to grayscale BGR for compression testing
107
+ img_np = np.array(image)
108
+ if len(img_np.shape) == 3:
109
+ x = cv.cvtColor(img_np, cv.COLOR_RGB2GRAY)
110
+ else:
111
+ x = img_np
112
+
113
+ c = np.array(
114
+ [cv.mean(cv.absdiff(compress_jpg(x, q, False), x))[0] for q in qualities]
115
+ )
116
+ if normalize:
117
+ c = cv.normalize(c, None, 0, 1, cv.NORM_MINMAX).flatten()
118
+ return c
119
+
120
+
121
+ def estimate_qf(image):
122
+ return np.argmin(loss_curve(image))
123
+
124
+
125
+ def get_tables(quality):
126
+ luma = np.array(
127
+ [
128
+ [16, 11, 10, 16, 24, 40, 51, 61],
129
+ [12, 12, 14, 19, 26, 58, 60, 55],
130
+ [14, 13, 16, 24, 40, 57, 69, 56],
131
+ [14, 17, 22, 29, 51, 87, 80, 62],
132
+ [18, 22, 37, 56, 68, 109, 103, 77],
133
+ [24, 35, 55, 64, 81, 104, 113, 92],
134
+ [49, 64, 78, 87, 103, 121, 120, 101],
135
+ [72, 92, 95, 98, 112, 100, 103, 99],
136
+ ]
137
+ )
138
+ chroma = np.array(
139
+ [
140
+ [17, 18, 24, 47, 99, 99, 99, 99],
141
+ [18, 21, 26, 66, 99, 99, 99, 99],
142
+ [24, 26, 56, 99, 99, 99, 99, 99],
143
+ [47, 66, 99, 99, 99, 99, 99, 99],
144
+ [99, 99, 99, 99, 99, 99, 99, 99],
145
+ [99, 99, 99, 99, 99, 99, 99, 99],
146
+ [99, 99, 99, 99, 99, 99, 99, 99],
147
+ [99, 99, 99, 99, 99, 99, 99, 99],
148
+ ]
149
+ )
150
+ quality = np.clip(quality, 1, 100)
151
+ if quality < 50:
152
+ quality = 5000 / quality
153
+ else:
154
+ quality = 200 - quality * 2
155
+ tables = np.concatenate((luma[:, :, np.newaxis], chroma[:, :, np.newaxis]), axis=2)
156
+ tables = (tables * quality + 50) / 100
157
+ return np.clip(tables, 1, 255).astype(int)
utils/model_loader.py CHANGED
@@ -86,11 +86,11 @@ def register_all_models(MODEL_PATHS, CLASS_NAMES, device, infer_onnx_model, prep
86
  if model_key == "model_1":
87
  contributor = "haywoodsloan"
88
  architecture = "SwinV2"
89
- dataset = "DeepFakeDetection"
90
  elif model_key == "model_2":
91
  contributor = "Heem2"
92
  architecture = "ViT"
93
- dataset = "DeepFakeDetection"
94
  elif model_key == "model_3":
95
  contributor = "Organika"
96
  architecture = "VIT"
@@ -126,7 +126,7 @@ def register_all_models(MODEL_PATHS, CLASS_NAMES, device, infer_onnx_model, prep
126
  elif model_key == "model_8":
127
  contributor = "aiwithoutborders-xyz"
128
  architecture = "ViT"
129
- dataset = "DeepfakeDetection"
130
  display_name_parts = [model_num]
131
  if architecture and architecture not in ["Unknown"]:
132
  display_name_parts.append(architecture)
 
86
  if model_key == "model_1":
87
  contributor = "haywoodsloan"
88
  architecture = "SwinV2"
89
+ dataset = "Mixed"
90
  elif model_key == "model_2":
91
  contributor = "Heem2"
92
  architecture = "ViT"
93
+ dataset = "Mixed"
94
  elif model_key == "model_3":
95
  contributor = "Organika"
96
  architecture = "VIT"
 
126
  elif model_key == "model_8":
127
  contributor = "aiwithoutborders-xyz"
128
  architecture = "ViT"
129
+ dataset = "Massive"
130
  display_name_parts = [model_num]
131
  if architecture and architecture not in ["Unknown"]:
132
  display_name_parts.append(architecture)