An agent solution which evaluates local models for moderation of image content.
Python
0
3 commits
updated Aug 8, 2026
Local Image Moderation is a Python command-line workbench for comparing image moderation tools on a shared set of local test images. It runs each image through NudeNet, Marqo's NSFW image classifier, ShieldGemma 2, and the OpenAI Moderation API, then records normalized scores and raw model responses.
[!WARNING] This is an experimental project and should not be considered production-ready.
The project was created to explore how locally runnable image-moderation tools
compare with an online moderation API. The benchmark is intentionally direct:
place images in .images, run the CLI, and inspect the generated JSON files in a
timestamped .runs directory.
The CLI scans the .images directory for supported image files and evaluates
each image with the configured moderation tools:
nsfw-image-detection-384, for NSFW image classification.google/shieldgemma-2-4b-it, for sexual, dangerous, and
violence/gore policies.omni-moderation-latest, through the Moderation API.Each tool response is normalized into a shared score shape:
nuditysexual_contentchild_sexual_safetyviolencegoreself_harmweaponsdangerous_activityhate_extremismScores use a 0.0 to 100.0 scale. Categories that a tool does not evaluate
are recorded as null rather than 0.0, so missing coverage is not mistaken
for a safe result.
For each image and tool, the application records:
The command also prints per-image timing progress in a compact format:
Marqo: image-02.jpg (232ms)
ShieldGemma 2: image-02.jpg (20m 14s)
OPENAI_API_KEY environment variable for OpenAI Moderation API calls.HF_TOKEN environment variable for gated Hugging Face model access.google/shieldgemma-2-4b-it.ShieldGemma 2 is much slower and heavier than NudeNet and Marqo on CPU-only hardware. A full run over many images can take many hours.
Create the virtual environment and install the project with development dependencies:
.\scripts\setup-dev.ps1
The setup script expects Python 3.11 at the path configured in
scripts\setup-dev.ps1.
Before using ShieldGemma 2, ensure your Hugging Face token can read public gated repositories you can access. In Hugging Face token permissions, this is the permission named:
Read contents of public gated repos you can access
Place input images in the .images directory, then run from the repository
root:
.\.venv\Scripts\python.exe -m local_image_moderation
The installed console command is equivalent:
.\.venv\Scripts\local-image-moderation.exe
The current default run sends every supported image to all configured tools: NudeNet, Marqo, ShieldGemma 2, and the OpenAI Moderation API.
Each run creates a timestamped folder under .runs:
.runs/
YYYYMMDD-HHMMSS/
nudenet.json
marqo.json
shieldgemma-2.json
openai-moderation.json
Each JSON file contains one model-level result object:
model_name
friendly_name
generated_at
images_directory
results[]
Each item in results contains normalized scores and the raw response for a
single image. The raw response is retained so score mappings can be refined
later without rerunning every model.
The adapters map each tool's native outputs as closely as possible to the shared score categories.
NudeNet maps exposed-body detections to nudity. Other categories are null.
Marqo maps the model's NSFW probability to sexual_content. Other categories
are null.
ShieldGemma 2 maps:
sexual to sexual_contentdangerous to dangerous_activityviolence to both violence and goreOpenAI Moderation maps image-applicable category scores from
omni-moderation-latest:
sexual to both nudity and sexual_contentsexual/minors to child_sexual_safety when image-applicableviolence to violenceviolence/graphic to goreself_harmdangerous_activity and weapons
when image-applicablehate_extremism when image-applicableThe OpenAI adapter checks category_applied_input_types so text-only category
scores are not treated as image moderation results.
Run formatting, linting, type checking, and tests:
.\scripts\check.ps1
This runs:
ruff format .ruff check .pyrightpytestsrc/local_image_moderation/
__main__.py Package entry point for python -m local_image_moderation
cli.py Command-line interface
detectors.py NudeNet, Marqo, ShieldGemma 2, and OpenAI adapters
models.py Shared dataclasses for normalized results
runner.py Image discovery, timing, run folders, and JSON output
tests/
test_smoke.py
scripts/
setup-dev.ps1
check.ps1
This project is intended for comparison and exploration, not as a moderation policy by itself. A flagged or unflagged score should be interpreted in the context of the model's documented coverage and the test image set.
The tools do not cover the same policy areas. Empty scores in one model's output often mean "not evaluated" rather than "safe."
OpenAI API calls may incur usage costs. Hugging Face and PyTorch model downloads can be large. ShieldGemma 2 can be extremely slow on CPU-only hardware.
This project has direct runtime dependencies on third-party Python packages,
including accelerate, nudenet, openai, pillow, timm, torch, and
transformers. It also downloads or accesses third-party model artifacts,
including Marqo nsfw-image-detection-384 and Google
shieldgemma-2-4b-it. See each package and model page for full license,
acceptable-use, and notice terms.
GNU General Public License v3.0. See the LICENSE file for details.
An agent solution which evaluates local models for moderation of image content.
Python
0
3 commits
updated Aug 8, 2026
Local Image Moderation is a Python command-line workbench for comparing image moderation tools on a shared set of local test images. It runs each image through NudeNet, Marqo's NSFW image classifier, ShieldGemma 2, and the OpenAI Moderation API, then records normalized scores and raw model responses.
[!WARNING] This is an experimental project and should not be considered production-ready.
The project was created to explore how locally runnable image-moderation tools
compare with an online moderation API. The benchmark is intentionally direct:
place images in .images, run the CLI, and inspect the generated JSON files in a
timestamped .runs directory.
The CLI scans the .images directory for supported image files and evaluates
each image with the configured moderation tools:
nsfw-image-detection-384, for NSFW image classification.google/shieldgemma-2-4b-it, for sexual, dangerous, and
violence/gore policies.omni-moderation-latest, through the Moderation API.Each tool response is normalized into a shared score shape:
nuditysexual_contentchild_sexual_safetyviolencegoreself_harmweaponsdangerous_activityhate_extremismScores use a 0.0 to 100.0 scale. Categories that a tool does not evaluate
are recorded as null rather than 0.0, so missing coverage is not mistaken
for a safe result.
For each image and tool, the application records:
The command also prints per-image timing progress in a compact format:
Marqo: image-02.jpg (232ms)
ShieldGemma 2: image-02.jpg (20m 14s)
OPENAI_API_KEY environment variable for OpenAI Moderation API calls.HF_TOKEN environment variable for gated Hugging Face model access.google/shieldgemma-2-4b-it.ShieldGemma 2 is much slower and heavier than NudeNet and Marqo on CPU-only hardware. A full run over many images can take many hours.
Create the virtual environment and install the project with development dependencies:
.\scripts\setup-dev.ps1
The setup script expects Python 3.11 at the path configured in
scripts\setup-dev.ps1.
Before using ShieldGemma 2, ensure your Hugging Face token can read public gated repositories you can access. In Hugging Face token permissions, this is the permission named:
Read contents of public gated repos you can access
Place input images in the .images directory, then run from the repository
root:
.\.venv\Scripts\python.exe -m local_image_moderation
The installed console command is equivalent:
.\.venv\Scripts\local-image-moderation.exe
The current default run sends every supported image to all configured tools: NudeNet, Marqo, ShieldGemma 2, and the OpenAI Moderation API.
Each run creates a timestamped folder under .runs:
.runs/
YYYYMMDD-HHMMSS/
nudenet.json
marqo.json
shieldgemma-2.json
openai-moderation.json
Each JSON file contains one model-level result object:
model_name
friendly_name
generated_at
images_directory
results[]
Each item in results contains normalized scores and the raw response for a
single image. The raw response is retained so score mappings can be refined
later without rerunning every model.
The adapters map each tool's native outputs as closely as possible to the shared score categories.
NudeNet maps exposed-body detections to nudity. Other categories are null.
Marqo maps the model's NSFW probability to sexual_content. Other categories
are null.
ShieldGemma 2 maps:
sexual to sexual_contentdangerous to dangerous_activityviolence to both violence and goreOpenAI Moderation maps image-applicable category scores from
omni-moderation-latest:
sexual to both nudity and sexual_contentsexual/minors to child_sexual_safety when image-applicableviolence to violenceviolence/graphic to goreself_harmdangerous_activity and weapons
when image-applicablehate_extremism when image-applicableThe OpenAI adapter checks category_applied_input_types so text-only category
scores are not treated as image moderation results.
Run formatting, linting, type checking, and tests:
.\scripts\check.ps1
This runs:
ruff format .ruff check .pyrightpytestsrc/local_image_moderation/
__main__.py Package entry point for python -m local_image_moderation
cli.py Command-line interface
detectors.py NudeNet, Marqo, ShieldGemma 2, and OpenAI adapters
models.py Shared dataclasses for normalized results
runner.py Image discovery, timing, run folders, and JSON output
tests/
test_smoke.py
scripts/
setup-dev.ps1
check.ps1
This project is intended for comparison and exploration, not as a moderation policy by itself. A flagged or unflagged score should be interpreted in the context of the model's documented coverage and the test image set.
The tools do not cover the same policy areas. Empty scores in one model's output often mean "not evaluated" rather than "safe."
OpenAI API calls may incur usage costs. Hugging Face and PyTorch model downloads can be large. ShieldGemma 2 can be extremely slow on CPU-only hardware.
This project has direct runtime dependencies on third-party Python packages,
including accelerate, nudenet, openai, pillow, timm, torch, and
transformers. It also downloads or accesses third-party model artifacts,
including Marqo nsfw-image-detection-384 and Google
shieldgemma-2-4b-it. See each package and model page for full license,
acceptable-use, and notice terms.
GNU General Public License v3.0. See the LICENSE file for details.