faryalnimra commited on
Commit
4fe453b
·
1 Parent(s): 1dffd8b

Add backend files for deployment

Browse files
Files changed (2) hide show
  1. app.py +408 -0
  2. requirements.txt +0 -0
app.py ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template, url_for
2
+ from flask_cors import CORS
3
+ import torch
4
+ import torch.nn as nn
5
+ from torchvision import models, transforms
6
+ from PIL import Image
7
+ from huggingface_hub import hf_hub_download
8
+ import os
9
+ from mtcnn import MTCNN
10
+ import cv2
11
+ from flask_bcrypt import generate_password_hash, check_password_hash
12
+ from pymongo import MongoClient
13
+ import numpy as np
14
+ from werkzeug.security import generate_password_hash, check_password_hash
15
+ from werkzeug.utils import secure_filename
16
+ import logging
17
+ import matplotlib.pyplot as plt
18
+ import seaborn as sns
19
+ from transformers import AutoImageProcessor, AutoModelForImageClassification # New imports
20
+
21
+ # Setup logging
22
+ logging.basicConfig(level=logging.INFO)
23
+
24
+ app = Flask(__name__, template_folder="templates", static_folder="static")
25
+ CORS(app)
26
+
27
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
28
+ UPLOAD_FOLDER = "static/uploads"
29
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
30
+
31
+ # ------------------- Model Loading Functions -------------------
32
+
33
+ def load_model_from_hf(repo_id, filename, num_classes):
34
+ model_path = hf_hub_download(repo_id=repo_id, filename=filename)
35
+ model = models.convnext_tiny(weights=None)
36
+ in_features = model.classifier[2].in_features
37
+ model.classifier[2] = nn.Linear(in_features, num_classes)
38
+ model.load_state_dict(torch.load(model_path, map_location=device))
39
+ model.to(device)
40
+ model.eval()
41
+ return model
42
+
43
+ # Load the existing deepfake/cheapfake models
44
+ deepfake_model = load_model_from_hf("faryalnimra/DFDC-detection-model", "DFDC.pth", 2)
45
+ cheapfake_model = load_model_from_hf("faryalnimra/ORIG-TAMP", "ORIG-TAMP.pth", 1)
46
+
47
+ # ------------------- New Real/Fake Detector Model -------------------
48
+ # This model determines if the uploaded image is real (label 1) or fake (label 0)
49
+ model_name = "prithivMLmods/Deep-Fake-Detector-Model"
50
+ processor = AutoImageProcessor.from_pretrained(model_name, use_fast=False)
51
+ realfake_detector = AutoModelForImageClassification.from_pretrained(model_name)
52
+ realfake_detector.to(device)
53
+ realfake_detector.eval()
54
+
55
+ # ------------------- Image Preprocessing -------------------
56
+
57
+ transform = transforms.Compose([
58
+ transforms.ToTensor(),
59
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
60
+ ])
61
+
62
+ # ------------------- Face Detector -------------------
63
+
64
+ face_detector = MTCNN()
65
+
66
+ def detect_face(image_path):
67
+ image = cv2.imread(image_path)
68
+ image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
69
+ faces = face_detector.detect_faces(image_rgb)
70
+ face_count = sum(1 for face in faces if face.get("confidence", 0) > 0.90 and face.get("box", [0, 0, 0, 0])[2] > 30)
71
+ return face_count
72
+
73
+ # ------------------- API Endpoint: /predict -------------------
74
+ @app.route("/predict", methods=["POST"])
75
+ def predict():
76
+ if "file" not in request.files:
77
+ return jsonify({"error": "No file uploaded"}), 400
78
+
79
+ file = request.files["file"]
80
+ prediction_type = request.form.get("prediction_type", "real_vs_fake") # default
81
+
82
+ filename = os.path.join(UPLOAD_FOLDER, file.filename)
83
+ file.save(filename)
84
+
85
+ try:
86
+ image = Image.open(filename).convert("RGB")
87
+ image_tensor = transform(image).unsqueeze(0).to(device)
88
+ except Exception as e:
89
+ return jsonify({"error": "Error processing image", "details": str(e)}), 500
90
+
91
+ # --------- CASE 1: ONLY Real/Fake Prediction ----------
92
+ if prediction_type == "real_vs_fake":
93
+ with torch.no_grad():
94
+ inputs = processor(images=image, return_tensors="pt").to(device)
95
+ outputs_realfake = realfake_detector(**inputs)
96
+ pred_label = torch.argmax(outputs_realfake.logits, dim=1).item()
97
+
98
+ if pred_label == 1:
99
+ return jsonify({
100
+ "prediction": "Real",
101
+ "message": "Image is authentic. No further processing.",
102
+ "image_url": url_for("static", filename=f"uploads/{file.filename}")
103
+ })
104
+ else:
105
+ return jsonify({
106
+ "prediction": "Fake",
107
+ "message": "Image is fake, but type (Deepfake/Cheapfake) not determined in this mode.",
108
+ "image_url": url_for("static", filename=f"uploads/{file.filename}")
109
+ })
110
+
111
+ # --------- CASE 2: Deepfake vs Cheapfake Analysis ----------
112
+ elif prediction_type == "deepfake_vs_cheapfake":
113
+ with torch.no_grad():
114
+ deepfake_probs = torch.softmax(deepfake_model(image_tensor), dim=1)[0]
115
+ deepfake_confidence_before = deepfake_probs[1].item() * 100
116
+ cheapfake_confidence_before = torch.sigmoid(cheapfake_model(image_tensor)).item() * 100
117
+
118
+ face_count = detect_face(filename)
119
+ face_factor = min(face_count / 2, 1)
120
+
121
+ if deepfake_confidence_before <= cheapfake_confidence_before:
122
+ adjusted_deepfake_confidence = deepfake_confidence_before * (1 + 0.3 * face_factor)
123
+ adjusted_cheapfake_confidence = cheapfake_confidence_before * (1 - 0.3 * face_factor)
124
+ else:
125
+ adjusted_deepfake_confidence = deepfake_confidence_before
126
+ adjusted_cheapfake_confidence = cheapfake_confidence_before
127
+
128
+ fake_type = "Deepfake" if adjusted_deepfake_confidence > adjusted_cheapfake_confidence else "Cheapfake"
129
+
130
+ return jsonify({
131
+ "prediction": "Fake",
132
+ "fake_type": fake_type,
133
+ "deepfake_confidence_before": f"{deepfake_confidence_before:.2f}%",
134
+ "deepfake_confidence_adjusted": f"{adjusted_deepfake_confidence:.2f}%",
135
+ "cheapfake_confidence_before": f"{cheapfake_confidence_before:.2f}%",
136
+ "cheapfake_confidence_adjusted": f"{adjusted_cheapfake_confidence:.2f}%",
137
+ "faces_detected": face_count,
138
+ "image_url": url_for("static", filename=f"uploads/{file.filename}")
139
+ })
140
+
141
+ # --------- CASE 3: Invalid prediction_type ---------
142
+ else:
143
+ return jsonify({"error": "Invalid prediction_type. Use 'real_vs_fake' or 'deepfake_vs_cheapfake'"}), 400
144
+
145
+ # ------------------- Heatmap Generator and API -------------------
146
+
147
+
148
+
149
+ # Flask setup
150
+
151
+ UPLOAD_FOLDER = "static/uploads"
152
+ HEATMAP_FOLDER = "static/heatmaps"
153
+ ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}
154
+
155
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
156
+ os.makedirs(HEATMAP_FOLDER, exist_ok=True)
157
+
158
+ def allowed_file(filename):
159
+ return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
160
+
161
+ # Load your model
162
+ deepfake_model = load_model_from_hf("faryalnimra/DFDC-detection-model", "DFDC.pth", 2)
163
+ deepfake_model.eval()
164
+
165
+ # Choose the last Conv2D layer
166
+ target_layer = None
167
+ for name, module in deepfake_model.named_modules():
168
+ if isinstance(module, torch.nn.Conv2d):
169
+ target_layer = module
170
+
171
+ # Grad-CAM class
172
+ class GradCAM:
173
+ def __init__(self, model, target_layer):
174
+ self.model = model
175
+ self.target_layer = target_layer
176
+ self.gradients = None
177
+ self.activations = None
178
+ self._register_hooks()
179
+
180
+ def _register_hooks(self):
181
+ def forward_hook(module, input, output):
182
+ self.activations = output.detach()
183
+
184
+ def backward_hook(module, grad_in, grad_out):
185
+ self.gradients = grad_out[0].detach()
186
+
187
+ self.target_layer.register_forward_hook(forward_hook)
188
+ self.target_layer.register_backward_hook(backward_hook)
189
+
190
+ def generate(self, input_tensor, class_idx=None):
191
+ self.model.eval()
192
+ output = self.model(input_tensor)
193
+
194
+ if class_idx is None:
195
+ class_idx = torch.argmax(output, dim=1).item()
196
+
197
+ self.model.zero_grad()
198
+ loss = output[0, class_idx]
199
+ loss.backward()
200
+
201
+ gradients = self.gradients.cpu().numpy()[0]
202
+ activations = self.activations.cpu().numpy()[0]
203
+
204
+ weights = np.mean(gradients, axis=(1, 2))
205
+ cam = np.zeros(activations.shape[1:], dtype=np.float32)
206
+
207
+ for i, w in enumerate(weights):
208
+ cam += w * activations[i, :, :]
209
+
210
+ cam = np.maximum(cam, 0)
211
+ cam = cv2.resize(cam, (input_tensor.size(3), input_tensor.size(2)))
212
+ cam = cam - np.min(cam)
213
+ cam = cam / np.max(cam)
214
+ return cam, output
215
+
216
+ # Preprocessing
217
+ preprocess = transforms.Compose([
218
+ transforms.Resize((224, 224)),
219
+ transforms.ToTensor(),
220
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
221
+ ])
222
+
223
+ gradcam = GradCAM(deepfake_model, target_layer)
224
+
225
+ # Generate heatmap and prediction
226
+ def generate_heatmap(original_image_path, heatmap_save_path):
227
+ img = Image.open(original_image_path).convert("RGB")
228
+ input_tensor = preprocess(img).unsqueeze(0)
229
+
230
+ cam, output = gradcam.generate(input_tensor)
231
+
232
+ # Get prediction
233
+ probabilities = torch.nn.functional.softmax(output, dim=1)[0]
234
+ class_idx = torch.argmax(probabilities).item()
235
+ confidence = probabilities[class_idx].item()
236
+ label = "Fake" if class_idx == 1 else "Real"
237
+
238
+ # Generate heatmap
239
+ heatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)
240
+ heatmap = cv2.GaussianBlur(heatmap, (7, 7), 0)
241
+ heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)
242
+
243
+ img_np = np.array(img.resize((224, 224)))
244
+
245
+ superimposed_img = heatmap * 0.5 + img_np * 0.5
246
+ superimposed_img = np.uint8(superimposed_img)
247
+
248
+ Image.fromarray(superimposed_img).save(heatmap_save_path)
249
+
250
+ return label, confidence
251
+
252
+ # Flask route
253
+ @app.route("/generate_heatmap", methods=["POST"])
254
+ def generate_heatmap_api():
255
+ if "file" not in request.files:
256
+ return jsonify({"error": "No file uploaded"}), 400
257
+
258
+ file = request.files["file"]
259
+
260
+ if file.filename == "" or not allowed_file(file.filename):
261
+ return jsonify({"error": "Invalid file type. Allowed types are .png, .jpg, .jpeg"}), 400
262
+
263
+ filename = secure_filename(file.filename)
264
+ original_image_path = os.path.join(UPLOAD_FOLDER, filename)
265
+
266
+ try:
267
+ file.save(original_image_path)
268
+ except Exception as e:
269
+ return jsonify({"error": "Failed to save the file"}), 500
270
+
271
+ heatmap_filename = f"heatmap_{filename}"
272
+ heatmap_path = os.path.join(HEATMAP_FOLDER, heatmap_filename)
273
+
274
+ label, confidence = generate_heatmap(original_image_path, heatmap_path)
275
+
276
+ return jsonify({
277
+ "original_image_url": url_for("static", filename=f"uploads/{filename}", _external=True),
278
+ "heatmap_image_url": url_for("static", filename=f"heatmaps/{heatmap_filename}", _external=True),
279
+ "prediction": label,
280
+ "confidence": f"{confidence:.2f}"
281
+ })
282
+
283
+ # To run:
284
+ # if __name__ == "__main__":
285
+ # app.run(debug=True)
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+ #MongoDB Atlantis from flask import Flask, request, jsonify
294
+
295
+
296
+ # MongoDB connection
297
+ client = MongoClient('mongodb+srv://fakecatcherai:[email protected]/?retryWrites=true&w=majority&appName=Cluster0')
298
+ db = client['fakecatcherDB']
299
+ users_collection = db['users']
300
+ contacts_collection = db['contacts']
301
+
302
+ def is_valid_password(password):
303
+ if (len(password) < 8 or
304
+ not re.search(r'[A-Z]', password) or
305
+ not re.search(r'[a-z]', password) or
306
+ not re.search(r'[0-9]', password) or
307
+ not re.search(r'[!@#$%^&*(),.?":{}|<>]', password)):
308
+ return False
309
+ return True
310
+
311
+ @app.route('/Register', methods=['POST'])
312
+ def register():
313
+ data = request.get_json()
314
+ first_name = data.get('firstName')
315
+ last_name = data.get('lastName')
316
+ email = data.get('email')
317
+ password = data.get('password')
318
+
319
+ if users_collection.find_one({'email': email}):
320
+ logging.warning(f"Attempted register with existing email: {email}")
321
+ return jsonify({'message': 'Email already exists!'}), 400
322
+
323
+ # ✅ Password constraints check
324
+ if not is_valid_password(password):
325
+ return jsonify({'message': 'Password must be at least 8 characters long and include uppercase, lowercase, number, and special character.'}), 400
326
+
327
+ hashed_pw = generate_password_hash(password)
328
+ users_collection.insert_one({
329
+ 'first_name': first_name,
330
+ 'last_name': last_name,
331
+ 'email': email,
332
+ 'password': hashed_pw
333
+ })
334
+
335
+ logging.info(f"New user registered: {first_name} {last_name}, Email: {email}")
336
+ return jsonify({'message': 'Registration successful!'}), 201
337
+
338
+ # 🔵 Login Route
339
+ @app.route('/Login', methods=['POST'])
340
+ def login():
341
+ data = request.get_json()
342
+ email = data.get('email')
343
+ password = data.get('password')
344
+
345
+ # Check if the user exists
346
+ user = users_collection.find_one({'email': email})
347
+ if not user or not check_password_hash(user['password'], password):
348
+ logging.warning(f"Failed login attempt for email: {email}")
349
+ return jsonify({'message': 'Invalid email or password!'}), 401
350
+
351
+ logging.info(f"User logged in successfully: {email}")
352
+ return jsonify({'message': 'Login successful!'}), 200
353
+ @app.route('/ForgotPassword', methods=['POST'])
354
+ def forgot_password():
355
+ data = request.get_json()
356
+ email = data.get('email')
357
+ new_password = data.get('newPassword')
358
+ confirm_password = data.get('confirmPassword')
359
+
360
+ # Check if passwords match
361
+ if new_password != confirm_password:
362
+ logging.warning(f"Password reset failed. Passwords do not match for email: {email}")
363
+ return jsonify({'message': 'Passwords do not match!'}), 400
364
+
365
+ # Check if the user exists
366
+ user = users_collection.find_one({'email': email})
367
+ if not user:
368
+ logging.warning(f"Password reset attempt for non-existent email: {email}")
369
+ return jsonify({'message': 'User not found!'}), 404
370
+
371
+ # Hash the new password and update it
372
+ hashed_pw = generate_password_hash(new_password)
373
+ users_collection.update_one({'email': email}, {'$set': {'password': hashed_pw}})
374
+
375
+ logging.info(f"Password successfully reset for email: {email}")
376
+ return jsonify({'message': 'Password updated successfully!'}), 200
377
+
378
+
379
+
380
+
381
+
382
+
383
+ # 🟣 Contact Form Route (React Page: Contact)
384
+ @app.route('/Contact', methods=['POST'])
385
+ def contact():
386
+ data = request.get_json()
387
+ email = data.get('email')
388
+ query = data.get('query')
389
+ message = data.get('message')
390
+
391
+ # Check if all fields are provided
392
+ if not email or not query or not message:
393
+ logging.warning(f"Incomplete contact form submission from email: {email}")
394
+ return jsonify({'message': 'All fields are required!'}), 400
395
+
396
+ # Insert the contact data
397
+ contact_data = {
398
+ 'email': email,
399
+ 'query': query,
400
+ 'message': message
401
+ }
402
+ contacts_collection.insert_one(contact_data)
403
+
404
+ logging.info(f"Contact form submitted successfully from email: {email}")
405
+ return jsonify({'message': 'Your message has been sent successfully.'}), 200
406
+
407
+ if __name__ == '__main__':
408
+ app.run(debug=True)
requirements.txt ADDED
Binary file (1.07 kB). View file