feat: metadata filtering (#45) and force-categorical toggle (#35) - #51
Merged
Conversation
…RaredonLab#35) Closes RaredonLab#35 Closes RaredonLab#45 Both issues are the same question asked twice — "what kind of thing is this metadata column?" — so they share one module, readers/metadata_filter.py. RaredonLab#35 — treat as categorical Integer cluster IDs from Seurat were routed to a viridis gradient by dtype. color_values() and edge_color_values() now take an explicit `categorical` override, and category labels sort numerically so cluster 10 follows 2. _color_values_meta moves to the base class. All six readers carried a near-identical copy and the copies had drifted: CosMx filled NaN with ""/0 where the others dropped it, and only some sorted with key=str. A reader now supplies only _metadata_frame(). The frontend stops guessing the type from the schema dtype — it could not, since the rule also depends on cardinality. The old guess disagreed for exactly the columns this issue is about: the canvas drew discrete colours while the panel showed a gradient with two sliders that did nothing. Panel 0 now reports the type the backend returned. That also removed the duplicate color-values fetch both legends were making for themselves. RaredonLab#45 — select cells and edges by metadata A MetadataFilter is a categorical allowlist or an inclusive numeric range, resolved server-side and applied BEFORE sampling. That ordering is the point: both queries sample on the server, so a client-side filter would leave a fraction of a subset — a cluster holding 5% of cells at a 10% sample would draw 0.5% of the tissue. cell_boundaries() takes cell_ids in all five readers. query_grouped() takes cell_ids and edge_filter; an edge survives only when both endpoints do, since a half-outside edge runs off to a cell that is not drawn. Large id sets go through a registered DuckDB relation rather than an IN list. An unknown column raises 400 rather than rendering everything under an apparently-active filter. Also fixed, because the filter exposed it: useCellBoundaries picks its auto fraction from the previous fetch's total, which a filter invalidates, and nothing else triggered a refetch — so the layer sat showing a tenth of an already-small subset until the user panned. It now recalibrates once. Guard extended to 191 probes across 7 datasets, covering both features on every platform. The demo fixture gains a 12-level seurat_clusters column so the repo's own data reproduces RaredonLab#35. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Closes #35
Closes #45
Both issues are the same question asked twice — "what kind of thing is this metadata column?" — so they share one module, readers/metadata_filter.py.
#35 — treat as categorical
Integer cluster IDs from Seurat were routed to a viridis gradient by dtype.
color_values() and edge_color_values() now take an explicit
categoricaloverride, and category labels sort numerically so cluster 10 follows 2.
_color_values_meta moves to the base class. All six readers carried a
near-identical copy and the copies had drifted: CosMx filled NaN with ""/0
where the others dropped it, and only some sorted with key=str. A reader now
supplies only _metadata_frame().
The frontend stops guessing the type from the schema dtype — it could not,
since the rule also depends on cardinality. The old guess disagreed for
exactly the columns this issue is about: the canvas drew discrete colours
while the panel showed a gradient with two sliders that did nothing. Panel 0
now reports the type the backend returned. That also removed the duplicate
color-values fetch both legends were making for themselves.
#45 — select cells and edges by metadata
A MetadataFilter is a categorical allowlist or an inclusive numeric range,
resolved server-side and applied BEFORE sampling. That ordering is the point:
both queries sample on the server, so a client-side filter would leave a
fraction of a subset — a cluster holding 5% of cells at a 10% sample would
draw 0.5% of the tissue.
cell_boundaries() takes cell_ids in all five readers. query_grouped() takes
cell_ids and edge_filter; an edge survives only when both endpoints do, since
a half-outside edge runs off to a cell that is not drawn. Large id sets go
through a registered DuckDB relation rather than an IN list.
An unknown column raises 400 rather than rendering everything under an
apparently-active filter.
Also fixed, because the filter exposed it: useCellBoundaries picks its auto fraction from the previous fetch's total, which a filter invalidates, and nothing else triggered a refetch — so the layer sat showing a tenth of an already-small subset until the user panned. It now recalibrates once.
Guard extended to 191 probes across 7 datasets, covering both features on every platform. The demo fixture gains a 12-level seurat_clusters column so the repo's own data reproduces #35.