lookbook
Distill raw image pools into optimized, high-diversity reference sets for training personalized models (character LoRAs, product LoRAs, style LoRAs).
Status: All five v1 phases are shipped. The package can: clean a photo dump (200→20 in <30s, no GPU); run the full embeddings + facility-location workflow with CLIP or DINOv2; curate a person / character LoRA with face detection, ArcFace identity diversity, and pose-bin quotas; serve every verb over HTTP (FastAPI via
qh); and expose every verb over MCP (viafastmcp) so an LLM agent can drive curation directly. Since then it has also gained one-call character / environment reference picks, a human-in-the-loopcurate_interactive, andcompare_to_reference— "is this generation still the same subject?" as a number. Seemisc/docs/lookbook_development_plan.mdfor the full roadmap andmisc/docs/lookbook_design_report.mdfor the design rationale.
Why
Training a personalized image model needs ~15–30 carefully chosen reference images, where each adds new context — pose, lighting, expression, distance. "Top-K by score" collapses to near-duplicates; this is fundamentally a set-selection problem with diversity as a constraint, not a tiebreaker.
lookbook separates per-image scoring (is this image individually good?)
from set-level selection (does this collection cover the concept?), and
makes both extensible.
Install
pip install lookbook # core: Pillow, numpy, dol, meshed, config2py
pip install lookbook[funnel] # + cv2 / imagededup for the cheap funnel
pip install lookbook[embed] # + torch, CLIP, DINOv2, pyiqa, apricot
pip install lookbook[person] # + InsightFace, head pose, mediapipe
pip install lookbook[http] # + FastAPI / qh server
pip install lookbook[mcp] # + fastmcp (Anthropic MCP server)
The base install has no ML dependencies — Phase 1 (cheap funnel) works on a
plain laptop. [embed] and beyond pull torch.
Quickstart
CLI:
# Phase 1: clean up a photo dump (drops blurry, dark, duplicate, tiny).
lookbook curate ./photos --k 20 --recipe funnel
# Phase 2: same funnel + DINOv2 embeddings + facility-location selection
# for "diverse but sharp" picks. Downloads ~350MB on first run; subsequent
# runs are fast and cached. Use --recipe diverse_clip for CLIP semantic
# embeddings instead.
lookbook curate ./photos --k 20 --recipe diverse
# Phase 3: full character/person LoRA curation. Detects faces, embeds
# with ArcFace, applies pose-bin quotas, and runs cluster-coverage
# diagnosis. Pulls insightface + sixdrepnet (lazy on first use).
lookbook curate ./photos --k 20 --recipe person
# Same shape with no model downloads (mock backends; useful for tests):
lookbook curate ./photos --k 8 --recipe person_mock
# Phase 4: HTTP server. Every verb is POST /<verb> with a JSON body;
# Swagger UI at /docs.
lookbook serve --port 8000 --host 127.0.0.1
# curl examples:
# curl -X POST localhost:8000/list_recipes -H 'Content-Type: application/json' -d '{}'
# curl -X POST localhost:8000/curate_source -H 'Content-Type: application/json' \
# -d '{"source_path":"/abs/photos","k":20,"recipe":"funnel"}'
# Phase 5: MCP server (stdio transport — for Claude Desktop, Anthropic SDK).
# Each verb is exposed as an MCP tool an agent can call.
lookbook mcp
# See available scorers, embedders, filters, selectors, recipes / profiles:
lookbook list-plugins
lookbook list-recipes
Python:
from lookbook import curate
result = curate(
"./photos",
k=20,
scorer_ids=("resolution", "file_hash", "phash", "blur", "exposure"),
filter_ids=(
("min_resolution", {"min_long_side": 800}),
"exposure_range",
"min_blur",
"no_exact_duplicate",
"no_near_duplicate",
),
selector_id=("top_k", {"metric_id": "blur"}),
)
print(result.report) # drop counts attributed to each filter
print([r.image_id for r in result.kept])
One-call presets: character and environment references
Two opinionated facades over curate for the "pick the best reference
image(s) from this pool" job:
from lookbook import curate_for_character, curate_for_environment
# Known character: scores resolution / sharpness / exposure / face quality,
# ranks by the composite `face_quality`. face_detector="mock_face" runs it
# offline; the default "insightface" needs `pip install lookbook[person]`.
best = curate_for_character("./frames", k=1)
# No-face pools (environment plates, props, style boards): ranks by the
# composite `technical_quality`. No ML dependency at all.
best = curate_for_environment("./plates", k=3)
Identity check: does this generation still match the reference?
from lookbook import compare_to_reference
result = compare_to_reference(reference, candidate, embedder="arcface")
result.score # [0, 1], 1 = same identity
result.passed # score >= threshold (0.85 default) — advisory
result.per_reference # per-view breakdown when the reference is a pool
reference is one image ref or a list of them (folded by aggregation,
"max" by default). Use "clip" / "dinov2" as the embedder for scenes,
props and architecture. Also exposed as POST /compare_to_reference and as
the compare_to_reference MCP tool.
Interactive curate (keep the human in the loop)
Pipeline-only top-k can rank "boring but sharp" above "stylistically
perfect but slightly blurry". curate_interactive invites the caller
into the loop, one round at a time:
from lookbook import curate_interactive, InteractiveDecision
def decide(presented, info):
# show `presented` to the user; collect their picks
return InteractiveDecision(keep=("img-abc",), reject=("img-xyz",))
result = curate_interactive(
"./photos",
on_decision=decide,
k=20,
present=8,
scorer_ids=("blur", "exposure"),
)
Pre-recorded decisions (a list of InteractiveDecision) are accepted in
place of the callable — handy for tests, headless replays, and
agent-scripted flows.
local_path() on every ImageRef
PathImageRef, BytesImageRef, and UrlImageRef all expose
local_path(cache_dir=None) -> str. Path refs return their existing
path; bytes/url refs materialize once into a content-addressed cache
(honors $LOOKBOOK_REFS_CACHE_DIR). The free function
lookbook.to_local_path(ref) dispatches across all subtypes — useful
when downstream tools (ffmpeg, fal upload) need a real file.
Architecture
Five layers; the heavy ML libs live only at the bottom so the upper layers stay laptop-installable.
Interface (CLI, HTTP via qh, MCP via fastmcp, Python lib)
↓
Recipe / facade (lookbook.curate, named recipes, profiles)
↓
Orchestration (lookbook.pipeline, manifest, drop attribution, run records)
↓
Plugin layer (Scorer | Filter | Embedder | Selector — Protocols)
↓
Backend (CLIP, DINOv2, InsightFace, 6DRepNet — wrapped, lazy-imported)
The manifest — MutableMapping[(image_id, metric_id), Annotation] — is
the SSOT. Persistence is pluggable via dol: filesystem (default), SQLite,
S3, Mongo, Redis. The default location is the user's app data folder via
config2py: ~/.local/share/lookbook on Linux and macOS (it follows the XDG
convention, not ~/Library/Application Support), %LOCALAPPDATA% on Windows.
New scorers/selectors/filters are registered, never subclassed. See the
.claude/skills/ directory for developer skills (lookbook-dev,
lookbook-add-scorer, lookbook-add-selector, lookbook-storage).
Project layout
lookbook/
base.py Protocols + Annotation + Manifest type
store.py dol-backed Stores bundle, manifest codec
_paths.py config2py-backed default folders
refs.py PathImageRef, BytesImageRef, UrlImageRef
manifest.py Manifest helpers
registry.py Plugin registries
pipeline.py Orchestrator (topo-sorted scorers + filters + selector)
facade.py curate, score, curate_for_character/environment
interactive.py curate_interactive + InteractiveDecision
report.py Drop-attributing Report
scorers/ random, resolution, file_hash, phash, blur, exposure,
technical_quality
+ person.py: face detection, area, head pose, quality
+ identity.py: cross-image identity similarity
embedders/ mock, clip, dinov2, arcface (lazy-imported)
filters/ min_resolution, min_blur, exposure_range, dedup
+ person.py: has_face, single_face_only, min_face_*
selectors/ top_k, facility_location, quota
profiles/ person.yaml, person_mock.yaml (+ user-edited via config2py)
diagnose.py cluster_coverage (set-level diagnosis)
io/ ingest, ingest_to_store
http.py qh-built FastAPI surface; mk_lookbook_app, serve
mcp.py fastmcp-built MCP server; mk_lookbook_mcp, serve
__main__.py CLI (cw): curate, list-plugins, list-recipes
.claude/skills/ Claude Code skills for development & agent use
misc/docs/ Design report + development plan
tests/ pytest, hermetic
Claude Code skills
The .claude/skills/ directory ships nine skills covering both ends of
the workflow:
Dev skills (used while building / extending lookbook):
lookbook-dev— overall architecture, cross-references between moduleslookbook-storage— repository pattern, swapping storage backendslookbook-add-scorer— adding a new per-image metriclookbook-add-selector— adding a new set-selection algorithmlookbook-profile— adding a subject profile (YAML)lookbook-http— HTTP route layout + adding endpoints
Usage skills (for agents driving lookbook to curate):
lookbook-curate— the headline workflowlookbook-diagnose— interpreting reports, querying the manifestlookbook-recipe— customizing recipes per-call or via user YAML
Metadata
Release files for lookbook 0.0.12
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lookbook-0.0.12.tar.gz | 138.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lookbook-0.0.12-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 209.4 kB
Release files / lookbook-0.0.12.tar.gz
| Download URL | lookbook-0.0.12.tar.gz |
|---|---|
| Size | 138.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
81454a2814e0182f8459bed5e6d8fa5d615c8225b44e9da27c246f3c9b192e7c
|
|
BLAKE2b-256 checksum How to use checksums |
7b944bbe8ff94b1658a56bdd2052cdfb80024779707a0c6356716c1b9d4033c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / lookbook-0.0.12-py3-none-any.whl
| Download URL | lookbook-0.0.12-py3-none-any.whl |
|---|---|
| Size | 71.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2e6b178a2d494873cc047ca0ee384b93dfd10f985147a1cfb1f1f5222b10c4a1
|
|
BLAKE2b-256 checksum How to use checksums |
0e490724d9fda303fd7c9edb3ea9bf7fda2b2d84ab5466eebdf552472bcad18a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|