Spaces:
Running
Running
github-actions[bot]
Deploy from GitHub 1af737237f5d10dfa9cf12849d98f47c3b30ce37 (filtered: code + Dockerfile + README + NOTICES only)
a2f55b2 Download src/deepfake_scanner/preprocess.py from Veridicate/scanner: direct link, hf CLI and curl.
- Browser
- Download file 2.01 kB
-
https://huggingface.co/spaces/Veridicate/scanner/resolve/main/src/deepfake_scanner/preprocess.py
- Command line
-
hf download hf://spaces/Veridicate/scanner/src/deepfake_scanner/preprocess.py
-
curl -L -o preprocess.py https://huggingface.co/spaces/Veridicate/scanner/resolve/main/src/deepfake_scanner/preprocess.py
2.01 kB
| """ | |
| Image decoding. | |
| In the new architecture, each detector does its own tensor preprocessing | |
| (CLIP needs CLIP's specific resize/normalise; frequency detectors don't | |
| resize at all). This module just turns raw bytes into a validated PIL Image | |
| that all detectors can consume. | |
| """ | |
| from __future__ import annotations | |
| from io import BytesIO | |
| from PIL import Image, ImageOps, UnidentifiedImageError | |
| # Pillow's default decompression-bomb guard is ~89 MP. Tighten it: a 25 MP | |
| # image is more than enough for any consumer photo, and rejecting larger | |
| # inputs caps memory usage on the inference server. | |
| Image.MAX_IMAGE_PIXELS = 25_000_000 | |
| class InvalidImageError(ValueError): | |
| """Raised when the bytes do not decode as a usable image.""" | |
| def decode(image_bytes: bytes) -> Image.Image: | |
| """Decode raw bytes into an RGB PIL image. Raises InvalidImageError on failure.""" | |
| try: | |
| image = Image.open(BytesIO(image_bytes)) | |
| image.load() # Force decode now so we catch errors here, not later. | |
| except (UnidentifiedImageError, OSError) as exc: | |
| raise InvalidImageError(f"Could not decode image: {exc}") from exc | |
| except Image.DecompressionBombError as exc: | |
| raise InvalidImageError(f"Image too large: {exc}") from exc | |
| # Phone cameras almost always store the sensor readout unrotated and record | |
| # the intended rotation in the EXIF Orientation tag — 9 of the first 13 real | |
| # test photos (Galaxy S23 Ultra) came through as orientation 6, i.e. stored | |
| # sideways. Every viewer applies that tag, so a user who uploads a portrait | |
| # photo sees it upright and assumes we scanned it upright. Without this call | |
| # the detector was seeing it rotated 90°, which is both wrong for users and | |
| # would have silently corrupted the P0 false-positive measurement. | |
| # exif_transpose is a no-op when the tag is absent or already upright. | |
| image = ImageOps.exif_transpose(image) | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| return image | |