Severity: low. Two items from the same review, neither blocking.
1. buildClusterAnnotations emits one annotation per cluster, uncapped
invokeai/frontend/webv2/src/workbench/image-map/imageMapTraces.ts
DBSCAN at the adaptive eps on a several-thousand-point map routinely produces well over a hundred clusters, and the backend caps nothing either. Plotly lays out every annotation in SVG and re-solves their positions on each relayout — and attachWheelZoom's applyRanges calls Plotly.relayout per wheel event, so pan and zoom on a busy map degrade to visible stutter. At that density the labels are also unreadably stacked, so the cost buys nothing.
Capping to the N largest clusters would fix both halves. Left unfixed because picking N is a product decision about what the map should show, not a mechanical fix.
2. label_clusters assumes a shape contract it does not check
invokeai/app/services/image_index/cluster_labels.py:117
The function assumes len(vocabulary) == vocab_embeddings.shape[0] and that the vocabulary is non-empty:
count = min(top_k, len(vocabulary)) comes from the list while scores is sized by the matrix. If the vocabulary is longer, np.argpartition(-scores, count - 1) raises ValueError; if shorter, vocabulary[top[0]] raises IndexError.
- An all-comment
cluster_vocab.txt gives count == 0 and so np.argpartition(-scores, -1).
Nothing reachable violates this today — the vocabulary and its embeddings are built together and cached as a pair, with a fingerprint check on load. But the test double in tests/app/routers/test_image_map.py already leans on DIM == 4 matching its four-phrase vocabulary, so changing DIM alone would trip it. A one-line assert at the top of the function turns an assumption into an enforced contract.
Context
Found by an adversarial review of #44 (feat/image-map-11-cluster-labels), the last PR in the image-map stack. The two high and five medium findings from that review are fixed on that branch. A third low — an unclosed np.load handle — was fixed there too rather than filed, because it sat in the same lines as the atomic-write fix.
Severity: low. Two items from the same review, neither blocking.
1.
buildClusterAnnotationsemits one annotation per cluster, uncappedinvokeai/frontend/webv2/src/workbench/image-map/imageMapTraces.tsDBSCAN at the adaptive eps on a several-thousand-point map routinely produces well over a hundred clusters, and the backend caps nothing either. Plotly lays out every annotation in SVG and re-solves their positions on each
relayout— andattachWheelZoom'sapplyRangescallsPlotly.relayoutper wheel event, so pan and zoom on a busy map degrade to visible stutter. At that density the labels are also unreadably stacked, so the cost buys nothing.Capping to the N largest clusters would fix both halves. Left unfixed because picking N is a product decision about what the map should show, not a mechanical fix.
2.
label_clustersassumes a shape contract it does not checkinvokeai/app/services/image_index/cluster_labels.py:117The function assumes
len(vocabulary) == vocab_embeddings.shape[0]and that the vocabulary is non-empty:count = min(top_k, len(vocabulary))comes from the list whilescoresis sized by the matrix. If the vocabulary is longer,np.argpartition(-scores, count - 1)raisesValueError; if shorter,vocabulary[top[0]]raisesIndexError.cluster_vocab.txtgivescount == 0and sonp.argpartition(-scores, -1).Nothing reachable violates this today — the vocabulary and its embeddings are built together and cached as a pair, with a fingerprint check on load. But the test double in
tests/app/routers/test_image_map.pyalready leans onDIM == 4matching its four-phrase vocabulary, so changingDIMalone would trip it. A one-line assert at the top of the function turns an assumption into an enforced contract.Context
Found by an adversarial review of #44 (
feat/image-map-11-cluster-labels), the last PR in the image-map stack. The two high and five medium findings from that review are fixed on that branch. A third low — an unclosednp.loadhandle — was fixed there too rather than filed, because it sat in the same lines as the atomic-write fix.