All pages

Standalone moderation and dataset auditing

argus-curator 0.15.2.dev2+g97021d6 @ 97021d6PyPI (opens in new tab)Source (opens in new tab)Changelog (opens in new tab)

Imported from docs/moderation.md on argus-curator's dev branch at that commit. Fixes go upstream, not here.

Everything curator records about an image is usable without the rest of the suite. A scan never modifies the source folder; it produces a per-image record (--json, --csv, or GET /scan/{scan_id}), and an export applies filters to it, giving every excluded image a keep_reason. That record doubles as an audit trail: you can show which images were removed and on what grounds.

CheckWhat is recordedFilter on it
Technical qualitysharpness, resolution, aspect ratio, compression artifacts--min-score, hard-filter reject_reason
Near-duplicatespHash groups, best representative kept--keep-similar to keep the rest
Peopleface count, identity clusters, head pose--face-clusters, --pose
Apparent ageMiVOLO age in years and its bracket, per face (needs mivolo-convert)--min-age / --max-age, --age
Apparent genderMiVOLO gender with its probability, pooled per identity--gender, --min-attr-conf
SettingPlaces365 scene top-k, indoor/outdoor--scene, --environment
ClothingFashionpedia garments: type, colour, cut off at the frame edge--garment, --exclude-garment, --uncropped
NudityNudeNet detections per body-part class, exposed score (needs a NudeNet server)--nsfw-threshold, --nsfw-label
PolicySingGuard verdict and the rules that fired: sexual content, crime, gore and self-harm, … (needs a SingGuard server)--guard-unsafe, --guard-rules

A typical moderation pass is scan, review, act:

# 1. Scan and report only: nothing is copied, moved or deleted.
argus-curator scan /data/inbox --mivolo --scenes --garments --csv report.csv --json scan.json

# 2. Review report.csv: one row per image with every tag and its confidence.

# 3. Keep only images whose primary face reads as clearly adult. --min-score 0
#    and --keep-similar stop score and duplicates from removing anything, so
#    among the images that passed the hard filters age is the only criterion.
#    An image that failed them is never kept, and shows its own reject reason.
#    Images where no age could be estimated (no face found) are excluded as
#    `face-attributes-missing`: the filter fails closed rather than letting
#    unknowns through.
argus-curator scan /data/inbox --mivolo --min-age 25 \
    --min-score 0 --keep-similar \
    --copy-to /data/approved --csv decisions.csv

decisions.csv then lists every image with keep and its keep_reason (face-age-out-of-range, face-attributes-missing, garment-excluded, …), which is the list to send for human review or to quarantine. The same scans and filters are available over HTTP (POST /scan/folder, GET /scan/{scan_id}, POST /export; see HTTP API), so curator can sit in front of an upload or moderation queue.

The under-18 rule (minor-exposed)

One exclusion is not a filter you choose. When MiVOLO reads any face or body in the image as under 18 and NudeNet (or EraX) gives it an exposed score of at least 0.2, the image is never kept or exported, whatever the request says, a named selection included. Its keep reason is minor-exposed. The server also refuses to serve the original file (GET /image), and lets POST /scan/{id}/delete remove it without --allow-delete.

Ages come from every face (MiVOLO, with or without the body) and from people found with no face (body-only MiVOLO). A FairFace age bracket does not count. Covered parts, faces, belly, feet and chest are not exposure.

ARGUS_CURATOR_MINOR_EXPOSED (or --minor-exposed on scan and serve) picks how the age and the exposure have to meet:

ValueFlags the image when
frame (default)a young age and an exposure are both in the frame. This is argus-darkroom's rule.
overlapas frame, and the exposure also overlaps a young person's box (person boxes, else face boxes), or overlaps no box at all. An exposure that overlaps only adults does not flag. It still flags any exposure when a young face has no person box, or a young age has no box to compare.

frame excludes more images: a group photo with a child in it and an exposed adult is excluded too. Set overlap to get the rule curator used up to 0.15.1. Any other value is refused: scan and serve exit with status 2 and the server does not start, rather than falling back to either rule. GET /health reports the rule in use as minor_exposed.

Limits: read before relying on it for moderation

  • Content checks need their servers, and both miss things. NudeNet detects exposed body parts only, and misses stylised, partial or unusual poses; the optional EraX sidecar is a second pass on those misses. SingGuard judges a whole policy (violence, gore, self-harm, …) but gives a verdict without a confidence, so nothing is "near the threshold" to review. Set NSFW thresholds conservatively and have a person review flagged and borderline images.
  • Age and gender are model estimates from appearance, not facts about anyone. Age estimates are typically off by a few years, more so from the face alone (the default). --mivolo-body adds each face's body from InsightFace's person detector; a person with no face still gets a body-only estimate. Filter with a margin above any legal threshold (--min-age 25 rather than 18) and send borderline images to a person. See Face attributes.
  • Only the primary face of each image is filtered on; other faces are recorded per face (faces[].attributes) but do not exclude the image.
  • Scene and garment models make mistakes too; see the measured accuracy in Scene tags and Garments.

Curator is a filter and an audit trail to put in front of human review, not a replacement for it.