- Python 78.1%
- HTML 21.1%
- Dockerfile 0.6%
- Go Template 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .agents | ||
| cache | ||
| docs | ||
| helm/culler | ||
| logs | ||
| models | ||
| output | ||
| scripts | ||
| src/culler | ||
| tests | ||
| utils | ||
| .dockerignore | ||
| .gitignore | ||
| CHANGELOG.md | ||
| CODE_OF_CONDUCT.md | ||
| config.yml | ||
| CONTRIBUTING.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| main.py | ||
| pyproject.toml | ||
| README.md | ||
| requirements.txt | ||
| run_dashboard.py | ||
| SECURITY.md | ||
| SUPPORT.md | ||
| VERSION | ||
Local AI Culling
Local AI Culling is an open-source, cross-platform, completely offline,
privacy-first AI-assisted image culling application built specifically for
professional photographers. It analyzes your raw photo shoots, groups bursts,
detects duplicates, evaluates technical quality (focus, noise, composition,
expressions), and automatically organizes your images into KEEP, REVIEW,
and REJECT folders so you can immediately begin editing your best work in
your preferred editor.
Because it runs 100% locally on your own hardware, your photographs are never uploaded to the cloud, ensuring total client privacy and eliminating subscription fees.
Platform & Hardware Support
Local AI Culling is engineered with a single, unified codebase that runs seamlessly across platforms with automatic, platform-neutral hardware acceleration:
| Operating System | Architecture | Hardware Acceleration | Runtime Fallback |
|---|---|---|---|
| Windows | x86_64 |
NVIDIA CUDA (where available) | Universal CPU |
| macOS | Apple Silicon (arm64) / Intel |
Apple Metal / MPS (Metal Performance Shaders) | ARM NEON / CPU |
| Linux | x86_64 / arm64 |
NVIDIA CUDA (where available) | Universal CPU |
Features
- Duplicate Detection: Groups visually identical photos taken moments apart.
- Burst Detection & Ranking: Identifies high-speed bursts and automatically selects the single best frame.
- Intelligent Classification: Sorts images into
KEEP,REVIEW, andREJECTbased on customizable scoring. - Expression Intelligence: Understands facial semantics (e.g., closed eyes, blinking, awkward mouth shapes) to avoid penalizing artistic choices while flagging ruined shots.
- Image Editability Engine: Simulates shadow and highlight recovery on the raw preview to estimate real-world editability (penalizing noise and color degradation).
- Scene Intelligence: Detects environmental and subject context (e.g., weddings, portraits, low-light).
- Photographer Feedback Dashboard: A local web interface to review decisions, adjust thresholds, and understand exactly why the AI made a decision.
- Hardlink Export Engine: Instantly creates organized output folders using zero-byte storage hardlinks across Windows drives and POSIX filesystem devices. The original files are never duplicated or modified.
- Explainable Decisions: Transparent scoring. No black-box AI magic—you can see exactly why a photo was rejected.
Screenshot Gallery
Dashboard Overview (Browser Mode)
Scene Intelligence (Inspector Mode)
KEEP Folder View
REVIEW Folder View
REJECT Folder View
Hardlinked Export Structure
output/
├── KEEP/
├── REVIEW/
├── REJECT/
├── final_export.csv
└── session.json
Architecture
Local AI Culling operates as a highly modular pipeline.
graph TD
A[Image Ingestion] --> B[Technical Analysis]
B --> C[Duplicate Detection]
C --> D[Burst Ranking]
D --> E[Classification]
E --> F[Scene Intelligence]
F --> G[Export Engine]
G --> H[Feedback Dashboard]
subgraph TA [Technical Analysis]
B1(Blur & Focus)
B2(Composition)
B3(Subject Quality)
B4(Face & Eye State)
B5(Editability Engine)
end
B -.-> TA
Session Management
The dashboard includes an automated Session Management engine. When the pipeline processes a new directory, it generates a deterministic session.json based on the dataset name and filenames. When the feedback dashboard launches, it compares this session against previous feedback files. If it detects a new photography assignment, it safely archives the older feedback to prevent data collisions, ensuring that photographers can seamlessly review multiple consecutive shoots without manual cleanup.
Installation
Prerequisites
- Python: 3.11 recommended (Python 3.10+ supported; the type union syntax
in
bulk_rulesis 3.10-only). - Storage: SSD highly recommended for fast preview reading and zero-byte hardlink exports.
Setup
-
Clone the repository:
git clone https://git.christofschulze.com/photo-culling.git cd photo-culling -
Create and activate a virtual environment:
# macOS / Linux python3 -m venv .venv source .venv/bin/activate # Windows python -m venv .venv .venv\Scripts\activateKeep the environment activated for the next step so that
piptargets this environment, and so thatculleris on yourPATHafterwards. -
Install the package:
pip install -e .This installs dependencies and the project itself, which is what creates the
cullerentry point.Do not use
pip install -r requirements.txtas the install step. It installs the third-party dependencies only, so thecullerscript is never created and you are left running the code as loose scripts. Use it only to pre-populate dependencies without installing the package, such as in a network-restricted environment, followed bypip install -e . --no-deps. -
Verify the install:
culler --help culler --versionBoth should print output without error. If
culler: command not found, the virtual environment is not active in the current shell. -
Model Placement: Models download automatically on first run into the per-user cache (
$XDG_CACHE_HOME/local-ai-culling, defaulting to~/.cache/local-ai-culling). To use existing copies instead, place them in the project root ormodels/directory:yolov8n.pt(Ultralytics YOLOv8)face_landmarker.task(MediaPipe FaceLandmarker)
Keeping culler available
The script lives in the virtual environment, so it is only on your PATH while
that environment is active. To have it available in every new shell, either
activate the environment from your shell profile:
# ~/.bashrc or ~/.zshrc
source /path/to/.venv/bin/activate
Or invoke it by full path without activating anything:
/path/to/.venv/bin/culler run -i path/to/photos -o output
Usage
The installed culler command has three subcommands.
1. Running the Pipeline
Run the culling engine on a folder of photographs:
culler run -i path/to/your/photos -o output
This will analyze the photos and create the final KEEP, REVIEW, and REJECT folders via hardlinks.
Configuration is read from config.yml in the current directory, falling back
to the copy shipped with the project. Pass --config (a top-level option, so
it must come before the subcommand) to point at another file:
culler --config path/to/config.yml run -i path/to/your/photos -o output
2. Launching the Dashboard
To review the results visually:
culler dashboard -o output
Open your browser to http://localhost:5000. The -o must point at the same
output directory the pipeline used, since that is where feedback is recorded.
3. Container / Server Deployment (multiple photographers)
For hosting the dashboard as a shared service — per-user sessions, a job queue with upload (zip or files) and download, CPU-only pods, and HTTP Basic Auth at the ingress — use the container build and Helm chart:
docker build -t lai-culler . && docker compose up -d
# or the full Kubernetes story:
helm install culler helm/culler --set ingress.enabled=true --set ingress.host=culling.example.com
See docs/architecture/Hosting_plan.md and docs/architecture/Deployment.md
for the plan and operator guide, and helm/culler/README.md for the values
reference. The container runs culler serve, which opens a workspace list in
the browser: drop a zip or photographs to create a workspace — the culler starts
as soon as the upload lands and the finished export opens by itself — then
download the culled output as a zip.
4. Generating a Feedback Report
To summarise a set of dashboard decisions and derive configuration suggestions:
culler report -o output
Legacy scripts
python main.py and python run_dashboard.py still work and behave as
culler run and culler dashboard respectively. They exist for compatibility
with older setups; prefer the culler command.
Project Structure
src/culler/ai/: Core AI and scoring models (blur, composition, face/eye, editability, duplicate detection).src/culler/engine/: System engine (pipeline orchestration, configuration loading, caching).src/culler/engine/io/: File ingestion, JSON/CSV exports, visual export, and preview generation.src/culler/engine/analysis/: Profiling and metadata utilities.src/culler/dashboard/: Local web dashboard interface (feedback_server.py) and HTML templates.tests/: Smoke tests and module linkage tests.docs/: System documentation, phase plans, and architectural walk-throughs.models/: Directory for required machine learning weights (.pt,.task).output/: Default directory for hardlinked output and runtime metadata.
Configuration
The software is highly customizable via config.yml. Key configurable options include:
thresholds: Adjust the strictness for blur, noise, and face quality.weights: Adjust how much different factors (e.g., composition vs focus) contribute to the final score.export.mode: Choose betweenhardlink(default, zero extra storage) orcopy.culling.max_burst_time_gap: The maximum time gap (in seconds) to consider photos part of the same burst.
Documentation
For a deeper dive into the engineering and design philosophy behind Local AI Culling, see:
Roadmap
Check out the ROADMAP.md for a summary of completed milestones (like Expression Intelligence and the Editability Engine) and a look at our future goals.
License
This project is licensed under the MIT License - see the LICENSE file for details.





