Skip to content

Cluster labels: uncapped annotations, and a shape contract label_clusters assumes rather than checks #101

Description

@lstein

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions