Detects AI-generated or AI-edited images using signal-processing artifact analysis — no ML model.
Project description
Minosse
Detects AI-generated or AI-edited images using three complementary, hand-crafted artifact signals — no ML model, just signal processing.
Minosse looks for tell-tale traces that diffusion models and neural upsamplers leave behind: ringing halos around text, noise in flat regions that shouldn't have any, and a synthetic signature in the high-frequency spectrum.
Signals
| Signal | What it catches | How it works |
|---|---|---|
| ringing | Diffusion VAE decoder halos | Radial profile of band-pass energy vs. distance from strong edges. Real edges decay fast; ringing persists 10–40 px out. |
| noisy-flats | AI "film grain" on solid panels | RMS residual noise inside coarse-flat regions. Real screenshots are truly flat; JPEG smooths photo flats. |
| synthetic-noise | Upsampled / generated noise | Spectral energy at the Nyquist ring vs. mid frequencies. Real sensor noise stays hot at the highest frequencies; synthetic noise dies off. |
An image is flagged if any signal fires; the fired signals are reported along with the raw feature values.
Quick start
python -m venv .venv
.venv/bin/pip install -r requirements.txt
# Analyse a single image
.venv/bin/python -m minosse path/to/image.png
# Analyse multiple images with visual proof sheets
.venv/bin/python -m minosse --proof results/ image1.png image2.jpg
--proof output
The --proof flag writes <name>_proof.png: a side-by-side sheet with
the original image next to one panel per fired signal, with the artifact
evidence burned in as a red heatmap.
Heat is scaled absolutely (calibrated to the detection thresholds), so clean images render dark — the glow itself is the proof. For ringing, the map only shows oscillation in regions that should be smooth (8–40 px from strong edges, locally flat at coarse scale), so legitimate texture doesn't light up.
Python API
from minosse import analyze, analyze_path, Result
# From a file path
r = analyze_path("image.png")
print(r.verdict) # "likely AI-generated/edited" or "likely clean"
print(r.reasons) # e.g. ["ringing", "synthetic-noise"]
print(r.decay) # ringing feature value
print(r.vhf_mf) # spectral ratio feature value
print(r.flat_noise) # flat-region noise feature value
print(r.resid_std) # overall residual noise
# From a PIL Image
from PIL import Image
img = Image.open("image.png")
r = analyze(img)
Result fields
| Field | Type | Description |
|---|---|---|
verdict |
str |
"likely AI-generated/edited" or "likely clean" |
reasons |
list[str] |
Fired signal names: ringing, noisy-flats, synthetic-noise |
score |
float |
Confidence score [0, 1] (0.75 per firing signal, capped at 1.0) |
decay |
float |
Ringing persistence: mid-distance / near-edge band energy |
halo_vs_bg |
float |
Mid-distance energy vs. image background |
vhf_mf |
float |
Spectral ratio: Nyquist ring / mid frequencies |
resid_std |
float |
Standard deviation of the noise residual |
flat_noise |
float |
RMS residual inside coarse-flat regions |
Visual proof from Python
from minosse import analyze, evidence
from PIL import Image
img = Image.open("image.png")
r = analyze(img)
sheet = evidence.render(img, r)
sheet.save("proof.png")
Project structure
minosse/
├── minosse/
│ ├── __init__.py # Public API: analyze, analyze_path, Result
│ ├── __main__.py # python -m minosse entry point
│ ├── cli.py # Argument parsing and CLI loop
│ ├── detector.py # Feature extraction + threshold logic
│ └── evidence.py # Visual proof sheet renderer
├── results/ # Default --proof output directory
├── requirements.txt # numpy, pillow, scipy
└── README.md
How it works (briefly)
- Preprocessing — the image is converted to RGB and downscaled if its longest edge exceeds 1600 px.
- Feature extraction (
detector._features()) — three independent signal-processing pipelines compute scalar features from the grayscale luminance. - Thresholding — each feature is compared against a tuned threshold; if it exceeds the threshold the corresponding signal is flagged.
- Verdict — if any signal fires, the image is marked likely AI-generated/edited; otherwise it's likely clean.
For a deeper explanation of each signal, see docs/signals.md.
Caveats
The thresholds are calibrated on a small set of examples and should be re-validated as more AI samples are added. See docs/tuning.md for instructions on re-tuning.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file minosse-0.1.0.tar.gz.
File metadata
- Download URL: minosse-0.1.0.tar.gz
- Upload date:
- Size: 11.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e734b5d47fc398b7c2372fc03960877db639a6af0a048fdea63657584592b6a1
|
|
| MD5 |
18d64a9e860b5b0a39057165c3b4bb52
|
|
| BLAKE2b-256 |
7fa4c2c1a30f1ca5fad117efcd4ce6217fb2f66d7c7003e2f51414e36af9d2b7
|
Provenance
The following attestation bundles were made for minosse-0.1.0.tar.gz:
Publisher:
publish.yml on cesp99/Minosse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minosse-0.1.0.tar.gz -
Subject digest:
e734b5d47fc398b7c2372fc03960877db639a6af0a048fdea63657584592b6a1 - Sigstore transparency entry: 2187899290
- Sigstore integration time:
-
Permalink:
cesp99/Minosse@c87d796e52879ebb41b24beee41b694e0ddb8914 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/cesp99
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c87d796e52879ebb41b24beee41b694e0ddb8914 -
Trigger Event:
push
-
Statement type:
File details
Details for the file minosse-0.1.0-py3-none-any.whl.
File metadata
- Download URL: minosse-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4530ed8dbb3b2771181b4447d23a511d6ed7d76577a7661b1327d63fdc9f371
|
|
| MD5 |
a8fbf3094f087721877cf9aeb79021ba
|
|
| BLAKE2b-256 |
5fe0f2e4f2af6512d44a755beb683fbd2e845b4398d095cf40696264425cbb1d
|
Provenance
The following attestation bundles were made for minosse-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on cesp99/Minosse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minosse-0.1.0-py3-none-any.whl -
Subject digest:
d4530ed8dbb3b2771181b4447d23a511d6ed7d76577a7661b1327d63fdc9f371 - Sigstore transparency entry: 2187899291
- Sigstore integration time:
-
Permalink:
cesp99/Minosse@c87d796e52879ebb41b24beee41b694e0ddb8914 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/cesp99
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c87d796e52879ebb41b24beee41b694e0ddb8914 -
Trigger Event:
push
-
Statement type: