feat: automatic cluster labels for the image map - #44
Open
lstein wants to merge 1 commit into
Open
Conversation
lstein
marked this pull request as draft
August 2, 2026 22:24
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 2, 2026 22:38
2b60df0 to
fb7b7f8
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 2, 2026 22:38
8b1e3f5 to
5c13db0
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
3 times, most recently
from
August 3, 2026 13:55
b9be000 to
9c23a67
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
2 times, most recently
from
August 3, 2026 14:42
3c2129e to
c7e2eb2
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 3, 2026 15:48
9c23a67 to
ff47c33
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 3, 2026 15:50
c7e2eb2 to
0a63c01
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 3, 2026 16:10
ff47c33 to
f5fab5e
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 3, 2026 16:11
0a63c01 to
95e3459
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 3, 2026 18:21
f5fab5e to
14ad377
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 3, 2026 18:22
95e3459 to
3284470
Compare
lstein
marked this pull request as ready for review
August 3, 2026 18:28
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 4, 2026 00:24
14ad377 to
000ebea
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
2 times, most recently
from
August 4, 2026 01:43
aabf797 to
e768baf
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
2 times, most recently
from
August 4, 2026 05:37
986cd46 to
b9b9f00
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 4, 2026 05:37
e768baf to
3de9a16
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 4, 2026 14:58
b9b9f00 to
ff1ee4f
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 4, 2026 14:58
3de9a16 to
5777b78
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 4, 2026 22:32
ff1ee4f to
ff2a0ad
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 4, 2026 22:32
5777b78 to
ad66071
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 5, 2026 00:31
ff2a0ad to
b592992
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
2 times, most recently
from
August 5, 2026 00:38
3ef9291 to
c38d3ef
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 5, 2026 00:38
b592992 to
f46643e
Compare
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 5, 2026 01:18
c38d3ef to
f6299e2
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 5, 2026 01:18
f46643e to
e6b4634
Compare
Ports PhotoMapAI's vocabulary-based cluster labeling: - A bundled ~1700-phrase vocabulary (Places365 + curated additions) is embedded through the index model's text tower with a prompt-template ensemble (encode every template, L2-normalize, mean-pool, re-normalize). The first run per model takes tens of seconds, so phrase embeddings are disk-cached next to the database, keyed by model and a vocabulary fingerprint. - `GET /v1/image_map/cluster_labels` labels each of the caller's visible clusters with the vocabulary phrase nearest the cluster's image-embedding centroid (top-k with alternates). Clustering mirrors /points exactly — same eps semantics over the same accessible set — so cluster ids line up with the served map. 409 with a clear message when no text encoder is installed. - The frontend fetches labels best-effort after each points refresh and renders them as plotly annotations at cluster centroids; no labels simply means an unlabeled map. With the contrastive default encoder no score calibration is needed; the ensemble keeps drawings/paintings scoring reasonably. Amended during the rebase onto the re-architected webv2: header toggle rewired to useWorkbenchCommands/widgets.patchValues and @platform/ui, dropped the superseded EMPTY_SELECTION selector, merged the new InvocationServices placeholders into the router test fixture, regenerated OpenAPI artifacts, and re-recorded the build performance baseline for entry-chunk graph churn (+11 bytes launchpad-owned). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Review fix: a stale labels request's late failure no longer wipes labels a newer request already set (catch is sequence-guarded); labels also reset on account invalidation.
lstein
force-pushed
the
feat/image-map-11-cluster-labels
branch
from
August 5, 2026 01:21
f6299e2 to
e963fe7
Compare
lstein
force-pushed
the
feat/image-map-10-semantic-search
branch
from
August 5, 2026 01:21
e6b4634 to
7362636
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR 11 — the top of the Image Map stack. Ports PhotoMapAI's vocabulary-based cluster labeling.
GET /v1/image_map/cluster_labelslabels each visible cluster with the phrase nearest its image-embedding centroid (top-k with alternates). Clustering mirrors/pointsexactly (same eps semantics over the same accessible set), image embeddings come from the same LRU matrix cache the search endpoint uses (no per-request full-BLOB read), and the response carries the projection'supdated_at.ready— and renders them as plotly annotations at cluster centroids. Label responses are discarded unless they match the current projection and are the newest request, so a recompute or out-of-order response can never attach labels to renumbered clusters; identical label sets don't trigger a rebuild.An adversarial review covered this diff; all its findings (stale-label attachment, per-request matrix materialization, Windows-illegal cache filename, cache poisoning across recipe changes, wasted 409 fetches) are fixed above.
Image Map setup walkthrough (whole stack, PRs 1–11)
Since this PR is the top of the stack, here is an end-to-end guide to running the feature.
1. Configure
invokeai.yamlRestart the backend after changing these.
image_index_device: cpuembeds with a service-local CPU copy of the model: no VRAM use, and indexing keeps running during generations. Left unset, embedding runs through the model cache on the GPU and politely pauses while a generation is in progress.2. Install the embedding model
Model Manager → Starter Models → install "clip-vit-large-patch14" (CLIP ViT-L Image Encoder, type: CLIP Vision).
The CLIP Vision install ships the full dual-tower checkpoint, so it also powers text search and cluster labels. On first load, transformers prints a loud "LOAD REPORT" listing
text_model.*keys as UNEXPECTED — this is benign (the vision tower is being loaded from a dual-tower checkpoint; the text tower loads separately when search/labels need it).There is no auto-download: if the configured model isn't installed, the service idles and logs a warning.
SigLIP-family models also work (
image_index_model: <name>), with different score characteristics. Changing the model discards all embeddings computed by the previous model and re-embeds from scratch.3. Populating the index
On the next start, a backfill pass embeds every existing gallery image in batches; new images are embedded within a second or two of landing in the gallery. Admins see an "indexing N/M" counter in the Image Map widget's footer while work is pending.
The first map open after a backend restart takes ~30–60 s: UMAP's numba kernels JIT-compile once per process, then the projection computes. Later recomputes take seconds, and an unchanged map serves instantly from cache.
4. Using the map
Open the Image Map widget from the widget picker (docks left/right/center; the widget menu also offers Float window — drag, resize, shade, maximize, dock back).
The map refreshes itself: after a generation, the new point appears a few seconds after the embed completes (admins ride the status event; other users get a per-user counts-free poke for their own images). Deletions vanish automatically for admins; non-admin users must press the footer refresh after deleting (the backend can't resolve an owner for an already-deleted row).
5. Semantic and image-similarity search
In the gallery search field:
Semantic search: "…"chip and the grid re-ranks by similarity. Requires the model's text tower (the full CLIP Vision install has it; a vision-only install returns a clear "text search unavailable" error).Similar to <image>chip, grid ranked by visual similarity. The field also accepts image files dropped from the OS and images dragged in from web pages (app-origin URLs resolve to the stored embedding; external URLs are fetched and embedded server-side for that one query).from:,to:…) and starred-first ordering don't apply while a semantic query is active.Gotchas
clip-vit-large-patch14; the index needs the CLIP Vision one (see §2).image_index_modelre-embeds the entire gallery.epsis available on the API (GET /v1/image_map/points?eps=) but not yet exposed in the UI.Testing
Labeling math (vocabulary load/dedup, fingerprint discrimination, template ensembling/pooling, per-cluster nearest-phrase with noise exclusion), router alignment test proving labels line up with
/pointscluster ids, text-encoder-unavailable 409, no-projection empty case. Full suites green: 200 backend / 907 frontend / lint / build at the stack top.🤖 Generated with Claude Code