lookbook
lookbook — distill image pools into reference sets for personalized model training.
Public facade. The package is split across:
lookbook.base — Protocols and core types (Annotation, Manifest)
lookbook.store — Repository pattern (Stores) over dol
lookbook.refs — ImageRef implementations
lookbook.manifest — Manifest helpers
lookbook.registry — Plugin registries (scorers, filters, embedders, selectors)
lookbook.pipeline — Pipeline orchestrator
lookbook.scorers — Per-image scorer modules (registered on import)
lookbook.selectors — Selector modules (registered on import)
lookbook.io — Ingest, export
Plugin registries are accessed as lookbook.registry.scorers etc. (not lookbook.scorers, which is the submodule that holds scorers).
- class lookbook.Annotation(image_id: str, metric_id: str, value: Any, config_hash: str = '', cost_tier: int = 0, timestamp: datetime = <factory>, backend: str = '')[source]
A single annotation produced by a Scorer or Embedder for one image.
The (image_id, metric_id) pair is the manifest key. config_hash is the cache key — re-running with the same config yields a cache hit; changing a threshold or model invalidates.
- class lookbook.BytesImageRef(payload: bytes, image_id: str = '', metadata: Mapping[str, ~typing.Any]=<factory>)[source]
An image stored as raw bytes in memory.
- class lookbook.Embedder(*args, **kwargs)[source]
Computes a vector representation in some named embedding space.
- class lookbook.Filter(*args, **kwargs)[source]
Predicate over annotations that decides whether to keep an image.
- class lookbook.IdentitySimilarity(metric_id: str = 'identity_similarity', cost_tier: int = 2, requires: tuple = (), backend: str = 'lookbook:identity_similarity', reference_embedding: Any | None = None, reference_image: Any | None = None, reference_images: Sequence[Any] | None = None, embedder: EmbedderSpec = 'arcface', threshold: float = 0.85, aggregation: str = 'max', normalization: str = 'rescale')[source]
Score a candidate image’s identity-likeness to a locked reference.
Unlike the single-image scorers, this one is stateful by reference: it holds the reference embedding(s) and compares each candidate against them. Construct one per locked subject, then
scoreevery generation.Construction (provide exactly one of the reference inputs):
reference_embedding— a precomputed vector (1-D) or pool (2-D / sequence of vectors). Cheapest; the reference is never re-embedded.reference_image— a singleImageRef(or anything the injected embedder accepts), embedded once at construction and cached.reference_images— a pool of refs, each embedded once.
Parameters (keyword-only past the embedder seam):
embedder— registry name ("arcface"default), anEmbedderobject, or a bareembed_fn(ref) -> vectorcallable. Injectable so tests pass a fake embedder and non-face subjects pass"clip"/"dinov2".threshold— advisory pass/fail cutoff on the[0, 1]scale.aggregation—"max"(default) |"mean"|"min"over the reference pool.normalization— cosine →[0, 1]map; seeDFLT_NORMALIZATION.
The
score(ref, manifest)method returns a JSON-able dict (seeSimilarityResult.as_dict()) so it persists through the manifest’s default codec. Usecompare_to_reference()for a one-call facade that returns the typedSimilarityResult.- compare(ref: ImageRef) SimilarityResult[source]
Compare one candidate ref against the reference; typed result.
- score(ref: ImageRef, manifest: MutableMapping[Tuple[str, str], Annotation]) dict[source]
Scorer-protocol entry point — returns the JSON-able result dict.
- class lookbook.ImageRef(*args, **kwargs)[source]
Anything that knows its identity and how to be opened lazily.
- class lookbook.InteractiveDecision(*, keep: tuple[str, ...] = (), reject: tuple[str, ...] = (), stop: bool = False)[source]
One round’s verdict from the caller.
- keep
``image_id``s the caller wants to lock into the final selection. They’re added immediately, removed from the candidate pool, and not shown again.
- Type:
tuple[str, …]
- stop
When True, the loop terminates after applying this round’s keep/reject and returns whatever is in the confirmed-kept set so far (clipped to
k).- Type:
bool
- class lookbook.PathImageRef(path: str, image_id: str = '', metadata: Mapping[str, ~typing.Any]=<factory>)[source]
An image referenced by filesystem path.
- class lookbook.Pipeline(scorers: list[Scorer] = <factory>, embedders: list[Embedder] = <factory>, filters: list[Filter] = <factory>, selector: Selector | None = None, diagnose_clusters: int = 0)[source]
An ordered set of scorers + embedders + filters + a final selector.
- class lookbook.RunResult(run_id: str, kept: list[~lookbook.base.ImageRef], candidates: list[~lookbook.base.ImageRef], selector_id: str, scorer_ids: list[str], started_at: ~datetime.datetime, finished_at: ~datetime.datetime, report: dict = <factory>)[source]
The result of running a pipeline.
- class lookbook.Scorer(*args, **kwargs)[source]
Computes one annotation per image. Idempotent w.r.t. config_hash.
- class lookbook.Selector(*args, **kwargs)[source]
Chooses a subset of size up-to-K from candidates given the manifest.
- class lookbook.SimilarityResult(identity_cosine: float, score: float, passed: bool, threshold: float, per_reference: tuple[float, ...]=<factory>, aggregation: str = 'max', n_references: int = 0)[source]
Outcome of comparing a candidate against a (possibly pooled) reference.
- identity_cosine
The aggregated raw cosine in
[-1, 1](pre-normalization). Theidentity_cosinename is kept for the supervisor’s vocabulary even when the embedder is CLIP/DINOv2 — it is the cosine in whatever space the embedder defines.- Type:
float
- score
The aggregated similarity normalized to
[0, 1](1 = same identity). This is what the gate compares againstthreshold.- Type:
float
- passed
score >= threshold. Advisory only — the consumer decides whether to act on it.- Type:
bool
- threshold
The threshold used for
passed(on the[0, 1]scale).- Type:
float
- per_reference
The per-reference normalized similarities before aggregation. Length is the size of the reference pool. Lets callers see which locked view matched (e.g.
argmaxforaggregation="max").- Type:
tuple[float, …]
- aggregation
The aggregation applied over
per_reference.- Type:
str
- n_references
Size of the reference pool.
- Type:
int
- class lookbook.Stores(images: MutableMapping, manifest: MutableMapping, runs: MutableMapping, embeddings: MutableMapping, root: str | None = None)[source]
Bundle of all stores lookbook uses for persistence.
- class lookbook.UrlImageRef(url: str, image_id: str = '', metadata: Mapping[str, ~typing.Any]=<factory>, _cached: bytes | None = None)[source]
An image referenced by URL.
The bytes are fetched on first access and cached on the instance.
- lookbook.compare_to_reference(reference_image: Any, candidate_image: Any, *, embedder: EmbedderSpec = 'arcface', threshold: float = 0.85, aggregation: str = 'max', normalization: str = 'rescale') SimilarityResult[source]
Compare a candidate to a reference in one call →
SimilarityResult.The supervisor’s headline verb.
reference_imagemay be a single image ref or a pool (any sequence of refs) — the pool is embedded once and folded byaggregation.candidate_imageis a single ref.All inputs are whatever the injected
embedderaccepts — typically anImageRef, but with a fake/callable embedder they can be anything (tests pass plain ids).- Parameters:
embedder – Registry name (
"arcface"default for faces;"clip"/"dinov2"for scenes/architecture), anEmbedderobject, or a bareembed_fn(x) -> vectorcallable.threshold – Advisory pass/fail cutoff on the
[0, 1]scale (1 = same identity).aggregation – How to fold a reference pool —
"max"(default),"mean","min".normalization – Cosine →
[0, 1]map —"rescale"(default) or"clamp".
- Returns:
.score(∈[0, 1]),.passed(advisory),.identity_cosine(raw cosine), and the per-reference breakdown.- Return type:
- lookbook.curate(source, *, k: int = 20, scorer_ids: Sequence[str | tuple] = ('random_score',), embedder_ids: Sequence[str | tuple] = (), filter_ids: Sequence[str | tuple] = (), selector_id: str | tuple = 'top_k', diagnose_clusters: int = 0, stores: Stores | None = None, constraints: Mapping[str, Any] | None = None) RunResult[source]
High-level facade: ingest a source, run a pipeline, return the result.
Each plugin id may be either a string (“blur”) or a (name, kwargs) tuple ((“blur”, {“max_side”: 256})) to override the default config.
diagnose_clusters > 0 runs cluster-coverage diagnosis after selection and writes the result into the report’s notes.
- lookbook.curate_for_character(source, *, k: int = 1, face_detector: str | tuple = 'insightface', stores: Stores | None = None, constraints: Mapping[str, Any] | None = None) RunResult[source]
Curate the best reference image(s) of a known character from a pool.
Opinionated facade over
curate(), tuned for the “pick the reference image of this one character” job (IP-adapter conditioning, model-sheet seeding): every image is scored for resolution, sharpness, exposure and face quality, then the topkare taken by the compositeface_qualitymetric.Identity is not scored — the pool is assumed to already be one character, so what matters is which frame shows them most usably: in focus, well exposed, face clearly visible and well sized.
face_detector names the registered face-box scorer:
"insightface"— real RetinaFace detection; needspip install lookbook[person]. The default."mock_face"— the deterministic centred-box detector, for tests and demos without the ML dependency.
Images with no detected face score
0and sort last; the pool is never filtered down to empty, so a small or awkward pool still yields a best-effort pick.
- lookbook.curate_for_environment(source, *, k: int = 1, stores: Stores | None = None, constraints: Mapping[str, Any] | None = None) RunResult[source]
Curate the best reference image(s) for a non-person subject.
Opinionated facade over
curate()for pools where face detection is moot — environment plates, prop references, style boards. Each image is scored for resolution, sharpness and exposure, folded into the compositetechnical_qualitymetric, and the topkare taken.Unlike
curate_for_character()this needs no ML dependency — the scorers run on Pillow + numpy (cv2 is used for sharpness when present, with a numpy fallback otherwise).
- lookbook.curate_interactive(source, *, on_decision: Callable[[list[ImageRef], dict[str, Any]], InteractiveDecision] | Sequence[InteractiveDecision], k: int = 20, present: int = 8, max_rounds: int = 20, scorer_ids: Sequence[Any] = ('random_score',), embedder_ids: Sequence[Any] = (), filter_ids: Sequence[Any] = (), selector_id: Any = 'top_k', stores: Stores | None = None, constraints: Mapping[str, Any] | None = None) RunResult[source]
Run the pipeline in rounds, taking keep/reject decisions per round.
Args mirror
lookbook.curate()plus:- Parameters:
on_decision – A callable or a pre-recorded sequence of
InteractiveDecision. The callable receives the round’s top candidates and an info dict{round, n_kept_so_far, n_pool_remaining}.present – How many candidates to surface per round.
max_rounds – Hard cap on iterations; a defensive guard against broken decision callables that never stop.
- Returns:
A
RunResultwhosekeptlist is the confirmed set (in the order they were kept).candidatescarries the same as the final round’s pre-selection candidate pool.
- lookbook.get_stores(*, root: str | None = None, images_store: MutableMapping | None = None, manifest_store: MutableMapping | None = None, runs_store: MutableMapping | None = None, embeddings: MutableMapping | None = None) Stores[source]
Build the lookbook Stores bundle.
All arguments are optional. When root is None and no stores are supplied, the user’s app data folder is used. Pass dict() for any slot to keep that store in memory (useful for tests).
>>> stores = get_stores( ... images_store={}, manifest_store={}, runs_store={}, embeddings={} ... ) >>> stores.manifest is not None True
- lookbook.ingest(source: str | PathLike | Iterable[ImageRef]) list[ImageRef][source]
Materialize a source into a list of ImageRefs.
>>> import tempfile, os >>> with tempfile.TemporaryDirectory() as d: ... for name in ['a.jpg', 'b.png', 'note.txt']: ... open(os.path.join(d, name), 'wb').close() ... refs = ingest(d) >>> sorted(os.path.basename(r.path) for r in refs) ['a.jpg', 'b.png']
- lookbook.score(ref: ImageRef | str, *, metric_id: str, stores: Stores | None = None) Any[source]
Score one image against one metric. Returns the bare value.
Caches into the manifest so repeated calls are free.
- lookbook.to_local_path(ref, *, cache_dir: str | None = None) str[source]
Return a filesystem path where the bytes of
refcan be read.Works on any object that exposes
.local_path(cache_dir)— i.e. every concreteImageRefshipped with lookbook. Provided as a free function so downstream code doesn’t need to know which subtype it has.
- lookbook.value_of(manifest: MutableMapping[Tuple[str, str], Annotation], image_id: str, metric_id: str, default: Any = None) Any[source]
Return the bare value of an annotation (not the wrapper), or default.