Repository navigation
Conversation
…rence Adds ordering=visual_similarity (and the reverse) to the occurrence list. Occurrences are ranked by the cosine distance between their feature vector and the vector of a seed occurrence, most similar first, and occurrences without a vector come last in both directions. Only one algorithm's vectors are compared at a time: similarity_algorithm picks it, and the default is the algorithm with the most vectors in the project. The seed is similar_to, or by default the most recently updated occurrence that has a vector. A view-level default ordering cannot replace the similarity order, because a view can list orderings it applies itself. In the interface, the details page shows "Show similar occurrences" only when the occurrence has a vector (from the embedding_algorithms list on the detail response), and links with the first algorithm that has one. The occurrences page shows a "Similar to occurrence" filter chip, the Snapshots column sorts by similarity only while a seed is set, and choosing any other sort drops the seed from the address. Stacked on the feature vector storage pull request (#1462). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…y sort ordering=visual_similarity without similar_to now returns 400 instead of guessing the most recently updated occurrence as the seed. The guessed seed cost three extra queries and gave a list that depended on whichever occurrence had changed last. similarity_algorithm still defaults to the model with the most vectors in the project. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…he distances The pagination COUNT carried the correlated cosine-distance subquery, so a sorted page evaluated every distance twice. The count now drops the annotation and the ordering, as the project and taxa lists already do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…list JIT compilation was measured at about 1.0 s of a 1.5 s first page. SET LOCAL inside the request's transaction limits the change to this list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
Above SIMILARITY_SORT_MAX_OCCURRENCES (default 50,000, set from the environment) the sorted list returns 400 asking the user to narrow the filters. The check runs on the page count, before any distance is computed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…t despite default filters When the project's default score or taxa filters hid the occurrence being compared against, the sorted list opened without it. apply_default_filters takes a keep_pk that exempts one occurrence from the defaults; filters the user chose still apply to it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
… menu The occurrence details page lists one 'Show similar by <model>' entry per model that has a feature vector for the occurrence, in a More actions menu, instead of linking with whichever model comes first. This is a prototype awaiting design. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…the first click When a seed occurrence is set, the sortable column is headed Similarity (with a tooltip) instead of Snapshots. The table header used to start every newly chosen sort descending; it now honours the column's default order, so the first click on Similarity shows the most similar occurrences first. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…similarity sort Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
✅ Deploy Preview for antenna-preview canceled.
|
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
6 tasks done
The sort control showed a downward arrow for ascending order while the table header showed an upward arrow for the same order. Ascending now points up in both, so most-similar-first reads the same in the control and in the header. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…sort when its seed is cleared The occurrence list request only forwards the filters the page registers, so the similarity_algorithm picked in the More actions menu never reached the API and the default model was always used. It is now a registered filter, shown read-only as "Compared by model" with the model's name, and dropped with the seed when another sort is chosen. Clearing the seed chip also removes the similarity ordering, which would otherwise be refused by the API for lacking a seed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…imilarity-sorted list Rows of a list sorted by visual similarity carry visual_similarity, the cosine similarity to the compared occurrence (1 is identical), or null for an occurrence with no vector from that model. The field is absent from every other list, from the detail view and from exports. The distance annotation is renamed visual_distance so the name says what it holds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…ilarity-sorted list A prototype awaiting design: the percentage sits under the thumbnail while the list is sorted by similarity, and a dash marks an occurrence without a vector. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
… and note the new field The count comment no longer claims that PostgreSQL evaluates the distance twice: measured, the planner drops the unused subquery output, and stripping it still roughly halves the count. See #1509. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…into feat/similarity-sort
…into feat/similarity-sort # Conflicts: # ami/exports/format_types.py # docs/claude/reference/canonical-patterns.md
…ranch) into the similarity sort The sort builds on the filters and the occurrence's embedding_algorithms that moved to #1525, so it now follows that branch. The vector table has no key column, so the representative vector and the default model take an algorithm only; the occurrence filter queries come from #1525's builders. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…on) into the similarity sort Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…rity sort Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
mihow
changed the base branch from
feat/detection-embeddings-task
to
feat/feature-vectors-in-filters-and-ui
October 16, 2026 07:20
…) into the similarity sort #1462 removed the reader module, so it comes back here with only what the sort calls: vector counts per algorithm, the algorithm with the most vectors (the sort's default model) and an occurrence's representative vectors. Their tests join TestReadingVectors. The similarity tests insert vectors directly, with declared lengths and the capture's project, as DetectionEmbedding.objects.store() is gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPrBDvE8e49rB4o811zHZi
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
Stacked on #1525 (vectors on the occurrence page and in the filters), which builds on #1462 (feature vector storage). That PR stores a feature vector for each detection but, on purpose, ships almost nothing that reads them. This one is the first thing a person can do with the stored vectors: open an occurrence, ask "which other occurrences look like this one?", and get the occurrence list ordered from most to least similar, according to the model they pick. A reviewer checking an unfamiliar moth can jump from it to its closest look-alikes, and a taxonomist can compare how two different models rank the same neighbours.
Nothing about identification changes. The sort only reorders the existing occurrence list; the project's default filters, permissions and every other filter still apply. Occurrences that have no vector from the chosen model sort last, in both directions. It is also the first reader of the vector table at list scale, so most of the changes below are guard rails that keep a sorted list from being slow or confusing. The storage plan is in #1457 (comment).
List of Changes
GET /occurrences/?ordering=visual_similarity&similar_to=<id>[&similarity_algorithm=<id>]; cosine distance from the occurrence's representative detection's vector, computed in a correlated subquery (OccurrenceQuerySet.with_visual_similarity,representative_embeddings);self_applied_orderingsso the ordering filter does not drop itsimilar_tothe API answers 400 with a clear messageSingleParamSerializerwithrequired=Truesimilarity_algorithmstill defaults to the model with the most vectors in the projectSET LOCAL jit = off, scoped to the request's transaction); the page count no longer carries the distance subquery (OccurrencePagination.get_count)<=>; the statement is issued only for this ordering and before the first distanceSIMILARITY_SORT_MAX_OCCURRENCES(environment variable, default 50,000), checked on the page count before any distance is computedapply_default_filters(..., keep_pk=seed)ORs the seed into the default filters onlyPopoverfrom the UI kit and the existing link button styles; each entry passes its ownsimilarity_algorithm; replaces a single link that used the first modelTableColumn.defaultSortOrderis now honoured by the table header's first click (it only affected the sort control before); a read-only "Similar to occurrence" chip clears when another sort is chosenSortControlrotated its arrow the opposite way toTableHeadersimilarity_algorithmwas not a registered filter, so the list request never sent it and the default model was always used; it is now registered, shown read-only as "Compared by model", and cleared with the seed. Clearing the "Similar to occurrence" chip also ends the similarity sortvisual_similarityfield (cosine similarity, 1 is identical, null without a vector) from the existing distance annotation, which is renamedvisual_distance; absent from other lists, the detail view and exports; no extra queriesOpen questions
SIMILARITY_SORT_MAX_OCCURRENCES.similarity_algorithmis omitted, the model with the most vectors in the project is used. If the seed has no vector from that model the request is refused. Would it be better to require the model as well?Detailed Description
Where the time goes (measured)
Profiled in-process against two PostgreSQL 16 databases with pgvector, the occurrence list endpoint at 20 rows per page, cachalot off, no debug toolbar, one local machine:
Observations and what they imply:
What we still need to verify
shared_bufferscan hold the vectors of a typical project (a 50,000-vector model is about 280 MB).Notes for reviewers
SingleParamSerializer): a non-integer, zero, an occurrence outside the project, and an occurrence without a vector from the chosen model each return 400 naming the parameter.visual_similarityis read from the existing annotation, so it costs no query; a test pins that the plain list, the detail view and the export serializer do not have it.How to Test
ami/main/test_visual_similarity.py).GET /api/v2/occurrences/?project_id=<p>&ordering=visual_similarity&similar_to=<occurrence id>returns the seed first and its nearest neighbours next; addingsimilarity_algorithm=<id>switches the model;ordering=-visual_similarityreverses it with vector-less occurrences still last.similar_to: the response is 400 namingsimilar_to.SIMILARITY_SORT_MAX_OCCURRENCES=3and repeat on a list of more than 3: 400 asking to narrow the filters; add a filter that brings the list under 3 and it sorts.python manage.py test ami.main.test_visual_similarity ami.ml.test_detection_embeddings;cd ui && yarn lint && npx tsc --noEmit && yarn test.Screenshots
The More actions menu on an occurrence that has vectors from two models (prototype, awaiting design):
The list after choosing "Show similar by Color-sensitive backbone": the compared occurrence first, then its neighbours, each with its similarity percentage; the sortable column is headed Similarity, and the filters show the compared occurrence and the model used:
The same occurrence compared by the other model, which has vectors for only eight of the twelve occurrences, so the other four come last with a dash:
These were taken on a throwaway stack with a generated demo project and synthetic four-value vectors, so the neighbours shown are by construction (one model ranks by colour, the other by size).
Checklist
main, Store feature vectors for Detections, introduce BaseEmbedding model and general patterns for vectors #1462 and Show which models have feature vectors on an occurrence, and filter occurrences by them #1525 at their current heads), run serially: 936 tests pass, 2 skipped;makemigrations --checkpassestsc --noEmitand ESLint pass on the UI after merging Show which models have feature vectors on an occurrence, and filter occurrences by them #1525🤖 Generated with Claude Code
https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy