Veritas is a Streamlit web app that runs published image-forensics techniques on a JPEG or PNG and reports what each one measured: headline numbers, findings, maps and each test's stated limitations.
Every result is an indicator, not proof. Each test detects one kind of trace under stated conditions; a clean result means only that this test found no inconsistency at its sensitivity. Veritas produces no authenticity score and no overall verdict: no combination of these tests can certify that an image is unedited. Its output is not evidence of authenticity and the hash ledger is not a legal chain of custody. Combine several independent tests with the image's context before drawing conclusions.
Veritas deliberately uses classical (non-learned) methods so it runs on CPU within Streamlit Community Cloud's memory limits and ships under a permissive licence. Trained networks such as TruFor / Noiseprint++ (Guillaro et al., CVPR 2023), CAT-Net (Kwon et al., IJCV 2022) and CLIP-based synthetic-image detectors (Cozzolino et al., CVPR-W 2024) currently outperform these methods, especially after recompression and for AI-generated images. They need GPUs or several GB of RAM and their weights are mostly licensed for research use only. For high-stakes casework, use them alongside β or instead of β this tool.
The app has 13 tabs. Each technique implements a published method; its
guide in Descriptions/ (also shown in the app under "How
this technique works") gives the method, the measured benchmark numbers and
the limitations.
| Tab | Technique | Published method | Guide |
|---|---|---|---|
| ELA | Error Level Analysis | Krawetz 2007; error normalised by local texture energy | ELA.md |
| Metadata | EXIF / XMP / PNG text consistency, C2PA Content Credentials | CIPA Exif 2.32, Adobe XMP, C2PA 2.x (c2pa-python); Kee, Johnson & Farid 2011 |
Metadata.md |
| Histogram | Contrast-enhancement fingerprint | Stamm & Liu 2010 (histogram-DFT HF energy) | Histogram.md |
| Noise | Noise-residual consistency | Splicebuster (Cozzolino, Poggi & Verdoliva 2015): residual co-occurrence features + EM | Noise_Ghost.md |
| JPEG | Quantization tables | IJG quality estimate and standard-table test (Farid 2006, Kornblum 2008) | Quantization.md |
| JPEG | Double-JPEG localization | Bianchi & Piva 2012 (aligned and non-aligned) | Quantization.md |
| JPEG | JPEG ghosts | Farid 2009, including recompression at an off-grid offset | Noise_Ghost.md |
| Copy-Move | Copy-move forgery detection | flip-aware SIFT/g2NN + RANSAC affine (Amerini et al. 2011) and dense Zernike/PatchMatch (Cozzolino, Poggi & Verdoliva 2015), ZNCC verification | CMFD.md |
| PRNU | Sensor fingerprint | LukΓ‘Ε‘/Fridrich/Goljan 2006, Goljan et al. 2009 (PCE > 60), Chierchia et al. 2014 (correlation predictor + MRF splice localisation) | PRNU.md |
| Frequency | Power spectrum | Radial 1/fΒ² slope and HF share (Field 1987; Torralba & Oliva 2003) | Frequency.md |
| Frequency | JPEG block grid | Block artifact grid (Li, Yuan & Yu 2009): grid origin and misaligned regions | Frequency.md |
| Resampling | Rescaling / rotation | Kirchner 2008 p-map spectrum with JPEG/Nyquist notching (Kirchner & Gloe 2009) fused with a derivative-projection detector (Gallagher 2005; Mahdian & Saic 2008), windowed localisation, NN duplication test | Resampling.md |
| Synthetic traces | Experimental: periodic noise-residual peaks | Corvi et al. 2023; Durall et al. 2020. Measurements only, no AI-image verdict | Deepfake.md |
| Steganography | LSB-replacement payload | Weighted Stego (Ker & BΓΆhme 2008), SPA (Dumitrescu 2003), RS (Fridrich 2001), PoV chiΒ² (Westfeld & Pfitzmann 1999) | Steganography.md |
| Hash ledger | File / pixel identity and visual similarity | SHA-256, pixel SHA-256, pHash/dHash/aHash; hash-chained per-session ledger, HMAC-signed export | Hash_Verification.md |
| About | What the app does and does not claim |
The synthetic-traces tab cannot tell whether an image is AI-generated: modern diffusion models, resizing and recompression remove the traces it measures, and their absence says nothing.
- Original bytes only. Every technique reads the uploaded file as stored. Nothing is downscaled and re-saved before analysis (re-encoding destroys compression, noise, resampling and LSB traces).
- Copy-move runs on an in-memory downscale to 2048 px on the long side for very large images and reports the analysed size. PRNU analyses up to 4096 px per side, centre-cropping larger images (never resizing); references are cropped identically.
- Per-session storage. Uploads go to a temporary directory private to the browser session, under random file names, so two visitors never see each other's files. The hash ledger also lives only in the session; export it to keep it.
- One result format. Every analysis returns the same result dictionary
(status, summary, findings, metrics, images, tables, limitations), rendered
by one function in
app.py. See docs/API.md.
assets/sample images/sampleImg.jpeg is loaded when nothing is uploaded. It
is a known fabricated example, not a clean reference: its C2PA manifest
declares compositeWithTrainedAlgorithmicMedia, the clouds are copy-moved,
and its EXIF thumbnail no longer matches the image. That makes it a good demo
(Metadata, Copy-Move and several JPEG tabs report findings), but do not use
it to judge what a clean photo looks like.
assets/examples/ holds nine images, each with one known edit or property
that a technique detects: a recompressed patch (ELA), a JPEG ghost, a cloned
block (Copy-Move), a 1.5x upscale (Resampling), a contrast stretch
(Histogram), an LSB payload (Steganography), contradictory EXIF (Metadata),
a smoothed region (Noise) and a camera match (PRNU). Pick one in the
sidebar's "Or try an example" box; the app says what was done and which tab
detects it, and the PRNU example loads its reference photos itself.
The first seven are edits of the bundled sample. Noise and PRNU need real
full-resolution camera noise, so they use four CC0 iPhone 5c photos from
Wikimedia Commons (credits in assets/examples/source/CREDITS.md).
python scripts/make_examples.py rebuilds everything and exits non-zero if a
technique stops catching its example.
- Python 3.10 (pinned in
.python-version;requirements.txtpins the versions the tests were run against) - A modern browser
git clone https://github.com/CodeRafay/Forensic-Image-Analysis-Toolkit.git
cd Forensic-Image-Analysis-Toolkit
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS/Linux
source .venv/bin/activate
pip install -r requirements.txt
streamlit run app.pyThe app opens at http://localhost:8501.
The suite uses the standard-library unittest runner and needs nothing beyond
requirements.txt:
python -m unittest discover -s tests -t .142 tests: one tests/test_<module>.py per analysis module (seeded synthetic
benchmarks that pin detection and false-alarm rates) plus
tests/test_contract.py, which runs every entry point on JPEG, PNG,
grayscale, RGBA, palette, 16Γ16, 1Γ1 and flat images and checks the result
contract. A full run takes a few minutes.
Forensic-Image-Analysis-Toolkit/
βββ app.py # Streamlit app: 13 tabs, one shared renderer
βββ requirements.txt # Pinned runtime dependencies
βββ requirements-dev.txt # Optional linters/tools (not needed for tests)
βββ .python-version # 3.10 (Streamlit Cloud / pyenv)
βββ README.md CHANGELOG.md CONTRIBUTING.md LICENSE
βββ projectSetup.md # Setup notes
βββ TECHNIQUE_DESCRIPTIONS_USER_GUIDE.md
βββ pytest.ini # Optional; pytest is not required
βββ .pre-commit-config.yaml
βββ .streamlit/config.toml # Theme, headless server, showErrorDetails="type"
β
βββ analysis/
β βββ __init__.py # Lazy sub-module imports
β βββ util.py # Result contract, decoding, JPEG grid (BAG) helpers
β βββ ela.py # analyze_ela
β βββ metadata_analysis.py # analyze_metadata, read_c2pa
β βββ histogram_analysis.py # analyze_histogram
β βββ noise_map.py # analyze_noise
β βββ quant_table.py # analyze_quantization_table
β βββ double_jpeg.py # analyze_double_jpeg
β βββ jpeg_ghost.py # analyze_jpeg_ghost
β βββ cmfd.py # detect_copy_move
β βββ prnu.py # analyze_prnu
β βββ frequency_analysis.py # analyze_spectrum, analyze_blocking
β βββ resampling_detector.py # detect_resampling
β βββ deepfake_detector.py # analyze_synthetic_traces (Experimental)
β βββ steganography_detection.py # analyze_lsb
β βββ hash_verification.py # hashes + per-session ledger
β
βββ Descriptions/ # In-app technique guides (one per tab)
β βββ ELA.md Metadata.md Histogram.md Noise_Ghost.md Quantization.md
β βββ CMFD.md PRNU.md Frequency.md Resampling.md Deepfake.md
β βββ Steganography.md Hash_Verification.md
β
βββ docs/
β βββ API.md # Result contract and every entry point
β βββ DEPLOYMENT.md
β βββ Documentation.md # Project report with UML diagrams
β βββ PROJECT_SUMMARY.md
β βββ TECHNIQUES.md
β
βββ tests/ # 142 tests
β βββ test_contract.py # Every entry point x every input format
β βββ test_ela.py test_metadata.py test_histogram_analysis.py
β βββ test_noise_map.py test_quant_table.py test_double_jpeg.py
β βββ test_jpeg_ghost.py test_cmfd.py test_prnu.py
β βββ test_frequency_analysis.py test_resampling_detector.py
β βββ test_deepfake_detector.py test_steganography_detection.py
β βββ test_hash_verification.py
β
βββ assets/
βββ style.css
βββ sample images/sampleImg.jpeg # Fabricated demo image (see above)
βββ examples/ # One known edit per technique (see above)
Streamlit Community Cloud:
- Push to GitHub, create a new app at share.streamlit.io, main file
app.py. - Python 3.10 is selected from
.python-version; dependencies install from the pinnedrequirements.txt(no system packages needed:opencv-python-headlessandc2pa-pythonship wheels). - Optional: add
LEDGER_KEY = "<random secret>"under App settings β Secrets. Ledger exports are then signed with HMAC-SHA256 and imports verify the signature; without it exports carry a plain SHA-256 digest that only detects accidental change.
The ledger is per session: it is not shared between visitors and is lost when the session ends unless exported. Docker and other hosts: see docs/DEPLOYMENT.md.
See CONTRIBUTING.md. New techniques must return the result
contract, be added to tests/test_contract.py, and have their thresholds
calibrated on a seeded benchmark.
BSD 3-Clause, see LICENSE.
- Author: CodeRafay (@CodeRafay)
- Repository: Forensic-Image-Analysis-Toolkit