LPX55 commited on
Commit
6555f50
·
1 Parent(s): 057dd29

major(refactor): prep for refactor project

Browse files
Files changed (4) hide show
  1. README.md +6 -4
  2. app_optimized.py → app.backup.7-26.py +194 -453
  3. app.py +422 -210
  4. utils/utils.py +54 -0
README.md CHANGED
@@ -6,17 +6,19 @@ colorFrom: yellow
6
  colorTo: yellow
7
  sdk: gradio
8
  sdk_version: 5.33.0
9
- app_file: app_optimized.py
10
  pinned: true
11
  models:
12
  - aiwithoutborders-xyz/OpenSight-CommunityForensics-Deepfake-ViT
13
  license: mit
14
  tags:
15
- - mcp-server-track
16
  - ai-agents
17
  - leaderboards
18
- - incentivized-contests
19
- - Agents-MCP-Hackathon
 
 
20
 
21
  ---
22
 
 
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
 
app_optimized.py → app.backup.7-26.py RENAMED
@@ -8,12 +8,6 @@ import logging
8
  import io
9
  import collections
10
  import onnxruntime
11
- import json
12
- from huggingface_hub import CommitScheduler, hf_hub_download, snapshot_download
13
- from dotenv import load_dotenv
14
- import concurrent.futures
15
- import ast
16
- import torch
17
 
18
  from utils.utils import softmax, augment_image
19
  from forensics.gradient import gradient_processing
@@ -29,6 +23,10 @@ from utils.registry import register_model, MODEL_REGISTRY, ModelEntry
29
  from agents.ensemble_weights import ModelWeightManager
30
  from transformers import pipeline, AutoImageProcessor, SwinForImageClassification, Swinv2ForImageClassification, AutoFeatureExtractor, AutoModelForImageClassification
31
  from torchvision import transforms
 
 
 
 
32
 
33
  logging.basicConfig(level=logging.INFO)
34
  logger = logging.getLogger(__name__)
@@ -68,25 +66,23 @@ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
68
 
69
  # Model paths and class names (copied from app_mcp.py)
70
  MODEL_PATHS = {
71
- "model_1": "LPX55/detection-model-1-ONNX",
72
- "model_2": "LPX55/detection-model-2-ONNX",
73
- "model_3": "LPX55/detection-model-3-ONNX",
74
  "model_4": "cmckinle/sdxl-flux-detector_v1.1",
75
- "model_5": "LPX55/detection-model-5-ONNX",
76
- "model_6": "LPX55/detection-model-6-ONNX",
77
- "model_7": "LPX55/detection-model-7-ONNX",
78
- "model_8": "aiwithoutborders-xyz/CommunityForensics-DeepfakeDet-ViT"
79
  }
80
 
81
  CLASS_NAMES = {
82
  "model_1": ['artificial', 'real'],
83
  "model_2": ['AI Image', 'Real Image'],
84
- "model_3": ['artificial', 'human'],
85
  "model_4": ['AI', 'Real'],
86
  "model_5": ['Realism', 'Deepfake'],
87
  "model_6": ['ai_gen', 'human'],
88
  "model_7": ['Fake', 'Real'],
89
- "model_8": ['Fake', 'Real'],
90
  }
91
 
92
  def preprocess_resize_256(image):
@@ -101,7 +97,7 @@ def preprocess_resize_224(image):
101
 
102
  def postprocess_pipeline(prediction, class_names):
103
  # Assumes HuggingFace pipeline output
104
- return {pred['label']: float(pred['score']) for pred in prediction}
105
 
106
  def postprocess_logits(outputs, class_names):
107
  # Assumes model output with logits
@@ -109,362 +105,182 @@ def postprocess_logits(outputs, class_names):
109
  probabilities = softmax(logits)
110
  return {class_names[i]: probabilities[i] for i in range(len(class_names))}
111
 
112
- def postprocess_binary_output(output, class_names):
113
- # output can be a dictionary {"probabilities": numpy_array} or directly a numpy_array
114
- probabilities_array = None
115
- if isinstance(output, dict) and "probabilities" in output:
116
- probabilities_array = output["probabilities"]
117
- elif isinstance(output, np.ndarray):
118
- probabilities_array = output
119
- else:
120
- logger.warning(f"Unexpected output type for binary post-processing: {type(output)}. Expected dict with 'probabilities' or numpy.ndarray.")
121
- return {class_names[0]: 0.0, class_names[1]: 1.0}
122
-
123
- logger.info(f"Debug: Probabilities array entering postprocess_binary_output: {probabilities_array}, type: {type(probabilities_array)}, shape: {probabilities_array.shape}")
124
-
125
- if probabilities_array is None:
126
- logger.warning("Probabilities array is None after extracting from output. Returning default scores.")
127
- return {class_names[0]: 0.0, class_names[1]: 1.0}
128
-
129
- if probabilities_array.size == 1:
130
- fake_prob = float(probabilities_array.item())
131
- elif probabilities_array.size == 2:
132
- fake_prob = float(probabilities_array[0])
133
- else:
134
- logger.warning(f"Unexpected probabilities array shape for binary post-processing: {probabilities_array.shape}. Expected size 1 or 2.")
135
- return {class_names[0]: 0.0, class_names[1]: 1.0}
136
-
137
- real_prob = 1.0 - fake_prob # Ensure Fake and Real sum to 1
138
- return {class_names[0]: fake_prob, class_names[1]: real_prob}
139
-
140
- def infer_gradio_api(image_path):
141
- client = Client("aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview")
142
- result_dict = client.predict(
143
- input_image=handle_file(image_path),
144
- api_name="/simple_predict"
145
- )
146
- logger.info(f"Debug: Raw result_dict from Gradio API (model_8): {result_dict}, type: {type(result_dict)}")
147
- # result_dict is already a dictionary, no need for ast.literal_eval
148
- fake_probability = result_dict.get('Fake Probability', 0.0)
149
- logger.info(f"Debug: Parsed result_dict: {result_dict}, Extracted fake_probability: {fake_probability}")
150
- return {"probabilities": np.array([fake_probability])} # Return as a numpy array with one element
151
-
152
- # New preprocess function for Gradio API
153
- def preprocess_gradio_api(image: Image.Image):
154
- # The Gradio API expects a file path, so we need to save the PIL Image to a temporary file.
155
- temp_file_path = "./temp_gradio_input.png"
156
- image.save(temp_file_path)
157
- return temp_file_path
158
-
159
- # New postprocess function for Gradio API (adapting postprocess_binary_output)
160
- def postprocess_gradio_api(gradio_output, class_names):
161
- # gradio_output is expected to be a dictionary like {"probabilities": np.array([fake_prob])}
162
- probabilities_array = None
163
- if isinstance(gradio_output, dict) and "probabilities" in gradio_output:
164
- probabilities_array = gradio_output["probabilities"]
165
- elif isinstance(gradio_output, np.ndarray):
166
- probabilities_array = gradio_output
167
- else:
168
- logger.warning(f"Unexpected output type for Gradio API post-processing: {type(gradio_output)}. Expected dict with 'probabilities' or numpy.ndarray.")
169
- return {class_names[0]: 0.0, class_names[1]: 1.0}
170
-
171
- logger.info(f"Debug: Probabilities array entering postprocess_gradio_api: {probabilities_array}, type: {type(probabilities_array)}, shape: {probabilities_array.shape}")
172
-
173
- if probabilities_array is None or probabilities_array.size == 0:
174
- logger.warning("Probabilities array is None or empty after extracting from Gradio API output. Returning default scores.")
175
- return {class_names[0]: 0.0, class_names[1]: 1.0}
176
-
177
- # It should always be a single element array for fake probability
178
- fake_prob = float(probabilities_array.item())
179
- real_prob = 1.0 - fake_prob
180
-
181
- return {class_names[0]: fake_prob, class_names[1]: real_prob}
182
-
183
  def register_model_with_metadata(model_id, model, preprocess, postprocess, class_names, display_name, contributor, model_path, architecture=None, dataset=None):
184
  entry = ModelEntry(model, preprocess, postprocess, class_names, display_name=display_name, contributor=contributor, model_path=model_path, architecture=architecture, dataset=dataset)
185
  MODEL_REGISTRY[model_id] = entry
186
 
 
 
 
 
 
 
 
 
 
187
 
188
- def load_onnx_model_and_preprocessor(hf_model_id):
189
- # model_dir = snapshot_download(repo_id=hf_model_id, local_dir_use_symlinks=False)
190
-
191
- # Create a unique local directory for each ONNX model
192
- model_specific_dir = os.path.join("./models", hf_model_id.replace('/', '_'))
193
- os.makedirs(model_specific_dir, exist_ok=True)
194
-
195
- # Use hf_hub_download to get specific files into the model-specific directory
196
- onnx_model_path = hf_hub_download(repo_id=hf_model_id, filename="model_quantized.onnx", subfolder="onnx", local_dir=model_specific_dir, local_dir_use_symlinks=False)
197
-
198
- # Load preprocessor config
199
- preprocessor_config = {}
200
- try:
201
- preprocessor_config_path = hf_hub_download(repo_id=hf_model_id, filename="preprocessor_config.json", local_dir=model_specific_dir, local_dir_use_symlinks=False)
202
- with open(preprocessor_config_path, 'r') as f:
203
- preprocessor_config = json.load(f)
204
- except Exception as e:
205
- logger.warning(f"Could not download or load preprocessor_config.json for {hf_model_id}: {e}")
206
-
207
- # Load model config for class names if available
208
- model_config = {}
209
- try:
210
- model_config_path = hf_hub_download(repo_id=hf_model_id, filename="config.json", local_dir=model_specific_dir, local_dir_use_symlinks=False)
211
- with open(model_config_path, 'r') as f:
212
- model_config = json.load(f)
213
- except Exception as e:
214
- logger.warning(f"Could not download or load config.json for {hf_model_id}: {e}")
215
-
216
- return onnxruntime.InferenceSession(onnx_model_path), preprocessor_config, model_config
217
-
218
-
219
- # Cache for ONNX sessions and preprocessors
220
- _onnx_model_cache = {}
221
-
222
- def get_onnx_model_from_cache(hf_model_id):
223
- if hf_model_id not in _onnx_model_cache:
224
- logger.info(f"Loading ONNX model and preprocessor for {hf_model_id}...")
225
- _onnx_model_cache[hf_model_id] = load_onnx_model_and_preprocessor(hf_model_id)
226
- return _onnx_model_cache[hf_model_id]
227
 
228
- def preprocess_onnx_input(image: Image.Image, preprocessor_config: dict):
229
- # Preprocess image for ONNX model based on preprocessor_config
230
  if image.mode != 'RGB':
231
  image = image.convert('RGB')
232
 
233
- # Get image size and normalization values from preprocessor_config or use defaults
234
- # Use 'size' for initial resize and 'crop_size' for center cropping
235
- initial_resize_size = preprocessor_config.get('size', {'height': 224, 'width': 224})
236
- crop_size = preprocessor_config.get('crop_size', initial_resize_size['height'])
237
- mean = preprocessor_config.get('image_mean', [0.485, 0.456, 0.406])
238
- std = preprocessor_config.get('image_std', [0.229, 0.224, 0.225])
239
-
240
  transform = transforms.Compose([
241
- transforms.Resize((initial_resize_size['height'], initial_resize_size['width'])),
242
- transforms.CenterCrop(crop_size), # Apply center crop
243
  transforms.ToTensor(),
244
- transforms.Normalize(mean=mean, std=std),
245
  ])
246
  input_tensor = transform(image)
247
  # ONNX expects numpy array with batch dimension (1, C, H, W)
248
  return input_tensor.unsqueeze(0).cpu().numpy()
249
 
250
- def infer_onnx_model(hf_model_id, preprocessed_image_np, model_config: dict):
251
  try:
252
- ort_session, _, _ = get_onnx_model_from_cache(hf_model_id)
253
-
254
- # Debug: Print expected input shape from ONNX model
255
- for input_meta in ort_session.get_inputs():
256
- logger.info(f"Debug: ONNX model expected input name: {input_meta.name}, shape: {input_meta.shape}, type: {input_meta.type}")
257
 
258
- logger.info(f"Debug: preprocessed_image_np shape: {preprocessed_image_np.shape}")
259
  ort_inputs = {ort_session.get_inputs()[0].name: preprocessed_image_np}
260
  ort_outputs = ort_session.run(None, ort_inputs)
261
 
 
262
  logits = ort_outputs[0]
263
- logger.info(f"Debug: logits type: {type(logits)}, shape: {logits.shape}")
264
- # If the model outputs a single logit (e.g., shape (1,)), use .item() to convert to scalar
265
- # Otherwise, assume it's a batch of logits (e.g., shape (1, num_classes)) and take the first element (batch dim)
266
- # The num_classes in config.json can be misleading; rely on actual output shape.
267
-
268
- # Apply softmax to the logits to get probabilities for the classes
269
- # The softmax function in utils/utils.py now ensures a list of floats
270
- probabilities = softmax(logits[0]) # Assuming logits[0] is the relevant output for a single prediction
271
-
272
  return {"logits": logits, "probabilities": probabilities}
273
 
274
  except Exception as e:
275
- logger.error(f"Error during ONNX inference for {hf_model_id}: {e}")
276
  # Return a structure consistent with other model errors
277
  return {"logits": np.array([]), "probabilities": np.array([])}
278
 
279
- def postprocess_onnx_output(onnx_output, model_config):
280
- # Get class names from model_config
281
- # Prioritize id2label, then check num_classes, otherwise default
282
- class_names_map = model_config.get('id2label')
283
- if class_names_map:
284
- class_names = [class_names_map[k] for k in sorted(class_names_map.keys())]
285
- elif model_config.get('num_classes') == 1: # Handle models that output a single value (e.g., probability of 'Fake')
286
- class_names = ['Fake', 'Real'] # Assume first class is 'Fake' and second 'Real'
287
- else:
288
- class_names = {0: 'Fake', 1: 'Real'} # Default to Fake/Real if not found or not 1 class
289
- class_names = [class_names[i] for i in sorted(class_names.keys())]
290
-
291
  probabilities = onnx_output.get("probabilities")
292
-
293
- if probabilities is not None:
294
- if model_config.get('num_classes') == 1 and len(probabilities) == 2: # Special handling for single output models
295
- # The single output is the probability of the 'Fake' class
296
- fake_prob = float(probabilities[0])
297
- real_prob = float(probabilities[1])
298
- return {class_names[0]: fake_prob, class_names[1]: real_prob}
299
- elif len(probabilities) == len(class_names):
300
- return {class_names[i]: float(probabilities[i]) for i in range(len(class_names))}
301
- else:
302
- logger.warning("ONNX post-processing: Probabilities length mismatch with class names.")
303
- return {name: 0.0 for name in class_names}
304
  else:
305
- logger.warning("ONNX post-processing failed: 'probabilities' key not found in output.")
306
  return {name: 0.0 for name in class_names}
307
 
308
  # Register the ONNX quantized model
309
- # Dummy entry for ONNX model to be loaded dynamically
310
- # We will now register a 'wrapper' that handles dynamic loading
311
-
312
- class ONNXModelWrapper:
313
- def __init__(self, hf_model_id):
314
- self.hf_model_id = hf_model_id
315
- self._session = None
316
- self._preprocessor_config = None
317
- self._model_config = None
318
-
319
- def load(self):
320
- if self._session is None:
321
- self._session, self._preprocessor_config, self._model_config = get_onnx_model_from_cache(self.hf_model_id)
322
- logger.info(f"ONNX model {self.hf_model_id} loaded into wrapper.")
323
-
324
- def __call__(self, image_np):
325
- self.load() # Ensure model is loaded on first call
326
- # Pass model_config to infer_onnx_model
327
- return infer_onnx_model(self.hf_model_id, image_np, self._model_config)
328
-
329
- def preprocess(self, image: Image.Image):
330
- self.load()
331
- return preprocess_onnx_input(image, self._preprocessor_config)
332
-
333
- def postprocess(self, onnx_output: dict, class_names_from_registry: list): # class_names_from_registry is ignored
334
- self.load()
335
- return postprocess_onnx_output(onnx_output, self._model_config)
336
-
337
- # Consolidate all model loading and registration
338
- for model_key, hf_model_path in MODEL_PATHS.items():
339
- logger.debug(f"Attempting to register model: {model_key} with path: {hf_model_path}")
340
- model_num = model_key.replace("model_", "").upper()
341
- contributor = "Unknown"
342
- architecture = "Unknown"
343
- dataset = "TBA"
344
-
345
- current_class_names = CLASS_NAMES.get(model_key, [])
346
-
347
- # Logic for ONNX models (1, 2, 3, 5, 6, 7)
348
- if "ONNX" in hf_model_path:
349
- logger.debug(f"Model {model_key} identified as ONNX.")
350
- logger.info(f"Registering ONNX model: {model_key} from {hf_model_path}")
351
- onnx_wrapper_instance = ONNXModelWrapper(hf_model_path)
352
-
353
- # Attempt to derive contributor, architecture, dataset based on model_key
354
- if model_key == "model_1":
355
- contributor = "haywoodsloan"
356
- architecture = "SwinV2"
357
- dataset = "DeepFakeDetection"
358
- elif model_key == "model_2":
359
- contributor = "Heem2"
360
- architecture = "ViT"
361
- dataset = "DeepFakeDetection"
362
- elif model_key == "model_3":
363
- contributor = "Organika"
364
- architecture = "VIT"
365
- dataset = "SDXL"
366
- elif model_key == "model_5":
367
- contributor = "prithivMLmods"
368
- architecture = "VIT"
369
- elif model_key == "model_6":
370
- contributor = "ideepankarsharma2003"
371
- architecture = "SWINv1"
372
- dataset = "SDXL, Midjourney"
373
- elif model_key == "model_7":
374
- contributor = "date3k2"
375
- architecture = "VIT"
376
-
377
- display_name_parts = [model_num]
378
- if architecture and architecture not in ["Unknown"]:
379
- display_name_parts.append(architecture)
380
- if dataset and dataset not in ["TBA"]:
381
- display_name_parts.append(dataset)
382
- display_name = "-".join(display_name_parts)
383
- display_name += "_ONNX" # Always append _ONNX for ONNX models
384
-
385
- register_model_with_metadata(
386
- model_id=model_key,
387
- model=onnx_wrapper_instance, # The callable wrapper for the ONNX model
388
- preprocess=onnx_wrapper_instance.preprocess,
389
- postprocess=onnx_wrapper_instance.postprocess,
390
- class_names=current_class_names, # Initial class names; will be overridden by model_config if available
391
- display_name=display_name,
392
- contributor=contributor,
393
- model_path=hf_model_path,
394
- architecture=architecture,
395
- dataset=dataset
396
- )
397
- # Logic for Gradio API model (model_8)
398
- elif model_key == "model_8":
399
- logger.debug(f"Model {model_key} identified as Gradio API.")
400
- logger.info(f"Registering Gradio API model: {model_key} from {hf_model_path}")
401
- contributor = "aiwithoutborders-xyz"
402
- architecture = "ViT"
403
- dataset = "DeepfakeDetection"
404
-
405
- display_name_parts = [model_num]
406
- if architecture and architecture not in ["Unknown"]:
407
- display_name_parts.append(architecture)
408
- if dataset and dataset not in ["TBA"]:
409
- display_name_parts.append(dataset)
410
- display_name = "-".join(display_name_parts)
411
-
412
- register_model_with_metadata(
413
- model_id=model_key,
414
- model=infer_gradio_api,
415
- preprocess=preprocess_gradio_api,
416
- postprocess=postprocess_gradio_api,
417
- class_names=current_class_names,
418
- display_name=display_name,
419
- contributor=contributor,
420
- model_path=hf_model_path,
421
- architecture=architecture,
422
- dataset=dataset
423
- )
424
- # Logic for PyTorch/Hugging Face pipeline models (currently only model_4)
425
- elif model_key == "model_4": # Explicitly handle model_4
426
- logger.debug(f"Model {model_key} identified as PyTorch/HuggingFace pipeline.")
427
- logger.info(f"Registering HuggingFace pipeline/AutoModel: {model_key} from {hf_model_path}")
428
- contributor = "cmckinle"
429
- architecture = "VIT"
430
- dataset = "SDXL, FLUX"
431
-
432
- display_name_parts = [model_num]
433
- if architecture and architecture not in ["Unknown"]:
434
- display_name_parts.append(architecture)
435
- if dataset and dataset not in ["TBA"]:
436
- display_name_parts.append(dataset)
437
- display_name = "-".join(display_name_parts)
438
-
439
- current_processor = AutoFeatureExtractor.from_pretrained(hf_model_path, device=device)
440
- model_instance = AutoModelForImageClassification.from_pretrained(hf_model_path).to(device)
441
-
442
- preprocess_func = preprocess_resize_256
443
- postprocess_func = postprocess_logits
444
-
445
- def custom_infer(image, processor_local=current_processor, model_local=model_instance):
446
- inputs = processor_local(image, return_tensors="pt").to(device)
447
- with torch.no_grad():
448
- outputs = model_local(**inputs)
449
- return outputs
450
- model_instance = custom_infer
451
-
452
- register_model_with_metadata(
453
- model_id=model_key,
454
- model=model_instance,
455
- preprocess=preprocess_func,
456
- postprocess=postprocess_func,
457
- class_names=current_class_names,
458
- display_name=display_name,
459
- contributor=contributor,
460
- model_path=hf_model_path,
461
- architecture=architecture,
462
- dataset=dataset
463
- )
464
- else: # Fallback for any unhandled models (shouldn't happen if MODEL_PATHS is fully covered)
465
- logger.warning(f"Could not automatically load and register model: {model_key} from {hf_model_path}. No matching registration logic found.")
466
 
 
 
 
 
 
 
467
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  def infer(image: Image.Image, model_id: str, confidence_threshold: float = 0.75) -> dict:
469
  """Predict using a specific model.
470
 
@@ -481,14 +297,8 @@ def infer(image: Image.Image, model_id: str, confidence_threshold: float = 0.75)
481
  try:
482
  result = entry.model(img)
483
  scores = entry.postprocess(result, entry.class_names)
484
-
485
- def _to_float_scalar(value):
486
- if isinstance(value, np.ndarray):
487
- return float(value.item()) # Convert numpy array scalar to Python float
488
- return float(value) # Already a Python scalar or convertible type
489
-
490
- ai_score = _to_float_scalar(scores.get(entry.class_names[0], 0.0))
491
- real_score = _to_float_scalar(scores.get(entry.class_names[1], 0.0))
492
  label = "AI" if ai_score >= confidence_threshold else ("REAL" if real_score >= confidence_threshold else "UNCERTAIN")
493
  return {
494
  "Model": entry.display_name,
@@ -561,97 +371,29 @@ def full_prediction(img, confidence_threshold, rotate_degrees, noise_level, shar
561
  results = []
562
  table_rows = []
563
 
564
- # Initialize lists for forensic outputs, starting with the original augmented image
565
- cleaned_forensics_images = []
566
- forensic_output_descriptions = []
567
-
568
- # Always add the original augmented image first for forensic display
569
- if isinstance(img_pil, Image.Image):
570
- cleaned_forensics_images.append(img_pil)
571
- forensic_output_descriptions.append(f"Original augmented image (PIL): {img_pil.width}x{img_pil.height}")
572
- elif isinstance(img_pil, np.ndarray):
573
- try:
574
- pil_img_from_np = Image.fromarray(img_pil)
575
- cleaned_forensics_images.append(pil_img_from_np)
576
- forensic_output_descriptions.append(f"Original augmented image (numpy converted to PIL): {pil_img_from_np.width}x{pil_img_from_np.height}")
577
- except Exception as e:
578
- logger.warning(f"Could not convert original numpy image to PIL for gallery: {e}")
579
-
580
- # Yield initial state with augmented image and empty model predictions
581
- yield img_pil, cleaned_forensics_images, table_rows, "[]", "<div style='font-size: 2.2em; font-weight: bold;padding: 10px;'>Consensus: <span style='color:orange'>UNCERTAIN</span></div>"
582
-
583
-
584
  # Stream results as each model finishes
585
  for model_id in MODEL_REGISTRY:
586
  model_start = time.time()
587
  result = infer(img_pil, model_id, confidence_threshold)
588
  model_end = time.time()
589
-
590
- # Helper to ensure values are Python floats, handling numpy scalars
591
- def _ensure_float_scalar(value):
592
- if isinstance(value, np.ndarray):
593
- return float(value.item()) # Convert numpy array scalar to Python float
594
- return float(value) # Already a Python scalar or convertible type
595
-
596
- ai_score_val = _ensure_float_scalar(result.get("AI Score", 0.0))
597
- real_score_val = _ensure_float_val = _ensure_float_scalar(result.get("Real Score", 0.0))
598
-
599
  monitor_agent.monitor_prediction(
600
  model_id,
601
  result["Label"],
602
- max(ai_score_val, real_score_val),
603
  model_end - model_start
604
  )
605
  model_predictions_raw[model_id] = result
606
- confidence_scores[model_id] = max(ai_score_val, real_score_val)
607
  results.append(result)
608
  table_rows.append([
609
  result.get("Model", ""),
610
  result.get("Contributor", ""),
611
- round(ai_score_val, 5),
612
- round(real_score_val, 5),
613
  result.get("Label", "Error")
614
  ])
615
  # Yield partial results: only update the table, others are None
616
- yield None, cleaned_forensics_images, table_rows, None, None # Keep cleaned_forensics_images as is (only augmented image for now)
617
-
618
- # Multi-threaded forensic processing
619
- def _run_forensic_task(task_func, img_input, description, **kwargs):
620
- try:
621
- result_img = task_func(img_input, **kwargs)
622
- return result_img, description
623
- except Exception as e:
624
- logger.error(f"Error processing forensic task {task_func.__name__}: {e}")
625
- return None, f"Error processing {description}: {str(e)}"
626
-
627
- with concurrent.futures.ThreadPoolExecutor() as executor:
628
- future_ela1 = executor.submit(_run_forensic_task, ELA, img_np_og, "ELA analysis (Pass 1): Grayscale error map, quality 75.", quality=75, scale=50, contrast=20, linear=False, grayscale=True)
629
- future_ela2 = executor.submit(_run_forensic_task, ELA, img_np_og, "ELA analysis (Pass 2): Grayscale error map, quality 75, enhanced contrast.", quality=75, scale=75, contrast=25, linear=False, grayscale=True)
630
- future_ela3 = executor.submit(_run_forensic_task, ELA, img_np_og, "ELA analysis (Pass 3): Color error map, quality 75, enhanced contrast.", quality=75, scale=75, contrast=25, linear=False, grayscale=False)
631
- future_gradient1 = executor.submit(_run_forensic_task, gradient_processing, img_np_og, "Gradient processing: Highlights edges and transitions.")
632
- future_gradient2 = executor.submit(_run_forensic_task, gradient_processing, img_np_og, "Gradient processing: Int=45, Equalize=True", intensity=45, equalize=True)
633
- future_minmax1 = executor.submit(_run_forensic_task, minmax_process, img_np_og, "MinMax processing: Deviations in local pixel values.")
634
- future_minmax2 = executor.submit(_run_forensic_task, minmax_process, img_np_og, "MinMax processing (Radius=6): Deviations in local pixel values.", radius=6)
635
-
636
- forensic_futures = [future_ela1, future_ela2, future_ela3, future_gradient1, future_gradient2, future_minmax1, future_minmax2]
637
-
638
- for future in concurrent.futures.as_completed(forensic_futures):
639
- processed_img, description = future.result()
640
- if processed_img is not None:
641
- if isinstance(processed_img, Image.Image):
642
- cleaned_forensics_images.append(processed_img)
643
- elif isinstance(processed_img, np.ndarray):
644
- try:
645
- cleaned_forensics_images.append(Image.fromarray(processed_img))
646
- except Exception as e:
647
- logger.warning(f"Could not convert numpy array to PIL Image for gallery: {e}")
648
- else:
649
- logger.warning(f"Unexpected type in processed_img from {description}: {type(processed_img)}. Skipping.")
650
-
651
- forensic_output_descriptions.append(description) # Keep track of descriptions for anomaly agent
652
-
653
- # Yield partial results: update gallery
654
- yield None, cleaned_forensics_images, table_rows, None, None
655
 
656
  # After all models, compute the rest as before
657
  image_data_for_context = {
@@ -659,17 +401,6 @@ def full_prediction(img, confidence_threshold, rotate_degrees, noise_level, shar
659
  "height": img.height,
660
  "mode": img.mode,
661
  }
662
- forensic_output_descriptions = [
663
- f"Original augmented image (PIL): {img_pil.width}x{img_pil.height}",
664
- "ELA analysis (Pass 1): Grayscale error map, quality 75.",
665
- "ELA analysis (Pass 2): Grayscale error map, quality 75, enhanced contrast.",
666
- "ELA analysis (Pass 3): Color error map, quality 75, enhanced contrast.",
667
- "Gradient processing: Highlights edges and transitions.",
668
- "Gradient processing: Int=45, Equalize=True",
669
- "MinMax processing: Deviations in local pixel values.",
670
- "MinMax processing (Radius=6): Deviations in local pixel values.",
671
- # "Bit Plane extractor: Visualization of individual bit planes from different color channels."
672
- ]
673
  detected_context_tags = context_agent.infer_context_tags(image_data_for_context, model_predictions_raw)
674
  logger.info(f"Detected context tags: {detected_context_tags}")
675
  adjusted_weights = weight_manager.adjust_weights(model_predictions_raw, confidence_scores, context_tags=detected_context_tags)
@@ -686,26 +417,26 @@ def full_prediction(img, confidence_threshold, rotate_degrees, noise_level, shar
686
  elif weighted_predictions["REAL"] > weighted_predictions["AI"] and weighted_predictions["REAL"] > weighted_predictions["UNCERTAIN"]:
687
  final_prediction_label = "REAL"
688
  optimization_agent.analyze_performance(final_prediction_label, None)
689
- # gradient_image = gradient_processing(img_np_og)
690
- # gradient_image2 = gradient_processing(img_np_og, intensity=45, equalize=True)
691
- # minmax_image = minmax_process(img_np_og)
692
- # minmax_image2 = minmax_process(img_np_og, radius=6)
693
- # # bitplane_image = bit_plane_extractor(img_pil)
694
- # ela1 = ELA(img_np_og, quality=75, scale=50, contrast=20, linear=False, grayscale=True)
695
- # ela2 = ELA(img_np_og, quality=75, scale=75, contrast=25, linear=False, grayscale=True)
696
- # ela3 = ELA(img_np_og, quality=75, scale=75, contrast=25, linear=False, grayscale=False)
697
- # forensics_images = [img_pil, ela1, ela2, ela3, gradient_image, gradient_image2, minmax_image, minmax_image2]
698
- # forensic_output_descriptions = [
699
- # f"Original augmented image (PIL): {img_pil.width}x{img_pil.height}",
700
- # "ELA analysis (Pass 1): Grayscale error map, quality 75.",
701
- # "ELA analysis (Pass 2): Grayscale error map, quality 75, enhanced contrast.",
702
- # "ELA analysis (Pass 3): Color error map, quality 75, enhanced contrast.",
703
- # "Gradient processing: Highlights edges and transitions.",
704
- # "Gradient processing: Int=45, Equalize=True",
705
- # "MinMax processing: Deviations in local pixel values.",
706
- # "MinMax processing (Radius=6): Deviations in local pixel values.",
707
- # # "Bit Plane extractor: Visualization of individual bit planes from different color channels."
708
- # ]
709
  anomaly_detection_results = anomaly_agent.analyze_forensic_outputs(forensic_output_descriptions)
710
  logger.info(f"Forensic anomaly detection: {anomaly_detection_results['summary']}")
711
  consensus_html = f"<div style='font-size: 2.2em; font-weight: bold;padding: 10px;'>Consensus: <span style='color:{'red' if final_prediction_label == 'AI' else ('green' if final_prediction_label == 'REAL' else 'orange')}'>{final_prediction_label}</span></div>"
@@ -743,11 +474,21 @@ def full_prediction(img, confidence_threshold, rotate_degrees, noise_level, shar
743
  inference_params=inference_params,
744
  model_predictions=results,
745
  ensemble_output=ensemble_output_data,
746
- forensic_images=cleaned_forensics_images, # Use the incrementally built list
747
  agent_monitoring_data=agent_monitoring_data_log,
748
  human_feedback=None
749
  )
750
-
 
 
 
 
 
 
 
 
 
 
751
  logger.info(f"Cleaned forensic images types: {[type(img) for img in cleaned_forensics_images]}")
752
  for i, res_dict in enumerate(results):
753
  for key in ["AI Score", "Real Score"]:
@@ -968,7 +709,7 @@ demo = gr.TabbedInterface(
968
 
969
  )
970
  footerMD = """
971
- ## ⚠️ ENSEMBLE TEAM IN TRAINING ⚠️ \n\n
972
 
973
  1. **DISCLAIMER: METADATA AS WELL AS MEDIA SUBMITTED TO THIS SPACE MAY BE VIEWED AND SELECTED FOR FUTURE DATASETS, PLEASE DO NOT SUBMIT PERSONAL CONTENT. FOR UNTRACKED, PRIVATE USE OF THE MODELS YOU MAY STILL USE [THE ORIGINAL SPACE HERE](https://huggingface.co/spaces/aiwithoutborders-xyz/OpenSight-Deepfake-Detection-Models-Playground), SOTA MODEL INCLUDED.**
974
  2. **UPDATE 6-13-25**: APOLOGIES FOR THE CONFUSION, WE ARE WORKING TO REVERT THE ORIGINAL REPO BACK TO ITS NON-DATA COLLECTION STATE -- ONLY THE "SIMPLE PREDICTION" ENDPOINT IS CURRENTLY 100% PRIVATE. PLEASE STAY TUNED AS WE FIGURE OUT A SOLUTION FOR THE ENSEMBLE + AGENT TEAM ENDPOINT. IT CAN GET RESOURCE INTENSIVE TO RUN A FULL PREDICTION. ALTERNATIVELY, WE **ENCOURAGE** ANYONE TO FORK AND CONTRIBUTE TO THE PROJECT.
@@ -977,7 +718,7 @@ footerMD = """
977
  TO SUMMARIZE: DATASET COLLECTION WILL CONTINUE FOR OUR NOVEL ENSEMBLE-TEAM PREDICTION PIPELINE UNTIL WE CAN GET THINGS SORTED OUT. FOR THOSE THAT WISH TO OPT-OUT, WE OFFER THE SIMPLE, BUT [MOST POWERFUL DETECTION MODEL HERE.](https://huggingface.co/spaces/aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview)
978
 
979
  """
980
- footer = gr.Markdown(footerMD, elem_classes="footer")
981
 
982
  with gr.Blocks() as app:
983
  demo.render()
 
8
  import io
9
  import collections
10
  import onnxruntime
 
 
 
 
 
 
11
 
12
  from utils.utils import softmax, augment_image
13
  from forensics.gradient import gradient_processing
 
23
  from agents.ensemble_weights import ModelWeightManager
24
  from transformers import pipeline, AutoImageProcessor, SwinForImageClassification, Swinv2ForImageClassification, AutoFeatureExtractor, AutoModelForImageClassification
25
  from torchvision import transforms
26
+ import torch
27
+ import json
28
+ from huggingface_hub import CommitScheduler
29
+ from dotenv import load_dotenv
30
 
31
  logging.basicConfig(level=logging.INFO)
32
  logger = logging.getLogger(__name__)
 
66
 
67
  # Model paths and class names (copied from app_mcp.py)
68
  MODEL_PATHS = {
69
+ "model_1": "haywoodsloan/ai-image-detector-deploy",
70
+ "model_2": "Heem2/AI-vs-Real-Image-Detection",
71
+ "model_3": "Organika/sdxl-detector",
72
  "model_4": "cmckinle/sdxl-flux-detector_v1.1",
73
+ "model_5": "prithivMLmods/Deep-Fake-Detector-v2-Model",
74
+ "model_6": "ideepankarsharma2003/AI_ImageClassification_MidjourneyV6_SDXL",
75
+ "model_7": "date3k2/vit-real-fake-classification-v4"
 
76
  }
77
 
78
  CLASS_NAMES = {
79
  "model_1": ['artificial', 'real'],
80
  "model_2": ['AI Image', 'Real Image'],
81
+ "model_3": ['AI', 'Real'],
82
  "model_4": ['AI', 'Real'],
83
  "model_5": ['Realism', 'Deepfake'],
84
  "model_6": ['ai_gen', 'human'],
85
  "model_7": ['Fake', 'Real'],
 
86
  }
87
 
88
  def preprocess_resize_256(image):
 
97
 
98
  def postprocess_pipeline(prediction, class_names):
99
  # Assumes HuggingFace pipeline output
100
+ return {pred['label']: pred['score'] for pred in prediction}
101
 
102
  def postprocess_logits(outputs, class_names):
103
  # Assumes model output with logits
 
105
  probabilities = softmax(logits)
106
  return {class_names[i]: probabilities[i] for i in range(len(class_names))}
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  def register_model_with_metadata(model_id, model, preprocess, postprocess, class_names, display_name, contributor, model_path, architecture=None, dataset=None):
109
  entry = ModelEntry(model, preprocess, postprocess, class_names, display_name=display_name, contributor=contributor, model_path=model_path, architecture=architecture, dataset=dataset)
110
  MODEL_REGISTRY[model_id] = entry
111
 
112
+ # Load and register models (copied from app_mcp.py)
113
+ # image_processor_1 = AutoImageProcessor.from_pretrained(MODEL_PATHS["model_1"], use_fast=True)
114
+ # model_1 = Swinv2ForImageClassification.from_pretrained(MODEL_PATHS["model_1"]).to(device)
115
+ # clf_1 = pipeline(model=model_1, task="image-classification", image_processor=image_processor_1, device=device)
116
+ # register_model_with_metadata(
117
+ # "model_1", clf_1, preprocess_resize_256, postprocess_pipeline, CLASS_NAMES["model_1"],
118
+ # display_name="SWIN1", contributor="haywoodsloan", model_path=MODEL_PATHS["model_1"],
119
+ # architecture="SwinV2", dataset="TBA"
120
+ # )
121
 
122
+ # --- ONNX Quantized Model Example ---
123
+ ONNX_QUANTIZED_MODEL_PATH = "./models/model_1_quantized.onnx"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
+ def preprocess_onnx_input(image: Image.Image):
126
+ # Preprocess image for ONNX model (e.g., for SwinV2, usually 256x256, normalized)
127
  if image.mode != 'RGB':
128
  image = image.convert('RGB')
129
 
 
 
 
 
 
 
 
130
  transform = transforms.Compose([
131
+ transforms.Resize((256, 256)),
 
132
  transforms.ToTensor(),
133
+ transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]), # ImageNet normalization
134
  ])
135
  input_tensor = transform(image)
136
  # ONNX expects numpy array with batch dimension (1, C, H, W)
137
  return input_tensor.unsqueeze(0).cpu().numpy()
138
 
139
+ def infer_onnx_model(preprocessed_image_np):
140
  try:
141
+ # Ensure the ONNX model exists before trying to load it
142
+ if not os.path.exists(ONNX_QUANTIZED_MODEL_PATH):
143
+ logger.error(f"ONNX quantized model not found at: {ONNX_QUANTIZED_MODEL_PATH}")
144
+ raise FileNotFoundError(f"ONNX quantized model not found at: {ONNX_QUANTIZED_MODEL_PATH}")
 
145
 
146
+ ort_session = onnxruntime.InferenceSession(ONNX_QUANTIZED_MODEL_PATH)
147
  ort_inputs = {ort_session.get_inputs()[0].name: preprocessed_image_np}
148
  ort_outputs = ort_session.run(None, ort_inputs)
149
 
150
+ # Assuming the output is logits, apply softmax to get probabilities
151
  logits = ort_outputs[0]
152
+ probabilities = softmax(logits[0]) # Remove batch dim, apply softmax
 
 
 
 
 
 
 
 
153
  return {"logits": logits, "probabilities": probabilities}
154
 
155
  except Exception as e:
156
+ logger.error(f"Error during ONNX inference: {e}")
157
  # Return a structure consistent with other model errors
158
  return {"logits": np.array([]), "probabilities": np.array([])}
159
 
160
+ def postprocess_onnx_output(onnx_output, class_names):
 
 
 
 
 
 
 
 
 
 
 
161
  probabilities = onnx_output.get("probabilities")
162
+ if probabilities is not None and len(probabilities) == len(class_names):
163
+ return {class_names[i]: probabilities[i] for i in range(len(class_names))}
 
 
 
 
 
 
 
 
 
 
164
  else:
165
+ logger.warning("ONNX post-processing failed or class names mismatch.")
166
  return {name: 0.0 for name in class_names}
167
 
168
  # Register the ONNX quantized model
169
+ register_model_with_metadata(
170
+ "model_1_onnx_quantized",
171
+ infer_onnx_model,
172
+ preprocess_onnx_input,
173
+ postprocess_onnx_output,
174
+ CLASS_NAMES["model_1"], # Assuming it uses the same class names as model_1
175
+ display_name="SWIN1",
176
+ contributor="haywoodsloan",
177
+ model_path=ONNX_QUANTIZED_MODEL_PATH,
178
+ architecture="SwinV2",
179
+ dataset="TBA"
180
+ )
181
+ # --- End ONNX Quantized Model Example ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
+ clf_2 = pipeline("image-classification", model=MODEL_PATHS["model_2"], device=device)
184
+ register_model_with_metadata(
185
+ "model_2", clf_2, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_2"],
186
+ display_name="VIT2", contributor="Heem2", model_path=MODEL_PATHS["model_2"],
187
+ architecture="ViT", dataset="TBA"
188
+ )
189
 
190
+ feature_extractor_3 = AutoFeatureExtractor.from_pretrained(MODEL_PATHS["model_3"], device=device)
191
+ model_3 = AutoModelForImageClassification.from_pretrained(MODEL_PATHS["model_3"]).to(device)
192
+ def preprocess_256(image):
193
+ if image.mode != 'RGB':
194
+ image = image.convert('RGB')
195
+ return transforms.Resize((256, 256))(image)
196
+ def postprocess_logits_model3(outputs, class_names):
197
+ logits = outputs.logits.cpu().numpy()[0]
198
+ probabilities = softmax(logits)
199
+ return {class_names[i]: probabilities[i] for i in range(len(class_names))}
200
+ def model3_infer(image):
201
+ inputs = feature_extractor_3(image, return_tensors="pt").to(device)
202
+ with torch.no_grad():
203
+ outputs = model_3(**inputs)
204
+ return outputs
205
+ register_model_with_metadata(
206
+ "model_3", model3_infer, preprocess_256, postprocess_logits_model3, CLASS_NAMES["model_3"],
207
+ display_name="SDXL3", contributor="Organika", model_path=MODEL_PATHS["model_3"],
208
+ architecture="VIT", dataset="SDXL"
209
+ )
210
+
211
+ feature_extractor_4 = AutoFeatureExtractor.from_pretrained(MODEL_PATHS["model_4"], device=device)
212
+ model_4 = AutoModelForImageClassification.from_pretrained(MODEL_PATHS["model_4"]).to(device)
213
+ def model4_infer(image):
214
+ inputs = feature_extractor_4(image, return_tensors="pt").to(device)
215
+ with torch.no_grad():
216
+ outputs = model_4(**inputs)
217
+ return outputs
218
+ def postprocess_logits_model4(outputs, class_names):
219
+ logits = outputs.logits.cpu().numpy()[0]
220
+ probabilities = softmax(logits)
221
+ return {class_names[i]: probabilities[i] for i in range(len(class_names))}
222
+ register_model_with_metadata(
223
+ "model_4", model4_infer, preprocess_256, postprocess_logits_model4, CLASS_NAMES["model_4"],
224
+ display_name="XLFLUX4", contributor="cmckinle", model_path=MODEL_PATHS["model_4"],
225
+ architecture="VIT", dataset="SDXL, FLUX"
226
+ )
227
+
228
+ clf_5 = pipeline("image-classification", model=MODEL_PATHS["model_5"], device=device)
229
+ register_model_with_metadata(
230
+ "model_5", clf_5, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_5"],
231
+ display_name="VIT5", contributor="prithivMLmods", model_path=MODEL_PATHS["model_5"],
232
+ architecture="VIT", dataset="TBA"
233
+ )
234
+
235
+ image_processor_6 = AutoImageProcessor.from_pretrained(MODEL_PATHS["model_6"], use_fast=True)
236
+ model_6 = SwinForImageClassification.from_pretrained(MODEL_PATHS["model_6"]).to(device)
237
+ clf_6 = pipeline(model=model_6, task="image-classification", image_processor=image_processor_6, device=device)
238
+ register_model_with_metadata(
239
+ "model_6", clf_6, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_6"],
240
+ display_name="SWIN6", contributor="ideepankarsharma2003", model_path=MODEL_PATHS["model_6"],
241
+ architecture="SWINv1", dataset="SDXL, Midjourney"
242
+ )
243
+
244
+ image_processor_7 = AutoImageProcessor.from_pretrained(MODEL_PATHS["model_7"], use_fast=True)
245
+ model_7 = AutoModelForImageClassification.from_pretrained(MODEL_PATHS["model_7"]).to(device)
246
+ clf_7 = pipeline(model=model_7, task="image-classification", image_processor=image_processor_7, device=device)
247
+ register_model_with_metadata(
248
+ "model_7", clf_7, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_7"],
249
+ display_name="VIT7", contributor="date3k2", model_path=MODEL_PATHS["model_7"],
250
+ architecture="VIT", dataset="TBA"
251
+ )
252
+
253
+ # def postprocess_simple_prediction(result, class_names):
254
+ # scores = {name: 0.0 for name in class_names}
255
+ # fake_prob = result.get("Fake Probability")
256
+ # if fake_prob is not None:
257
+ # # Assume class_names = ["AI", "REAL"]
258
+ # scores["AI"] = float(fake_prob)
259
+ # scores["REAL"] = 1.0 - float(fake_prob)
260
+ # return scores
261
+
262
+ # def simple_prediction(img):
263
+ # client = Client("aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview")
264
+ # client.view_api()
265
+ # print(type(img))
266
+ # result = client.predict(
267
+ # handle_file(img),
268
+ # api_name="simple_predict"
269
+ # )
270
+ # return result
271
+
272
+
273
+ # register_model_with_metadata(
274
+ # model_id="simple_prediction",
275
+ # model=simple_prediction,
276
+ # preprocess=None,
277
+ # postprocess=postprocess_simple_prediction,
278
+ # class_names=["AI", "REAL"],
279
+ # display_name="Community Forensics",
280
+ # contributor="Jeongsoo Park",
281
+ # model_path="aiwithoutborders-xyz/CommunityForensics-DeepfakeDet-ViT",
282
+ # architecture="ViT", dataset="GOAT"
283
+ # )
284
  def infer(image: Image.Image, model_id: str, confidence_threshold: float = 0.75) -> dict:
285
  """Predict using a specific model.
286
 
 
297
  try:
298
  result = entry.model(img)
299
  scores = entry.postprocess(result, entry.class_names)
300
+ ai_score = float(scores.get(entry.class_names[0], 0.0))
301
+ real_score = float(scores.get(entry.class_names[1], 0.0))
 
 
 
 
 
 
302
  label = "AI" if ai_score >= confidence_threshold else ("REAL" if real_score >= confidence_threshold else "UNCERTAIN")
303
  return {
304
  "Model": entry.display_name,
 
371
  results = []
372
  table_rows = []
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  # Stream results as each model finishes
375
  for model_id in MODEL_REGISTRY:
376
  model_start = time.time()
377
  result = infer(img_pil, model_id, confidence_threshold)
378
  model_end = time.time()
 
 
 
 
 
 
 
 
 
 
379
  monitor_agent.monitor_prediction(
380
  model_id,
381
  result["Label"],
382
+ max(result.get("AI Score", 0.0), result.get("Real Score", 0.0)),
383
  model_end - model_start
384
  )
385
  model_predictions_raw[model_id] = result
386
+ confidence_scores[model_id] = max(result.get("AI Score", 0.0), result.get("Real Score", 0.0))
387
  results.append(result)
388
  table_rows.append([
389
  result.get("Model", ""),
390
  result.get("Contributor", ""),
391
+ round(result.get("AI Score", 0.0), 3) if result.get("AI Score") is not None else 0.0,
392
+ round(result.get("Real Score", 0.0), 3) if result.get("Real Score") is not None else 0.0,
393
  result.get("Label", "Error")
394
  ])
395
  # Yield partial results: only update the table, others are None
396
+ yield None, None, table_rows, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
  # After all models, compute the rest as before
399
  image_data_for_context = {
 
401
  "height": img.height,
402
  "mode": img.mode,
403
  }
 
 
 
 
 
 
 
 
 
 
 
404
  detected_context_tags = context_agent.infer_context_tags(image_data_for_context, model_predictions_raw)
405
  logger.info(f"Detected context tags: {detected_context_tags}")
406
  adjusted_weights = weight_manager.adjust_weights(model_predictions_raw, confidence_scores, context_tags=detected_context_tags)
 
417
  elif weighted_predictions["REAL"] > weighted_predictions["AI"] and weighted_predictions["REAL"] > weighted_predictions["UNCERTAIN"]:
418
  final_prediction_label = "REAL"
419
  optimization_agent.analyze_performance(final_prediction_label, None)
420
+ gradient_image = gradient_processing(img_np_og)
421
+ gradient_image2 = gradient_processing(img_np_og, intensity=45, equalize=True)
422
+ minmax_image = minmax_process(img_np_og)
423
+ minmax_image2 = minmax_process(img_np_og, radius=6)
424
+ # bitplane_image = bit_plane_extractor(img_pil)
425
+ ela1 = ELA(img_np_og, quality=75, scale=50, contrast=20, linear=False, grayscale=True)
426
+ ela2 = ELA(img_np_og, quality=75, scale=75, contrast=25, linear=False, grayscale=True)
427
+ ela3 = ELA(img_np_og, quality=75, scale=75, contrast=25, linear=False, grayscale=False)
428
+ forensics_images = [img_pil, ela1, ela2, ela3, gradient_image, gradient_image2, minmax_image, minmax_image2]
429
+ forensic_output_descriptions = [
430
+ f"Original augmented image (PIL): {img_pil.width}x{img_pil.height}",
431
+ "ELA analysis (Pass 1): Grayscale error map, quality 75.",
432
+ "ELA analysis (Pass 2): Grayscale error map, quality 75, enhanced contrast.",
433
+ "ELA analysis (Pass 3): Color error map, quality 75, enhanced contrast.",
434
+ "Gradient processing: Highlights edges and transitions.",
435
+ "Gradient processing: Int=45, Equalize=True",
436
+ "MinMax processing: Deviations in local pixel values.",
437
+ "MinMax processing (Radius=6): Deviations in local pixel values.",
438
+ # "Bit Plane extractor: Visualization of individual bit planes from different color channels."
439
+ ]
440
  anomaly_detection_results = anomaly_agent.analyze_forensic_outputs(forensic_output_descriptions)
441
  logger.info(f"Forensic anomaly detection: {anomaly_detection_results['summary']}")
442
  consensus_html = f"<div style='font-size: 2.2em; font-weight: bold;padding: 10px;'>Consensus: <span style='color:{'red' if final_prediction_label == 'AI' else ('green' if final_prediction_label == 'REAL' else 'orange')}'>{final_prediction_label}</span></div>"
 
474
  inference_params=inference_params,
475
  model_predictions=results,
476
  ensemble_output=ensemble_output_data,
477
+ forensic_images=forensics_images,
478
  agent_monitoring_data=agent_monitoring_data_log,
479
  human_feedback=None
480
  )
481
+ cleaned_forensics_images = []
482
+ for f_img in forensics_images:
483
+ if isinstance(f_img, Image.Image):
484
+ cleaned_forensics_images.append(f_img)
485
+ elif isinstance(f_img, np.ndarray):
486
+ try:
487
+ cleaned_forensics_images.append(Image.fromarray(f_img))
488
+ except Exception as e:
489
+ logger.warning(f"Could not convert numpy array to PIL Image for gallery: {e}")
490
+ else:
491
+ logger.warning(f"Unexpected type in forensic_images: {type(f_img)}. Skipping.")
492
  logger.info(f"Cleaned forensic images types: {[type(img) for img in cleaned_forensics_images]}")
493
  for i, res_dict in enumerate(results):
494
  for key in ["AI Score", "Real Score"]:
 
709
 
710
  )
711
  footerMD = """
712
+ ### ⚠️ ENSEMBLE TEAM IN TRAINING ⚠️ \n\n
713
 
714
  1. **DISCLAIMER: METADATA AS WELL AS MEDIA SUBMITTED TO THIS SPACE MAY BE VIEWED AND SELECTED FOR FUTURE DATASETS, PLEASE DO NOT SUBMIT PERSONAL CONTENT. FOR UNTRACKED, PRIVATE USE OF THE MODELS YOU MAY STILL USE [THE ORIGINAL SPACE HERE](https://huggingface.co/spaces/aiwithoutborders-xyz/OpenSight-Deepfake-Detection-Models-Playground), SOTA MODEL INCLUDED.**
715
  2. **UPDATE 6-13-25**: APOLOGIES FOR THE CONFUSION, WE ARE WORKING TO REVERT THE ORIGINAL REPO BACK TO ITS NON-DATA COLLECTION STATE -- ONLY THE "SIMPLE PREDICTION" ENDPOINT IS CURRENTLY 100% PRIVATE. PLEASE STAY TUNED AS WE FIGURE OUT A SOLUTION FOR THE ENSEMBLE + AGENT TEAM ENDPOINT. IT CAN GET RESOURCE INTENSIVE TO RUN A FULL PREDICTION. ALTERNATIVELY, WE **ENCOURAGE** ANYONE TO FORK AND CONTRIBUTE TO THE PROJECT.
 
718
  TO SUMMARIZE: DATASET COLLECTION WILL CONTINUE FOR OUR NOVEL ENSEMBLE-TEAM PREDICTION PIPELINE UNTIL WE CAN GET THINGS SORTED OUT. FOR THOSE THAT WISH TO OPT-OUT, WE OFFER THE SIMPLE, BUT [MOST POWERFUL DETECTION MODEL HERE.](https://huggingface.co/spaces/aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview)
719
 
720
  """
721
+ footer = gr.Markdown("", elem_classes="footer")
722
 
723
  with gr.Blocks() as app:
724
  demo.render()
app.py CHANGED
@@ -8,8 +8,14 @@ import logging
8
  import io
9
  import collections
10
  import onnxruntime
 
 
 
 
 
 
11
 
12
- from utils.utils import softmax, augment_image
13
  from forensics.gradient import gradient_processing
14
  from forensics.minmax import minmax_process
15
  from forensics.ela import ELA
@@ -23,10 +29,6 @@ from utils.registry import register_model, MODEL_REGISTRY, ModelEntry
23
  from agents.ensemble_weights import ModelWeightManager
24
  from transformers import pipeline, AutoImageProcessor, SwinForImageClassification, Swinv2ForImageClassification, AutoFeatureExtractor, AutoModelForImageClassification
25
  from torchvision import transforms
26
- import torch
27
- import json
28
- from huggingface_hub import CommitScheduler
29
- from dotenv import load_dotenv
30
 
31
  logging.basicConfig(level=logging.INFO)
32
  logger = logging.getLogger(__name__)
@@ -66,221 +68,356 @@ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
66
 
67
  # Model paths and class names (copied from app_mcp.py)
68
  MODEL_PATHS = {
69
- "model_1": "haywoodsloan/ai-image-detector-deploy",
70
- "model_2": "Heem2/AI-vs-Real-Image-Detection",
71
- "model_3": "Organika/sdxl-detector",
72
  "model_4": "cmckinle/sdxl-flux-detector_v1.1",
73
- "model_5": "prithivMLmods/Deep-Fake-Detector-v2-Model",
74
- "model_6": "ideepankarsharma2003/AI_ImageClassification_MidjourneyV6_SDXL",
75
- "model_7": "date3k2/vit-real-fake-classification-v4"
 
76
  }
77
 
78
  CLASS_NAMES = {
79
  "model_1": ['artificial', 'real'],
80
  "model_2": ['AI Image', 'Real Image'],
81
- "model_3": ['AI', 'Real'],
82
  "model_4": ['AI', 'Real'],
83
  "model_5": ['Realism', 'Deepfake'],
84
  "model_6": ['ai_gen', 'human'],
85
  "model_7": ['Fake', 'Real'],
 
86
  }
87
 
88
- def preprocess_resize_256(image):
89
- if image.mode != 'RGB':
90
- image = image.convert('RGB')
91
- return transforms.Resize((256, 256))(image)
92
 
93
- def preprocess_resize_224(image):
94
- if image.mode != 'RGB':
95
- image = image.convert('RGB')
96
- return transforms.Resize((224, 224))(image)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- def postprocess_pipeline(prediction, class_names):
99
- # Assumes HuggingFace pipeline output
100
- return {pred['label']: pred['score'] for pred in prediction}
101
 
102
- def postprocess_logits(outputs, class_names):
103
- # Assumes model output with logits
104
- logits = outputs.logits.cpu().numpy()[0]
105
- probabilities = softmax(logits)
106
- return {class_names[i]: probabilities[i] for i in range(len(class_names))}
107
 
108
  def register_model_with_metadata(model_id, model, preprocess, postprocess, class_names, display_name, contributor, model_path, architecture=None, dataset=None):
109
  entry = ModelEntry(model, preprocess, postprocess, class_names, display_name=display_name, contributor=contributor, model_path=model_path, architecture=architecture, dataset=dataset)
110
  MODEL_REGISTRY[model_id] = entry
111
 
112
- # Load and register models (copied from app_mcp.py)
113
- # image_processor_1 = AutoImageProcessor.from_pretrained(MODEL_PATHS["model_1"], use_fast=True)
114
- # model_1 = Swinv2ForImageClassification.from_pretrained(MODEL_PATHS["model_1"]).to(device)
115
- # clf_1 = pipeline(model=model_1, task="image-classification", image_processor=image_processor_1, device=device)
116
- # register_model_with_metadata(
117
- # "model_1", clf_1, preprocess_resize_256, postprocess_pipeline, CLASS_NAMES["model_1"],
118
- # display_name="SWIN1", contributor="haywoodsloan", model_path=MODEL_PATHS["model_1"],
119
- # architecture="SwinV2", dataset="TBA"
120
- # )
121
 
122
- # --- ONNX Quantized Model Example ---
123
- ONNX_QUANTIZED_MODEL_PATH = "./models/model_1_quantized.onnx"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- def preprocess_onnx_input(image: Image.Image):
126
- # Preprocess image for ONNX model (e.g., for SwinV2, usually 256x256, normalized)
 
 
 
 
 
 
 
 
 
127
  if image.mode != 'RGB':
128
  image = image.convert('RGB')
129
 
 
 
 
 
 
 
 
130
  transform = transforms.Compose([
131
- transforms.Resize((256, 256)),
 
132
  transforms.ToTensor(),
133
- transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]), # ImageNet normalization
134
  ])
135
  input_tensor = transform(image)
136
  # ONNX expects numpy array with batch dimension (1, C, H, W)
137
  return input_tensor.unsqueeze(0).cpu().numpy()
138
 
139
- def infer_onnx_model(preprocessed_image_np):
140
  try:
141
- # Ensure the ONNX model exists before trying to load it
142
- if not os.path.exists(ONNX_QUANTIZED_MODEL_PATH):
143
- logger.error(f"ONNX quantized model not found at: {ONNX_QUANTIZED_MODEL_PATH}")
144
- raise FileNotFoundError(f"ONNX quantized model not found at: {ONNX_QUANTIZED_MODEL_PATH}")
 
145
 
146
- ort_session = onnxruntime.InferenceSession(ONNX_QUANTIZED_MODEL_PATH)
147
  ort_inputs = {ort_session.get_inputs()[0].name: preprocessed_image_np}
148
  ort_outputs = ort_session.run(None, ort_inputs)
149
 
150
- # Assuming the output is logits, apply softmax to get probabilities
151
  logits = ort_outputs[0]
152
- probabilities = softmax(logits[0]) # Remove batch dim, apply softmax
 
 
 
 
 
 
 
 
153
  return {"logits": logits, "probabilities": probabilities}
154
 
155
  except Exception as e:
156
- logger.error(f"Error during ONNX inference: {e}")
157
  # Return a structure consistent with other model errors
158
  return {"logits": np.array([]), "probabilities": np.array([])}
159
 
160
- def postprocess_onnx_output(onnx_output, class_names):
 
 
 
 
 
 
 
 
 
 
 
161
  probabilities = onnx_output.get("probabilities")
162
- if probabilities is not None and len(probabilities) == len(class_names):
163
- return {class_names[i]: probabilities[i] for i in range(len(class_names))}
 
 
 
 
 
 
 
 
 
 
164
  else:
165
- logger.warning("ONNX post-processing failed or class names mismatch.")
166
  return {name: 0.0 for name in class_names}
167
 
168
  # Register the ONNX quantized model
169
- register_model_with_metadata(
170
- "model_1_onnx_quantized",
171
- infer_onnx_model,
172
- preprocess_onnx_input,
173
- postprocess_onnx_output,
174
- CLASS_NAMES["model_1"], # Assuming it uses the same class names as model_1
175
- display_name="SWIN1",
176
- contributor="haywoodsloan",
177
- model_path=ONNX_QUANTIZED_MODEL_PATH,
178
- architecture="SwinV2",
179
- dataset="TBA"
180
- )
181
- # --- End ONNX Quantized Model Example ---
182
-
183
- clf_2 = pipeline("image-classification", model=MODEL_PATHS["model_2"], device=device)
184
- register_model_with_metadata(
185
- "model_2", clf_2, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_2"],
186
- display_name="VIT2", contributor="Heem2", model_path=MODEL_PATHS["model_2"],
187
- architecture="ViT", dataset="TBA"
188
- )
189
-
190
- feature_extractor_3 = AutoFeatureExtractor.from_pretrained(MODEL_PATHS["model_3"], device=device)
191
- model_3 = AutoModelForImageClassification.from_pretrained(MODEL_PATHS["model_3"]).to(device)
192
- def preprocess_256(image):
193
- if image.mode != 'RGB':
194
- image = image.convert('RGB')
195
- return transforms.Resize((256, 256))(image)
196
- def postprocess_logits_model3(outputs, class_names):
197
- logits = outputs.logits.cpu().numpy()[0]
198
- probabilities = softmax(logits)
199
- return {class_names[i]: probabilities[i] for i in range(len(class_names))}
200
- def model3_infer(image):
201
- inputs = feature_extractor_3(image, return_tensors="pt").to(device)
202
- with torch.no_grad():
203
- outputs = model_3(**inputs)
204
- return outputs
205
- register_model_with_metadata(
206
- "model_3", model3_infer, preprocess_256, postprocess_logits_model3, CLASS_NAMES["model_3"],
207
- display_name="SDXL3", contributor="Organika", model_path=MODEL_PATHS["model_3"],
208
- architecture="VIT", dataset="SDXL"
209
- )
210
-
211
- feature_extractor_4 = AutoFeatureExtractor.from_pretrained(MODEL_PATHS["model_4"], device=device)
212
- model_4 = AutoModelForImageClassification.from_pretrained(MODEL_PATHS["model_4"]).to(device)
213
- def model4_infer(image):
214
- inputs = feature_extractor_4(image, return_tensors="pt").to(device)
215
- with torch.no_grad():
216
- outputs = model_4(**inputs)
217
- return outputs
218
- def postprocess_logits_model4(outputs, class_names):
219
- logits = outputs.logits.cpu().numpy()[0]
220
- probabilities = softmax(logits)
221
- return {class_names[i]: probabilities[i] for i in range(len(class_names))}
222
- register_model_with_metadata(
223
- "model_4", model4_infer, preprocess_256, postprocess_logits_model4, CLASS_NAMES["model_4"],
224
- display_name="XLFLUX4", contributor="cmckinle", model_path=MODEL_PATHS["model_4"],
225
- architecture="VIT", dataset="SDXL, FLUX"
226
- )
227
-
228
- clf_5 = pipeline("image-classification", model=MODEL_PATHS["model_5"], device=device)
229
- register_model_with_metadata(
230
- "model_5", clf_5, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_5"],
231
- display_name="VIT5", contributor="prithivMLmods", model_path=MODEL_PATHS["model_5"],
232
- architecture="VIT", dataset="TBA"
233
- )
234
-
235
- image_processor_6 = AutoImageProcessor.from_pretrained(MODEL_PATHS["model_6"], use_fast=True)
236
- model_6 = SwinForImageClassification.from_pretrained(MODEL_PATHS["model_6"]).to(device)
237
- clf_6 = pipeline(model=model_6, task="image-classification", image_processor=image_processor_6, device=device)
238
- register_model_with_metadata(
239
- "model_6", clf_6, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_6"],
240
- display_name="SWIN6", contributor="ideepankarsharma2003", model_path=MODEL_PATHS["model_6"],
241
- architecture="SWINv1", dataset="SDXL, Midjourney"
242
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- image_processor_7 = AutoImageProcessor.from_pretrained(MODEL_PATHS["model_7"], use_fast=True)
245
- model_7 = AutoModelForImageClassification.from_pretrained(MODEL_PATHS["model_7"]).to(device)
246
- clf_7 = pipeline(model=model_7, task="image-classification", image_processor=image_processor_7, device=device)
247
- register_model_with_metadata(
248
- "model_7", clf_7, preprocess_resize_224, postprocess_pipeline, CLASS_NAMES["model_7"],
249
- display_name="VIT7", contributor="date3k2", model_path=MODEL_PATHS["model_7"],
250
- architecture="VIT", dataset="TBA"
251
- )
252
 
253
- # def postprocess_simple_prediction(result, class_names):
254
- # scores = {name: 0.0 for name in class_names}
255
- # fake_prob = result.get("Fake Probability")
256
- # if fake_prob is not None:
257
- # # Assume class_names = ["AI", "REAL"]
258
- # scores["AI"] = float(fake_prob)
259
- # scores["REAL"] = 1.0 - float(fake_prob)
260
- # return scores
261
-
262
- # def simple_prediction(img):
263
- # client = Client("aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview")
264
- # client.view_api()
265
- # print(type(img))
266
- # result = client.predict(
267
- # handle_file(img),
268
- # api_name="simple_predict"
269
- # )
270
- # return result
271
-
272
-
273
- # register_model_with_metadata(
274
- # model_id="simple_prediction",
275
- # model=simple_prediction,
276
- # preprocess=None,
277
- # postprocess=postprocess_simple_prediction,
278
- # class_names=["AI", "REAL"],
279
- # display_name="Community Forensics",
280
- # contributor="Jeongsoo Park",
281
- # model_path="aiwithoutborders-xyz/CommunityForensics-DeepfakeDet-ViT",
282
- # architecture="ViT", dataset="GOAT"
283
- # )
284
  def infer(image: Image.Image, model_id: str, confidence_threshold: float = 0.75) -> dict:
285
  """Predict using a specific model.
286
 
@@ -297,8 +434,14 @@ def infer(image: Image.Image, model_id: str, confidence_threshold: float = 0.75)
297
  try:
298
  result = entry.model(img)
299
  scores = entry.postprocess(result, entry.class_names)
300
- ai_score = float(scores.get(entry.class_names[0], 0.0))
301
- real_score = float(scores.get(entry.class_names[1], 0.0))
 
 
 
 
 
 
302
  label = "AI" if ai_score >= confidence_threshold else ("REAL" if real_score >= confidence_threshold else "UNCERTAIN")
303
  return {
304
  "Model": entry.display_name,
@@ -371,29 +514,97 @@ def full_prediction(img, confidence_threshold, rotate_degrees, noise_level, shar
371
  results = []
372
  table_rows = []
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  # Stream results as each model finishes
375
  for model_id in MODEL_REGISTRY:
376
  model_start = time.time()
377
  result = infer(img_pil, model_id, confidence_threshold)
378
  model_end = time.time()
 
 
 
 
 
 
 
 
 
 
379
  monitor_agent.monitor_prediction(
380
  model_id,
381
  result["Label"],
382
- max(result.get("AI Score", 0.0), result.get("Real Score", 0.0)),
383
  model_end - model_start
384
  )
385
  model_predictions_raw[model_id] = result
386
- confidence_scores[model_id] = max(result.get("AI Score", 0.0), result.get("Real Score", 0.0))
387
  results.append(result)
388
  table_rows.append([
389
  result.get("Model", ""),
390
  result.get("Contributor", ""),
391
- round(result.get("AI Score", 0.0), 3) if result.get("AI Score") is not None else 0.0,
392
- round(result.get("Real Score", 0.0), 3) if result.get("Real Score") is not None else 0.0,
393
  result.get("Label", "Error")
394
  ])
395
  # Yield partial results: only update the table, others are None
396
- yield None, None, table_rows, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
  # After all models, compute the rest as before
399
  image_data_for_context = {
@@ -401,6 +612,17 @@ def full_prediction(img, confidence_threshold, rotate_degrees, noise_level, shar
401
  "height": img.height,
402
  "mode": img.mode,
403
  }
 
 
 
 
 
 
 
 
 
 
 
404
  detected_context_tags = context_agent.infer_context_tags(image_data_for_context, model_predictions_raw)
405
  logger.info(f"Detected context tags: {detected_context_tags}")
406
  adjusted_weights = weight_manager.adjust_weights(model_predictions_raw, confidence_scores, context_tags=detected_context_tags)
@@ -417,26 +639,26 @@ def full_prediction(img, confidence_threshold, rotate_degrees, noise_level, shar
417
  elif weighted_predictions["REAL"] > weighted_predictions["AI"] and weighted_predictions["REAL"] > weighted_predictions["UNCERTAIN"]:
418
  final_prediction_label = "REAL"
419
  optimization_agent.analyze_performance(final_prediction_label, None)
420
- gradient_image = gradient_processing(img_np_og)
421
- gradient_image2 = gradient_processing(img_np_og, intensity=45, equalize=True)
422
- minmax_image = minmax_process(img_np_og)
423
- minmax_image2 = minmax_process(img_np_og, radius=6)
424
- # bitplane_image = bit_plane_extractor(img_pil)
425
- ela1 = ELA(img_np_og, quality=75, scale=50, contrast=20, linear=False, grayscale=True)
426
- ela2 = ELA(img_np_og, quality=75, scale=75, contrast=25, linear=False, grayscale=True)
427
- ela3 = ELA(img_np_og, quality=75, scale=75, contrast=25, linear=False, grayscale=False)
428
- forensics_images = [img_pil, ela1, ela2, ela3, gradient_image, gradient_image2, minmax_image, minmax_image2]
429
- forensic_output_descriptions = [
430
- f"Original augmented image (PIL): {img_pil.width}x{img_pil.height}",
431
- "ELA analysis (Pass 1): Grayscale error map, quality 75.",
432
- "ELA analysis (Pass 2): Grayscale error map, quality 75, enhanced contrast.",
433
- "ELA analysis (Pass 3): Color error map, quality 75, enhanced contrast.",
434
- "Gradient processing: Highlights edges and transitions.",
435
- "Gradient processing: Int=45, Equalize=True",
436
- "MinMax processing: Deviations in local pixel values.",
437
- "MinMax processing (Radius=6): Deviations in local pixel values.",
438
- # "Bit Plane extractor: Visualization of individual bit planes from different color channels."
439
- ]
440
  anomaly_detection_results = anomaly_agent.analyze_forensic_outputs(forensic_output_descriptions)
441
  logger.info(f"Forensic anomaly detection: {anomaly_detection_results['summary']}")
442
  consensus_html = f"<div style='font-size: 2.2em; font-weight: bold;padding: 10px;'>Consensus: <span style='color:{'red' if final_prediction_label == 'AI' else ('green' if final_prediction_label == 'REAL' else 'orange')}'>{final_prediction_label}</span></div>"
@@ -474,21 +696,11 @@ def full_prediction(img, confidence_threshold, rotate_degrees, noise_level, shar
474
  inference_params=inference_params,
475
  model_predictions=results,
476
  ensemble_output=ensemble_output_data,
477
- forensic_images=forensics_images,
478
  agent_monitoring_data=agent_monitoring_data_log,
479
  human_feedback=None
480
  )
481
- cleaned_forensics_images = []
482
- for f_img in forensics_images:
483
- if isinstance(f_img, Image.Image):
484
- cleaned_forensics_images.append(f_img)
485
- elif isinstance(f_img, np.ndarray):
486
- try:
487
- cleaned_forensics_images.append(Image.fromarray(f_img))
488
- except Exception as e:
489
- logger.warning(f"Could not convert numpy array to PIL Image for gallery: {e}")
490
- else:
491
- logger.warning(f"Unexpected type in forensic_images: {type(f_img)}. Skipping.")
492
  logger.info(f"Cleaned forensic images types: {[type(img) for img in cleaned_forensics_images]}")
493
  for i, res_dict in enumerate(results):
494
  for key in ["AI Score", "Real Score"]:
@@ -709,7 +921,7 @@ demo = gr.TabbedInterface(
709
 
710
  )
711
  footerMD = """
712
- ### ⚠️ ENSEMBLE TEAM IN TRAINING ⚠️ \n\n
713
 
714
  1. **DISCLAIMER: METADATA AS WELL AS MEDIA SUBMITTED TO THIS SPACE MAY BE VIEWED AND SELECTED FOR FUTURE DATASETS, PLEASE DO NOT SUBMIT PERSONAL CONTENT. FOR UNTRACKED, PRIVATE USE OF THE MODELS YOU MAY STILL USE [THE ORIGINAL SPACE HERE](https://huggingface.co/spaces/aiwithoutborders-xyz/OpenSight-Deepfake-Detection-Models-Playground), SOTA MODEL INCLUDED.**
715
  2. **UPDATE 6-13-25**: APOLOGIES FOR THE CONFUSION, WE ARE WORKING TO REVERT THE ORIGINAL REPO BACK TO ITS NON-DATA COLLECTION STATE -- ONLY THE "SIMPLE PREDICTION" ENDPOINT IS CURRENTLY 100% PRIVATE. PLEASE STAY TUNED AS WE FIGURE OUT A SOLUTION FOR THE ENSEMBLE + AGENT TEAM ENDPOINT. IT CAN GET RESOURCE INTENSIVE TO RUN A FULL PREDICTION. ALTERNATIVELY, WE **ENCOURAGE** ANYONE TO FORK AND CONTRIBUTE TO THE PROJECT.
@@ -718,7 +930,7 @@ footerMD = """
718
  TO SUMMARIZE: DATASET COLLECTION WILL CONTINUE FOR OUR NOVEL ENSEMBLE-TEAM PREDICTION PIPELINE UNTIL WE CAN GET THINGS SORTED OUT. FOR THOSE THAT WISH TO OPT-OUT, WE OFFER THE SIMPLE, BUT [MOST POWERFUL DETECTION MODEL HERE.](https://huggingface.co/spaces/aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview)
719
 
720
  """
721
- footer = gr.Markdown("", elem_classes="footer")
722
 
723
  with gr.Blocks() as app:
724
  demo.render()
 
8
  import io
9
  import collections
10
  import onnxruntime
11
+ import json
12
+ from huggingface_hub import CommitScheduler, hf_hub_download, snapshot_download
13
+ from dotenv import load_dotenv
14
+ import concurrent.futures
15
+ import ast
16
+ import torch
17
 
18
+ from utils.utils import softmax, augment_image, preprocess_resize_256, preprocess_resize_224, postprocess_pipeline, postprocess_logits, postprocess_binary_output, to_float_scalar
19
  from forensics.gradient import gradient_processing
20
  from forensics.minmax import minmax_process
21
  from forensics.ela import ELA
 
29
  from agents.ensemble_weights import ModelWeightManager
30
  from transformers import pipeline, AutoImageProcessor, SwinForImageClassification, Swinv2ForImageClassification, AutoFeatureExtractor, AutoModelForImageClassification
31
  from torchvision import transforms
 
 
 
 
32
 
33
  logging.basicConfig(level=logging.INFO)
34
  logger = logging.getLogger(__name__)
 
68
 
69
  # Model paths and class names (copied from app_mcp.py)
70
  MODEL_PATHS = {
71
+ "model_1": "LPX55/detection-model-1-ONNX",
72
+ "model_2": "LPX55/detection-model-2-ONNX",
73
+ "model_3": "LPX55/detection-model-3-ONNX",
74
  "model_4": "cmckinle/sdxl-flux-detector_v1.1",
75
+ "model_5": "LPX55/detection-model-5-ONNX",
76
+ "model_6": "LPX55/detection-model-6-ONNX",
77
+ "model_7": "LPX55/detection-model-7-ONNX",
78
+ "model_8": "aiwithoutborders-xyz/CommunityForensics-DeepfakeDet-ViT"
79
  }
80
 
81
  CLASS_NAMES = {
82
  "model_1": ['artificial', 'real'],
83
  "model_2": ['AI Image', 'Real Image'],
84
+ "model_3": ['artificial', 'human'],
85
  "model_4": ['AI', 'Real'],
86
  "model_5": ['Realism', 'Deepfake'],
87
  "model_6": ['ai_gen', 'human'],
88
  "model_7": ['Fake', 'Real'],
89
+ "model_8": ['Fake', 'Real'],
90
  }
91
 
 
 
 
 
92
 
93
+ def infer_gradio_api(image_path):
94
+ client = Client("aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview")
95
+ result_dict = client.predict(
96
+ input_image=handle_file(image_path),
97
+ api_name="/simple_predict"
98
+ )
99
+ logger.info(f"Debug: Raw result_dict from Gradio API (model_8): {result_dict}, type: {type(result_dict)}")
100
+ # result_dict is already a dictionary, no need for ast.literal_eval
101
+ fake_probability = result_dict.get('Fake Probability', 0.0)
102
+ logger.info(f"Debug: Parsed result_dict: {result_dict}, Extracted fake_probability: {fake_probability}")
103
+ return {"probabilities": np.array([fake_probability])} # Return as a numpy array with one element
104
+
105
+ # New preprocess function for Gradio API
106
+ def preprocess_gradio_api(image: Image.Image):
107
+ # The Gradio API expects a file path, so we need to save the PIL Image to a temporary file.
108
+ temp_file_path = "./temp_gradio_input.png"
109
+ image.save(temp_file_path)
110
+ return temp_file_path
111
+
112
+ # New postprocess function for Gradio API (adapting postprocess_binary_output)
113
+ def postprocess_gradio_api(gradio_output, class_names):
114
+ # gradio_output is expected to be a dictionary like {"probabilities": np.array([fake_prob])}
115
+ probabilities_array = None
116
+ if isinstance(gradio_output, dict) and "probabilities" in gradio_output:
117
+ probabilities_array = gradio_output["probabilities"]
118
+ elif isinstance(gradio_output, np.ndarray):
119
+ probabilities_array = gradio_output
120
+ else:
121
+ logger.warning(f"Unexpected output type for Gradio API post-processing: {type(gradio_output)}. Expected dict with 'probabilities' or numpy.ndarray.")
122
+ return {class_names[0]: 0.0, class_names[1]: 1.0}
123
+
124
+ logger.info(f"Debug: Probabilities array entering postprocess_gradio_api: {probabilities_array}, type: {type(probabilities_array)}, shape: {probabilities_array.shape}")
125
 
126
+ if probabilities_array is None or probabilities_array.size == 0:
127
+ logger.warning("Probabilities array is None or empty after extracting from Gradio API output. Returning default scores.")
128
+ return {class_names[0]: 0.0, class_names[1]: 1.0}
129
 
130
+ # It should always be a single element array for fake probability
131
+ fake_prob = float(probabilities_array.item())
132
+ real_prob = 1.0 - fake_prob
133
+
134
+ return {class_names[0]: fake_prob, class_names[1]: real_prob}
135
 
136
  def register_model_with_metadata(model_id, model, preprocess, postprocess, class_names, display_name, contributor, model_path, architecture=None, dataset=None):
137
  entry = ModelEntry(model, preprocess, postprocess, class_names, display_name=display_name, contributor=contributor, model_path=model_path, architecture=architecture, dataset=dataset)
138
  MODEL_REGISTRY[model_id] = entry
139
 
 
 
 
 
 
 
 
 
 
140
 
141
+ def load_onnx_model_and_preprocessor(hf_model_id):
142
+ # model_dir = snapshot_download(repo_id=hf_model_id, local_dir_use_symlinks=False)
143
+
144
+ # Create a unique local directory for each ONNX model
145
+ model_specific_dir = os.path.join("./models", hf_model_id.replace('/', '_'))
146
+ os.makedirs(model_specific_dir, exist_ok=True)
147
+
148
+ # Use hf_hub_download to get specific files into the model-specific directory
149
+ onnx_model_path = hf_hub_download(repo_id=hf_model_id, filename="model_quantized.onnx", subfolder="onnx", local_dir=model_specific_dir, local_dir_use_symlinks=False)
150
+
151
+ # Load preprocessor config
152
+ preprocessor_config = {}
153
+ try:
154
+ preprocessor_config_path = hf_hub_download(repo_id=hf_model_id, filename="preprocessor_config.json", local_dir=model_specific_dir, local_dir_use_symlinks=False)
155
+ with open(preprocessor_config_path, 'r') as f:
156
+ preprocessor_config = json.load(f)
157
+ except Exception as e:
158
+ logger.warning(f"Could not download or load preprocessor_config.json for {hf_model_id}: {e}")
159
+
160
+ # Load model config for class names if available
161
+ model_config = {}
162
+ try:
163
+ model_config_path = hf_hub_download(repo_id=hf_model_id, filename="config.json", local_dir=model_specific_dir, local_dir_use_symlinks=False)
164
+ with open(model_config_path, 'r') as f:
165
+ model_config = json.load(f)
166
+ except Exception as e:
167
+ logger.warning(f"Could not download or load config.json for {hf_model_id}: {e}")
168
+
169
+ return onnxruntime.InferenceSession(onnx_model_path), preprocessor_config, model_config
170
+
171
 
172
+ # Cache for ONNX sessions and preprocessors
173
+ _onnx_model_cache = {}
174
+
175
+ def get_onnx_model_from_cache(hf_model_id):
176
+ if hf_model_id not in _onnx_model_cache:
177
+ logger.info(f"Loading ONNX model and preprocessor for {hf_model_id}...")
178
+ _onnx_model_cache[hf_model_id] = load_onnx_model_and_preprocessor(hf_model_id)
179
+ return _onnx_model_cache[hf_model_id]
180
+
181
+ def preprocess_onnx_input(image: Image.Image, preprocessor_config: dict):
182
+ # Preprocess image for ONNX model based on preprocessor_config
183
  if image.mode != 'RGB':
184
  image = image.convert('RGB')
185
 
186
+ # Get image size and normalization values from preprocessor_config or use defaults
187
+ # Use 'size' for initial resize and 'crop_size' for center cropping
188
+ initial_resize_size = preprocessor_config.get('size', {'height': 224, 'width': 224})
189
+ crop_size = preprocessor_config.get('crop_size', initial_resize_size['height'])
190
+ mean = preprocessor_config.get('image_mean', [0.485, 0.456, 0.406])
191
+ std = preprocessor_config.get('image_std', [0.229, 0.224, 0.225])
192
+
193
  transform = transforms.Compose([
194
+ transforms.Resize((initial_resize_size['height'], initial_resize_size['width'])),
195
+ transforms.CenterCrop(crop_size), # Apply center crop
196
  transforms.ToTensor(),
197
+ transforms.Normalize(mean=mean, std=std),
198
  ])
199
  input_tensor = transform(image)
200
  # ONNX expects numpy array with batch dimension (1, C, H, W)
201
  return input_tensor.unsqueeze(0).cpu().numpy()
202
 
203
+ def infer_onnx_model(hf_model_id, preprocessed_image_np, model_config: dict):
204
  try:
205
+ ort_session, _, _ = get_onnx_model_from_cache(hf_model_id)
206
+
207
+ # Debug: Print expected input shape from ONNX model
208
+ for input_meta in ort_session.get_inputs():
209
+ logger.info(f"Debug: ONNX model expected input name: {input_meta.name}, shape: {input_meta.shape}, type: {input_meta.type}")
210
 
211
+ logger.info(f"Debug: preprocessed_image_np shape: {preprocessed_image_np.shape}")
212
  ort_inputs = {ort_session.get_inputs()[0].name: preprocessed_image_np}
213
  ort_outputs = ort_session.run(None, ort_inputs)
214
 
 
215
  logits = ort_outputs[0]
216
+ logger.info(f"Debug: logits type: {type(logits)}, shape: {logits.shape}")
217
+ # If the model outputs a single logit (e.g., shape (1,)), use .item() to convert to scalar
218
+ # Otherwise, assume it's a batch of logits (e.g., shape (1, num_classes)) and take the first element (batch dim)
219
+ # The num_classes in config.json can be misleading; rely on actual output shape.
220
+
221
+ # Apply softmax to the logits to get probabilities for the classes
222
+ # The softmax function in utils/utils.py now ensures a list of floats
223
+ probabilities = softmax(logits[0]) # Assuming logits[0] is the relevant output for a single prediction
224
+
225
  return {"logits": logits, "probabilities": probabilities}
226
 
227
  except Exception as e:
228
+ logger.error(f"Error during ONNX inference for {hf_model_id}: {e}")
229
  # Return a structure consistent with other model errors
230
  return {"logits": np.array([]), "probabilities": np.array([])}
231
 
232
+ def postprocess_onnx_output(onnx_output, model_config):
233
+ # Get class names from model_config
234
+ # Prioritize id2label, then check num_classes, otherwise default
235
+ class_names_map = model_config.get('id2label')
236
+ if class_names_map:
237
+ class_names = [class_names_map[k] for k in sorted(class_names_map.keys())]
238
+ elif model_config.get('num_classes') == 1: # Handle models that output a single value (e.g., probability of 'Fake')
239
+ class_names = ['Fake', 'Real'] # Assume first class is 'Fake' and second 'Real'
240
+ else:
241
+ class_names = {0: 'Fake', 1: 'Real'} # Default to Fake/Real if not found or not 1 class
242
+ class_names = [class_names[i] for i in sorted(class_names.keys())]
243
+
244
  probabilities = onnx_output.get("probabilities")
245
+
246
+ if probabilities is not None:
247
+ if model_config.get('num_classes') == 1 and len(probabilities) == 2: # Special handling for single output models
248
+ # The single output is the probability of the 'Fake' class
249
+ fake_prob = float(probabilities[0])
250
+ real_prob = float(probabilities[1])
251
+ return {class_names[0]: fake_prob, class_names[1]: real_prob}
252
+ elif len(probabilities) == len(class_names):
253
+ return {class_names[i]: float(probabilities[i]) for i in range(len(class_names))}
254
+ else:
255
+ logger.warning("ONNX post-processing: Probabilities length mismatch with class names.")
256
+ return {name: 0.0 for name in class_names}
257
  else:
258
+ logger.warning("ONNX post-processing failed: 'probabilities' key not found in output.")
259
  return {name: 0.0 for name in class_names}
260
 
261
  # Register the ONNX quantized model
262
+ # Dummy entry for ONNX model to be loaded dynamically
263
+ # We will now register a 'wrapper' that handles dynamic loading
264
+
265
+ class ONNXModelWrapper:
266
+ def __init__(self, hf_model_id):
267
+ self.hf_model_id = hf_model_id
268
+ self._session = None
269
+ self._preprocessor_config = None
270
+ self._model_config = None
271
+
272
+ def load(self):
273
+ if self._session is None:
274
+ self._session, self._preprocessor_config, self._model_config = get_onnx_model_from_cache(self.hf_model_id)
275
+ logger.info(f"ONNX model {self.hf_model_id} loaded into wrapper.")
276
+
277
+ def __call__(self, image_np):
278
+ self.load() # Ensure model is loaded on first call
279
+ # Pass model_config to infer_onnx_model
280
+ return infer_onnx_model(self.hf_model_id, image_np, self._model_config)
281
+
282
+ def preprocess(self, image: Image.Image):
283
+ self.load()
284
+ return preprocess_onnx_input(image, self._preprocessor_config)
285
+
286
+ def postprocess(self, onnx_output: dict, class_names_from_registry: list): # class_names_from_registry is ignored
287
+ self.load()
288
+ return postprocess_onnx_output(onnx_output, self._model_config)
289
+
290
+ # Consolidate all model loading and registration
291
+ for model_key, hf_model_path in MODEL_PATHS.items():
292
+ logger.debug(f"Attempting to register model: {model_key} with path: {hf_model_path}")
293
+ model_num = model_key.replace("model_", "").upper()
294
+ contributor = "Unknown"
295
+ architecture = "Unknown"
296
+ dataset = "TBA"
297
+
298
+ current_class_names = CLASS_NAMES.get(model_key, [])
299
+
300
+ # Logic for ONNX models (1, 2, 3, 5, 6, 7)
301
+ if "ONNX" in hf_model_path:
302
+ logger.debug(f"Model {model_key} identified as ONNX.")
303
+ logger.info(f"Registering ONNX model: {model_key} from {hf_model_path}")
304
+ onnx_wrapper_instance = ONNXModelWrapper(hf_model_path)
305
+
306
+ # Attempt to derive contributor, architecture, dataset based on model_key
307
+ if model_key == "model_1":
308
+ contributor = "haywoodsloan"
309
+ architecture = "SwinV2"
310
+ dataset = "DeepFakeDetection"
311
+ elif model_key == "model_2":
312
+ contributor = "Heem2"
313
+ architecture = "ViT"
314
+ dataset = "DeepFakeDetection"
315
+ elif model_key == "model_3":
316
+ contributor = "Organika"
317
+ architecture = "VIT"
318
+ dataset = "SDXL"
319
+ elif model_key == "model_5":
320
+ contributor = "prithivMLmods"
321
+ architecture = "VIT"
322
+ elif model_key == "model_6":
323
+ contributor = "ideepankarsharma2003"
324
+ architecture = "SWINv1"
325
+ dataset = "SDXL, Midjourney"
326
+ elif model_key == "model_7":
327
+ contributor = "date3k2"
328
+ architecture = "VIT"
329
+
330
+ display_name_parts = [model_num]
331
+ if architecture and architecture not in ["Unknown"]:
332
+ display_name_parts.append(architecture)
333
+ if dataset and dataset not in ["TBA"]:
334
+ display_name_parts.append(dataset)
335
+ display_name = "-".join(display_name_parts)
336
+ display_name += "_ONNX" # Always append _ONNX for ONNX models
337
+
338
+ register_model_with_metadata(
339
+ model_id=model_key,
340
+ model=onnx_wrapper_instance, # The callable wrapper for the ONNX model
341
+ preprocess=onnx_wrapper_instance.preprocess,
342
+ postprocess=onnx_wrapper_instance.postprocess,
343
+ class_names=current_class_names, # Initial class names; will be overridden by model_config if available
344
+ display_name=display_name,
345
+ contributor=contributor,
346
+ model_path=hf_model_path,
347
+ architecture=architecture,
348
+ dataset=dataset
349
+ )
350
+ # Logic for Gradio API model (model_8)
351
+ elif model_key == "model_8":
352
+ logger.debug(f"Model {model_key} identified as Gradio API.")
353
+ logger.info(f"Registering Gradio API model: {model_key} from {hf_model_path}")
354
+ contributor = "aiwithoutborders-xyz"
355
+ architecture = "ViT"
356
+ dataset = "DeepfakeDetection"
357
+
358
+ display_name_parts = [model_num]
359
+ if architecture and architecture not in ["Unknown"]:
360
+ display_name_parts.append(architecture)
361
+ if dataset and dataset not in ["TBA"]:
362
+ display_name_parts.append(dataset)
363
+ display_name = "-".join(display_name_parts)
364
+
365
+ register_model_with_metadata(
366
+ model_id=model_key,
367
+ model=infer_gradio_api,
368
+ preprocess=preprocess_gradio_api,
369
+ postprocess=postprocess_gradio_api,
370
+ class_names=current_class_names,
371
+ display_name=display_name,
372
+ contributor=contributor,
373
+ model_path=hf_model_path,
374
+ architecture=architecture,
375
+ dataset=dataset
376
+ )
377
+ # Logic for PyTorch/Hugging Face pipeline models (currently only model_4)
378
+ elif model_key == "model_4": # Explicitly handle model_4
379
+ logger.debug(f"Model {model_key} identified as PyTorch/HuggingFace pipeline.")
380
+ logger.info(f"Registering HuggingFace pipeline/AutoModel: {model_key} from {hf_model_path}")
381
+ contributor = "cmckinle"
382
+ architecture = "VIT"
383
+ dataset = "SDXL, FLUX"
384
+
385
+ display_name_parts = [model_num]
386
+ if architecture and architecture not in ["Unknown"]:
387
+ display_name_parts.append(architecture)
388
+ if dataset and dataset not in ["TBA"]:
389
+ display_name_parts.append(dataset)
390
+ display_name = "-".join(display_name_parts)
391
+
392
+ current_processor = AutoFeatureExtractor.from_pretrained(hf_model_path, device=device)
393
+ model_instance = AutoModelForImageClassification.from_pretrained(hf_model_path).to(device)
394
+
395
+ preprocess_func = preprocess_resize_256
396
+ postprocess_func = postprocess_logits
397
+
398
+ def custom_infer(image, processor_local=current_processor, model_local=model_instance):
399
+ inputs = processor_local(image, return_tensors="pt").to(device)
400
+ with torch.no_grad():
401
+ outputs = model_local(**inputs)
402
+ return outputs
403
+ model_instance = custom_infer
404
+
405
+ register_model_with_metadata(
406
+ model_id=model_key,
407
+ model=model_instance,
408
+ preprocess=preprocess_func,
409
+ postprocess=postprocess_func,
410
+ class_names=current_class_names,
411
+ display_name=display_name,
412
+ contributor=contributor,
413
+ model_path=hf_model_path,
414
+ architecture=architecture,
415
+ dataset=dataset
416
+ )
417
+ else: # Fallback for any unhandled models (shouldn't happen if MODEL_PATHS is fully covered)
418
+ logger.warning(f"Could not automatically load and register model: {model_key} from {hf_model_path}. No matching registration logic found.")
419
 
 
 
 
 
 
 
 
 
420
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  def infer(image: Image.Image, model_id: str, confidence_threshold: float = 0.75) -> dict:
422
  """Predict using a specific model.
423
 
 
434
  try:
435
  result = entry.model(img)
436
  scores = entry.postprocess(result, entry.class_names)
437
+
438
+ def _to_float_scalar(value):
439
+ if isinstance(value, np.ndarray):
440
+ return float(value.item()) # Convert numpy array scalar to Python float
441
+ return float(value) # Already a Python scalar or convertible type
442
+
443
+ ai_score = _to_float_scalar(scores.get(entry.class_names[0], 0.0))
444
+ real_score = _to_float_scalar(scores.get(entry.class_names[1], 0.0))
445
  label = "AI" if ai_score >= confidence_threshold else ("REAL" if real_score >= confidence_threshold else "UNCERTAIN")
446
  return {
447
  "Model": entry.display_name,
 
514
  results = []
515
  table_rows = []
516
 
517
+ # Initialize lists for forensic outputs, starting with the original augmented image
518
+ cleaned_forensics_images = []
519
+ forensic_output_descriptions = []
520
+
521
+ # Always add the original augmented image first for forensic display
522
+ if isinstance(img_pil, Image.Image):
523
+ cleaned_forensics_images.append(img_pil)
524
+ forensic_output_descriptions.append(f"Original augmented image (PIL): {img_pil.width}x{img_pil.height}")
525
+ elif isinstance(img_pil, np.ndarray):
526
+ try:
527
+ pil_img_from_np = Image.fromarray(img_pil)
528
+ cleaned_forensics_images.append(pil_img_from_np)
529
+ forensic_output_descriptions.append(f"Original augmented image (numpy converted to PIL): {pil_img_from_np.width}x{pil_img_from_np.height}")
530
+ except Exception as e:
531
+ logger.warning(f"Could not convert original numpy image to PIL for gallery: {e}")
532
+
533
+ # Yield initial state with augmented image and empty model predictions
534
+ yield img_pil, cleaned_forensics_images, table_rows, "[]", "<div style='font-size: 2.2em; font-weight: bold;padding: 10px;'>Consensus: <span style='color:orange'>UNCERTAIN</span></div>"
535
+
536
+
537
  # Stream results as each model finishes
538
  for model_id in MODEL_REGISTRY:
539
  model_start = time.time()
540
  result = infer(img_pil, model_id, confidence_threshold)
541
  model_end = time.time()
542
+
543
+ # Helper to ensure values are Python floats, handling numpy scalars
544
+ def _ensure_float_scalar(value):
545
+ if isinstance(value, np.ndarray):
546
+ return float(value.item()) # Convert numpy array scalar to Python float
547
+ return float(value) # Already a Python scalar or convertible type
548
+
549
+ ai_score_val = _ensure_float_scalar(result.get("AI Score", 0.0))
550
+ real_score_val = _ensure_float_val = _ensure_float_scalar(result.get("Real Score", 0.0))
551
+
552
  monitor_agent.monitor_prediction(
553
  model_id,
554
  result["Label"],
555
+ max(ai_score_val, real_score_val),
556
  model_end - model_start
557
  )
558
  model_predictions_raw[model_id] = result
559
+ confidence_scores[model_id] = max(ai_score_val, real_score_val)
560
  results.append(result)
561
  table_rows.append([
562
  result.get("Model", ""),
563
  result.get("Contributor", ""),
564
+ round(ai_score_val, 5),
565
+ round(real_score_val, 5),
566
  result.get("Label", "Error")
567
  ])
568
  # Yield partial results: only update the table, others are None
569
+ yield None, cleaned_forensics_images, table_rows, None, None # Keep cleaned_forensics_images as is (only augmented image for now)
570
+
571
+ # Multi-threaded forensic processing
572
+ def _run_forensic_task(task_func, img_input, description, **kwargs):
573
+ try:
574
+ result_img = task_func(img_input, **kwargs)
575
+ return result_img, description
576
+ except Exception as e:
577
+ logger.error(f"Error processing forensic task {task_func.__name__}: {e}")
578
+ return None, f"Error processing {description}: {str(e)}"
579
+
580
+ with concurrent.futures.ThreadPoolExecutor() as executor:
581
+ future_ela1 = executor.submit(_run_forensic_task, ELA, img_np_og, "ELA analysis (Pass 1): Grayscale error map, quality 75.", quality=75, scale=50, contrast=20, linear=False, grayscale=True)
582
+ future_ela2 = executor.submit(_run_forensic_task, ELA, img_np_og, "ELA analysis (Pass 2): Grayscale error map, quality 75, enhanced contrast.", quality=75, scale=75, contrast=25, linear=False, grayscale=True)
583
+ future_ela3 = executor.submit(_run_forensic_task, ELA, img_np_og, "ELA analysis (Pass 3): Color error map, quality 75, enhanced contrast.", quality=75, scale=75, contrast=25, linear=False, grayscale=False)
584
+ future_gradient1 = executor.submit(_run_forensic_task, gradient_processing, img_np_og, "Gradient processing: Highlights edges and transitions.")
585
+ future_gradient2 = executor.submit(_run_forensic_task, gradient_processing, img_np_og, "Gradient processing: Int=45, Equalize=True", intensity=45, equalize=True)
586
+ future_minmax1 = executor.submit(_run_forensic_task, minmax_process, img_np_og, "MinMax processing: Deviations in local pixel values.")
587
+ future_minmax2 = executor.submit(_run_forensic_task, minmax_process, img_np_og, "MinMax processing (Radius=6): Deviations in local pixel values.", radius=6)
588
+
589
+ forensic_futures = [future_ela1, future_ela2, future_ela3, future_gradient1, future_gradient2, future_minmax1, future_minmax2]
590
+
591
+ for future in concurrent.futures.as_completed(forensic_futures):
592
+ processed_img, description = future.result()
593
+ if processed_img is not None:
594
+ if isinstance(processed_img, Image.Image):
595
+ cleaned_forensics_images.append(processed_img)
596
+ elif isinstance(processed_img, np.ndarray):
597
+ try:
598
+ cleaned_forensics_images.append(Image.fromarray(processed_img))
599
+ except Exception as e:
600
+ logger.warning(f"Could not convert numpy array to PIL Image for gallery: {e}")
601
+ else:
602
+ logger.warning(f"Unexpected type in processed_img from {description}: {type(processed_img)}. Skipping.")
603
+
604
+ forensic_output_descriptions.append(description) # Keep track of descriptions for anomaly agent
605
+
606
+ # Yield partial results: update gallery
607
+ yield None, cleaned_forensics_images, table_rows, None, None
608
 
609
  # After all models, compute the rest as before
610
  image_data_for_context = {
 
612
  "height": img.height,
613
  "mode": img.mode,
614
  }
615
+ forensic_output_descriptions = [
616
+ f"Original augmented image (PIL): {img_pil.width}x{img_pil.height}",
617
+ "ELA analysis (Pass 1): Grayscale error map, quality 75.",
618
+ "ELA analysis (Pass 2): Grayscale error map, quality 75, enhanced contrast.",
619
+ "ELA analysis (Pass 3): Color error map, quality 75, enhanced contrast.",
620
+ "Gradient processing: Highlights edges and transitions.",
621
+ "Gradient processing: Int=45, Equalize=True",
622
+ "MinMax processing: Deviations in local pixel values.",
623
+ "MinMax processing (Radius=6): Deviations in local pixel values.",
624
+ # "Bit Plane extractor: Visualization of individual bit planes from different color channels."
625
+ ]
626
  detected_context_tags = context_agent.infer_context_tags(image_data_for_context, model_predictions_raw)
627
  logger.info(f"Detected context tags: {detected_context_tags}")
628
  adjusted_weights = weight_manager.adjust_weights(model_predictions_raw, confidence_scores, context_tags=detected_context_tags)
 
639
  elif weighted_predictions["REAL"] > weighted_predictions["AI"] and weighted_predictions["REAL"] > weighted_predictions["UNCERTAIN"]:
640
  final_prediction_label = "REAL"
641
  optimization_agent.analyze_performance(final_prediction_label, None)
642
+ # gradient_image = gradient_processing(img_np_og)
643
+ # gradient_image2 = gradient_processing(img_np_og, intensity=45, equalize=True)
644
+ # minmax_image = minmax_process(img_np_og)
645
+ # minmax_image2 = minmax_process(img_np_og, radius=6)
646
+ # # bitplane_image = bit_plane_extractor(img_pil)
647
+ # ela1 = ELA(img_np_og, quality=75, scale=50, contrast=20, linear=False, grayscale=True)
648
+ # ela2 = ELA(img_np_og, quality=75, scale=75, contrast=25, linear=False, grayscale=True)
649
+ # ela3 = ELA(img_np_og, quality=75, scale=75, contrast=25, linear=False, grayscale=False)
650
+ # forensics_images = [img_pil, ela1, ela2, ela3, gradient_image, gradient_image2, minmax_image, minmax_image2]
651
+ # forensic_output_descriptions = [
652
+ # f"Original augmented image (PIL): {img_pil.width}x{img_pil.height}",
653
+ # "ELA analysis (Pass 1): Grayscale error map, quality 75.",
654
+ # "ELA analysis (Pass 2): Grayscale error map, quality 75, enhanced contrast.",
655
+ # "ELA analysis (Pass 3): Color error map, quality 75, enhanced contrast.",
656
+ # "Gradient processing: Highlights edges and transitions.",
657
+ # "Gradient processing: Int=45, Equalize=True",
658
+ # "MinMax processing: Deviations in local pixel values.",
659
+ # "MinMax processing (Radius=6): Deviations in local pixel values.",
660
+ # # "Bit Plane extractor: Visualization of individual bit planes from different color channels."
661
+ # ]
662
  anomaly_detection_results = anomaly_agent.analyze_forensic_outputs(forensic_output_descriptions)
663
  logger.info(f"Forensic anomaly detection: {anomaly_detection_results['summary']}")
664
  consensus_html = f"<div style='font-size: 2.2em; font-weight: bold;padding: 10px;'>Consensus: <span style='color:{'red' if final_prediction_label == 'AI' else ('green' if final_prediction_label == 'REAL' else 'orange')}'>{final_prediction_label}</span></div>"
 
696
  inference_params=inference_params,
697
  model_predictions=results,
698
  ensemble_output=ensemble_output_data,
699
+ forensic_images=cleaned_forensics_images, # Use the incrementally built list
700
  agent_monitoring_data=agent_monitoring_data_log,
701
  human_feedback=None
702
  )
703
+
 
 
 
 
 
 
 
 
 
 
704
  logger.info(f"Cleaned forensic images types: {[type(img) for img in cleaned_forensics_images]}")
705
  for i, res_dict in enumerate(results):
706
  for key in ["AI Score", "Real Score"]:
 
921
 
922
  )
923
  footerMD = """
924
+ ## ⚠️ ENSEMBLE TEAM IN TRAINING ⚠️ \n\n
925
 
926
  1. **DISCLAIMER: METADATA AS WELL AS MEDIA SUBMITTED TO THIS SPACE MAY BE VIEWED AND SELECTED FOR FUTURE DATASETS, PLEASE DO NOT SUBMIT PERSONAL CONTENT. FOR UNTRACKED, PRIVATE USE OF THE MODELS YOU MAY STILL USE [THE ORIGINAL SPACE HERE](https://huggingface.co/spaces/aiwithoutborders-xyz/OpenSight-Deepfake-Detection-Models-Playground), SOTA MODEL INCLUDED.**
927
  2. **UPDATE 6-13-25**: APOLOGIES FOR THE CONFUSION, WE ARE WORKING TO REVERT THE ORIGINAL REPO BACK TO ITS NON-DATA COLLECTION STATE -- ONLY THE "SIMPLE PREDICTION" ENDPOINT IS CURRENTLY 100% PRIVATE. PLEASE STAY TUNED AS WE FIGURE OUT A SOLUTION FOR THE ENSEMBLE + AGENT TEAM ENDPOINT. IT CAN GET RESOURCE INTENSIVE TO RUN A FULL PREDICTION. ALTERNATIVELY, WE **ENCOURAGE** ANYONE TO FORK AND CONTRIBUTE TO THE PROJECT.
 
930
  TO SUMMARIZE: DATASET COLLECTION WILL CONTINUE FOR OUR NOVEL ENSEMBLE-TEAM PREDICTION PIPELINE UNTIL WE CAN GET THINGS SORTED OUT. FOR THOSE THAT WISH TO OPT-OUT, WE OFFER THE SIMPLE, BUT [MOST POWERFUL DETECTION MODEL HERE.](https://huggingface.co/spaces/aiwithoutborders-xyz/OpenSight-Community-Forensics-Preview)
931
 
932
  """
933
+ footer = gr.Markdown(footerMD, elem_classes="footer")
934
 
935
  with gr.Blocks() as app:
936
  demo.render()
utils/utils.py CHANGED
@@ -1,3 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import numpy as np
2
  import io
3
  from PIL import Image, ImageFilter, ImageChops
 
1
+ def preprocess_resize_256(image):
2
+ if image.mode != 'RGB':
3
+ image = image.convert('RGB')
4
+ return transforms.Resize((256, 256))(image)
5
+
6
+ def preprocess_resize_224(image):
7
+ if image.mode != 'RGB':
8
+ image = image.convert('RGB')
9
+ return transforms.Resize((224, 224))(image)
10
+
11
+ def postprocess_pipeline(prediction, class_names):
12
+ # Assumes HuggingFace pipeline output
13
+ return {pred['label']: float(pred['score']) for pred in prediction}
14
+
15
+ def postprocess_logits(outputs, class_names):
16
+ # Assumes model output with logits
17
+ logits = outputs.logits.cpu().numpy()[0]
18
+ probabilities = softmax(logits)
19
+ return {class_names[i]: probabilities[i] for i in range(len(class_names))}
20
+
21
+ def postprocess_binary_output(output, class_names):
22
+ # output can be a dictionary {"probabilities": numpy_array} or directly a numpy_array
23
+ import logging
24
+ logger = logging.getLogger(__name__)
25
+ probabilities_array = None
26
+ if isinstance(output, dict) and "probabilities" in output:
27
+ probabilities_array = output["probabilities"]
28
+ elif isinstance(output, np.ndarray):
29
+ probabilities_array = output
30
+ else:
31
+ logger.warning(f"Unexpected output type for binary post-processing: {type(output)}. Expected dict with 'probabilities' or numpy.ndarray.")
32
+ return {class_names[0]: 0.0, class_names[1]: 1.0}
33
+
34
+ logger.info(f"Debug: Probabilities array entering postprocess_binary_output: {probabilities_array}, type: {type(probabilities_array)}, shape: {getattr(probabilities_array, 'shape', None)}")
35
+
36
+ if probabilities_array is None:
37
+ logger.warning("Probabilities array is None after extracting from output. Returning default scores.")
38
+ return {class_names[0]: 0.0, class_names[1]: 1.0}
39
+
40
+ if probabilities_array.size == 1:
41
+ fake_prob = float(probabilities_array.item())
42
+ elif probabilities_array.size == 2:
43
+ fake_prob = float(probabilities_array[0])
44
+ else:
45
+ logger.warning(f"Unexpected probabilities array shape for binary post-processing: {probabilities_array.shape}. Expected size 1 or 2.")
46
+ return {class_names[0]: 0.0, class_names[1]: 1.0}
47
+
48
+ real_prob = 1.0 - fake_prob # Ensure Fake and Real sum to 1
49
+ return {class_names[0]: fake_prob, class_names[1]: real_prob}
50
+
51
+ def to_float_scalar(value):
52
+ if isinstance(value, np.ndarray):
53
+ return float(value.item()) # Convert numpy array scalar to Python float
54
+ return float(value) # Already a Python scalar or convertible type
55
  import numpy as np
56
  import io
57
  from PIL import Image, ImageFilter, ImageChops