usamaalam commited on
Commit
e3ec511
Β·
1 Parent(s): 0ceb664

Fix ELA preprocessing to match training notebook (amplify + JPEG + tf.image decode)

Browse files
Files changed (2) hide show
  1. .gitignore +3 -0
  2. app.py +29 -16
.gitignore CHANGED
@@ -41,3 +41,6 @@ Thumbs.db
41
  kaggle.json
42
  model/*.h5
43
  model/*.keras
 
 
 
 
41
  kaggle.json
42
  model/*.h5
43
  model/*.keras
44
+
45
+ # --- HF Hub model cache (downloaded at runtime) ---
46
+ .cache/
app.py CHANGED
@@ -12,21 +12,32 @@ ELA_QUALITY = 90
12
  ELA_SCALE = 15
13
 
14
  # ── Forensic Utilities ───────────────────────────────────────────────────────
15
- def compute_ela(original, quality=ELA_QUALITY, scale=ELA_SCALE):
 
 
 
 
16
  original = original.convert('RGB')
17
  buf = io.BytesIO()
18
  original.save(buf, 'JPEG', quality=quality)
19
  buf.seek(0)
20
- compressed = Image.open(buf)
21
-
22
- ela_image = ImageChops.difference(original, compressed)
23
- # Must match train.py exactly: ImageChops.multiply divides by 255 internally,
24
- # so this attenuates (diff * scale / 255) rather than amplifies. The model was
25
- # trained on these dark ELA images β€” using Brightness.enhance here breaks it.
26
- ela_image = ImageChops.multiply(
27
- ela_image, Image.new('RGB', ela_image.size, (scale, scale, scale))
28
- )
29
- return ela_image
 
 
 
 
 
 
 
30
 
31
  def get_gradcam(model, input_data):
32
  # Dynamically find the last conv layer
@@ -144,11 +155,13 @@ if uploaded_file is not None:
144
  # Load model
145
  m3 = load_trained_model()
146
 
147
- # RGB: preprocess_input handles normalization inside the branch
148
- # ELA: Rescaling(1/255) is inside the branch, so pass raw [0,255]
149
- rgb_in = np.array(image.resize(IMG_SIZE)).astype(np.float32)[np.newaxis]
150
- ela_img = compute_ela(image)
151
- ela_in = np.array(ela_img.resize(IMG_SIZE)).astype(np.float32)[np.newaxis]
 
 
152
  input_data = [rgb_in, ela_in]
153
 
154
  # Inference
 
12
  ELA_SCALE = 15
13
 
14
  # ── Forensic Utilities ───────────────────────────────────────────────────────
15
+ def compute_ela_jpeg_bytes(original, quality=ELA_QUALITY, scale=ELA_SCALE):
16
+ """Reproduce the TRAINING NOTEBOOK's cached ELA exactly (not train.py, which
17
+ is stale). Steps: recompress at `quality`, diff, Brightness.enhance(scale) to
18
+ amplify, then encode to JPEG bytes (PIL default quality 75) β€” the notebook
19
+ cached ELA to disk as JPEG, so the model saw JPEG-compressed ELA."""
20
  original = original.convert('RGB')
21
  buf = io.BytesIO()
22
  original.save(buf, 'JPEG', quality=quality)
23
  buf.seek(0)
24
+ recompressed = Image.open(buf).convert('RGB')
25
+
26
+ ela_image = ImageChops.difference(original, recompressed)
27
+ ela_image = ImageEnhance.Brightness(ela_image).enhance(scale)
28
+
29
+ out = io.BytesIO()
30
+ ela_image.save(out, 'JPEG')
31
+ return out.getvalue()
32
+
33
+
34
+ def ela_tensor(jpeg_bytes):
35
+ """Decode ELA exactly as the notebook's decode_ela: tf.image.decode_jpeg +
36
+ tf.image.resize (bilinear) + /255. The model is ELA-driven, so this must
37
+ match training's decode path byte-for-byte."""
38
+ img = tf.image.decode_jpeg(jpeg_bytes, channels=3)
39
+ img = tf.image.resize(img, IMG_SIZE)
40
+ return (tf.cast(img, tf.float32) / 255.0).numpy()
41
 
42
  def get_gradcam(model, input_data):
43
  # Dynamically find the last conv layer
 
155
  # Load model
156
  m3 = load_trained_model()
157
 
158
+ # Match the training notebook exactly. Both branches normalized to [0,1]
159
+ # (the model has no preprocess_input/Rescaling layers). RGB: PIL LANCZOS.
160
+ # ELA: bright + JPEG, decoded/resized via tf.image (bilinear).
161
+ rgb_in = np.array(image.resize(IMG_SIZE, Image.LANCZOS), np.float32)[np.newaxis] / 255.0
162
+ ela_bytes = compute_ela_jpeg_bytes(image)
163
+ ela_in = ela_tensor(ela_bytes)[np.newaxis]
164
+ ela_img = Image.open(io.BytesIO(ela_bytes)).convert('RGB') # for display
165
  input_data = [rgb_in, ela_in]
166
 
167
  # Inference