illustration.inspection

Candidate inspection — classical-CV pre-filters + VLM caption/judge (R2 §2-3).

Two cost tiers, cheapest first:

  1. Classical-CV pre-filters (R2 §3) — sub-10ms/image checks (aspect ratio, minimum size, brightness, blur, NSFW) that drop unusable candidates before any VLM token is spent. This is the single biggest cost lever in the loop. License-safe libraries only: Pillow (MIT-CMU) + NumPy for the core checks; the NSFW gate defaults to the Apache-2.0 Falconsai/nsfw_image_detection ViT (the torch stack the reranker already uses) and is fully injectable.

  2. VLM inspect (R2 §2) — a cheap one-sentence caption for the Correct path, or a full pointwise rubric judgement for the Ambiguous path. Both go through the injectable describe seam, which defaults to aix.describe_image() (lazy import). Judgements are pointwise (one image at a time) rather than pairwise, mitigating the position bias that plagues comparative VLM judges (R2 §4); use a judge model from a different family than any generator to cut self-preference bias.

Every expensive step is an injectable seam, so the whole module is testable offline with stubs (synthetic PIL images for the CV checks, canned strings for describe) — no network, no paid API.

>>> # offline: a metadata-only check needs no image fetch
>>> from illustration.schema import ImageResult
>>> r = ImageResult(provider="p", id="1", url="u", width=1200, height=800)
>>> out = aspect_ratio_check()(r, lambda: None)
>>> out.name, out.passed
('aspect_ratio', True)
illustration.inspection.CORE_CHECKS: tuple = (<function aspect_ratio_check.<locals>.check>, <function min_dimension_check.<locals>.check>, <function brightness_check.<locals>.check>, <function blur_check.<locals>.check>)

Core checks — need only Pillow + NumPy (the [curate] extra), no model.

illustration.inspection.Check

A check maps (result, get_image) -> CheckOutcome. get_image is a 0-arg thunk returning the candidate’s PIL image (cached) or None if unfetchable; metadata-only checks ignore it (so a metadata fail never triggers a fetch).

alias of Callable[[ImageResult, Callable[[], Any]], CheckOutcome]

class illustration.inspection.CheckOutcome(name: str, passed: bool, value: float | None = None, reason: str | None = None)[source]

The result of one check on one candidate.

illustration.inspection.DEFAULT_CHECKS: tuple = (<function aspect_ratio_check.<locals>.check>, <function min_dimension_check.<locals>.check>, <function brightness_check.<locals>.check>, <function blur_check.<locals>.check>, <function nsfw_check.<locals>.check>)

core CV + the NSFW gate. The NSFW check needs the classifier deps ([rerank] extra); see default_checks() for the dependency-aware default actually used when checks is omitted.

Type:

The recommended set

illustration.inspection.DFLT_CAPTION_MAX_TOKENS = 80

Token caps for the default VLM seam — a soft cost bound on the paid path. A one-sentence caption and a compact JSON rubric both need very few tokens.

illustration.inspection.Describe

A describe seam maps (image_ref, prompt) -> text. image_ref is anything aix.describe_image() accepts (URL / path / bytes / PIL / data URI).

alias of Callable[[Any, str], str]

class illustration.inspection.InspectReport(*, provider: str, id: str, mode: str, caption: str | None = None, rubric: RubricScore | None = None, rationale: str | None = None)[source]

A VLM inspection of one candidate (caption or judge mode; R2 INSPECT).

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class illustration.inspection.PrefilterReport(*, provider: str, id: str, passed: bool, reasons: list[str] = <factory>, values: dict[str, float]=<factory>)[source]

Per-candidate pre-filter verdict (the R2 PREFILTER stage record).

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class illustration.inspection.PrefilterResult(passed: list[ImageResult], reports: list[PrefilterReport])[source]

The outcome of pre-filtering a candidate set.

property dropped: list[PrefilterReport]

Reports for the candidates that failed at least one check.

class illustration.inspection.RubricScore(*, subject: float = 0.0, action: float = 0.0, setting: float = 0.0, mood: float = 0.0, style: float = 0.0, quality: float = 0.0, overall: float = 0.0, rationale: str = '', parsed: bool = True)[source]

A pointwise VLM judgement of one candidate (R2 §4 rubric dimensions).

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

recompute_overall() RubricScore[source]

Set overall to the mean of the six rubric dimensions.

exception illustration.inspection.SafetyGateDisabledWarning[source]

Raised when the default pre-filter set runs without the NSFW safety gate.

illustration.inspection.aspect_ratio_check(*, min_ratio: float = 0.2, max_ratio: float = 5.0) Callable[[ImageResult, Callable[[], Any]], CheckOutcome][source]

Reject degenerate aspect ratios (extreme slivers/panoramas). Metadata-only.

Uses the result’s width/height; passes when either is missing (can’t assess). ratio = width / height.

illustration.inspection.blur_check(*, min_variance: float = 100.0) Callable[[ImageResult, Callable[[], Any]], CheckOutcome][source]

Reject blurry images via variance of the Laplacian (R2 §3; OpenCV-free).

The 3x3 Laplacian is applied with NumPy (no OpenCV) on the grayscale image; a low response variance means few sharp edges, i.e. blur.

illustration.inspection.brightness_check(*, min_brightness: float = 0.1, max_brightness: float = 0.95) Callable[[ImageResult, Callable[[], Any]], CheckOutcome][source]

Reject near-black / blown-out images. Mean luminance, normalized to [0, 1].

illustration.inspection.default_checks() tuple[source]

The default check set, chosen by what’s installed.

Returns DEFAULT_CHECKS (core + NSFW) when the NSFW classifier deps are importable, else CORE_CHECKS — and emits a :class:`SafetyGateDisabledWarning` so the absence of the safety screen is never silent (install illustration[rerank] to enable it). Passing checks= to prefilter() explicitly always overrides this.

illustration.inspection.inspect_candidate(query: str, result: ImageResult, *, mode: str = 'caption', describe: Callable[[Any, str], str] | None = None, model: str | None = None, max_tokens: int | None = None) InspectReport[source]

Inspect one candidate: a cheap caption (default) or a full rubric judge.

mode="caption" (the Correct-grade path) produces a one-sentence caption from the thumbnail; mode="judge" (the Ambiguous path) delegates to judge_candidate(). describe defaults to a lazy aix.describe_image() bound to model and capped at max_tokens (default DFLT_CAPTION_MAX_TOKENS).

illustration.inspection.judge_candidate(query: str, result: ImageResult, *, describe: Callable[[Any, str], str] | None = None, model: str | None = None, max_tokens: int | None = None) InspectReport[source]

Score one candidate against query with a pointwise VLM rubric.

Uses the full-resolution image for accuracy. describe defaults to a lazy aix.describe_image() capped at max_tokens (default DFLT_JUDGE_MAX_TOKENS). The reply is parsed into a RubricScore (overall = mean of the six dimensions); an unparseable reply yields a neutral score flagged parsed=False so the loop can treat it as ambiguous rather than wrongly accept or reject.

illustration.inspection.min_dimension_check(*, min_width: int = 200, min_height: int = 200) Callable[[ImageResult, Callable[[], Any]], CheckOutcome][source]

Reject tiny images. Metadata-only (passes when dimensions are unknown).

illustration.inspection.nsfw_check(*, max_prob: float = 0.5, classifier: Callable[[Any], float] | None = None) Callable[[ImageResult, Callable[[], Any]], CheckOutcome][source]

Hard-drop unsafe images (R2: NSFW is a non-negotiable drop).

classifier is image -> nsfw_probability; it defaults to the Apache-2.0 Falconsai/nsfw_image_detection ViT (torch/transformers — the [rerank] extra). Fails closed: a candidate whose image can’t be fetched or classified is dropped, never passed.

illustration.inspection.prefilter(results: Sequence[ImageResult], *, checks: Sequence[Callable[[ImageResult, Callable[[], Any]], CheckOutcome]] | None = None, field: str = 'thumbnail_url', fetch: Callable | None = None) PrefilterResult[source]

Run checks over results, keeping only candidates that pass all.

Checks run cheapest-first and short-circuit on the first failure, so a candidate dropped on its (free) metadata never triggers an image fetch. Images are fetched at most once per URL (in-memory, for this call only); fetch overrides the fetch function (a test double avoids the network). checks defaults to default_checks() (core CV + NSFW where its deps are available).

Returns a PrefilterResult with the surviving passed results and a PrefilterReport per input candidate.