napari_track_edit.data_views.colormap
Attributes
Classes
Maps an array of ids/values to an (N, 4) RGBA array. |
|
Default ColorSource: cyclic color per unique id, 0 -> transparent. |
|
Two colors for a boolean feature - the shape every group has. |
|
One color for every node |
|
Node -> display color for a Tracks object, with color and alpha as |
Functions
A DirectLabelColormap built without pydantic's per-color validation. |
|
|
The ColorSource that can render a feature's values. |
|
Whether feature_key's values can actually be read off tracks. |
|
The node features that can currently drive coloring: tracklet id, lineage id, and |
|
The label to show for a feature in the UI. |
Module Contents
- napari_track_edit.data_views.colormap.PINK = (0.75, 0.08, 0.4)
- napari_track_edit.data_views.colormap.GREY = (0.7, 0.7, 0.7)
- napari_track_edit.data_views.colormap.construct_direct_colormap(color_dict: dict) napari.utils.DirectLabelColormap
A DirectLabelColormap built without pydantic’s per-color validation.
transform_color on every entry is the ~400x-slower path on large graphs, and every color handed over here is already a properly-shaped (4,) float array - napari’s validation is there for arbitrary user input (color names, 3-channel colors), not for values built this way.
napari’s models are native pydantic v2 from 0.7 on, where that constructor is model_construct. Before that they are built on pydantic.v1, which calls it construct.
- class napari_track_edit.data_views.colormap.ColorSource
Bases:
ProtocolMaps an array of ids/values to an (N, 4) RGBA array.
Duck-type compatible with CyclicLabelColormap.map, so it’s a drop-in replacement anywhere a napari colormap’s .map() is used for track-id coloring. Swap in a continuous-feature or constant-color source later without touching TrackColormap or its consumers.
shuffle() re-draws whatever colors the source uses, so the “new colormap” button does the right thing whichever source is installed.
- map(values: numpy.ndarray) numpy.ndarray
- shuffle() None
- class napari_track_edit.data_views.colormap.CategoricalColorSource(num_colors: int = 49, seed: float = 0.5)
Default ColorSource: cyclic color per unique id, 0 -> transparent.
Not track-specific - works for any categorical id (cell type, lineage id).
- _cyclic_colormap
- map(values: numpy.ndarray) numpy.ndarray
- shuffle(num_colors: int | None = None, seed: float | None = None) None
Replace the color cycle (see TrackLabels.new_colormap). With no arguments a random cycle is drawn, matching the argument-less ColorSource.shuffle that the “new colormap” button calls.
- class napari_track_edit.data_views.colormap.BinaryColorSource(false_color=GREY, true_color=PINK)
Two colors for a boolean feature - the shape every group has.
- false_color
- true_color
- map(values: numpy.ndarray) numpy.ndarray
- shuffle() None
Get two fresh colors
- class napari_track_edit.data_views.colormap.ConstantColorSource(color=GREY)
One color for every node
- color
- map(values: numpy.ndarray) numpy.ndarray
- shuffle() None
- napari_track_edit.data_views.colormap.make_color_source(tracks: funtracks.data_model.Tracks | None, feature_key: str | None) ColorSource
The ColorSource that can render a feature’s values.
A source and a feature have to match: a group’s True/False needs two colors, no feature at all means one flat color, categorical features get random colors via CategoricalColorSource. TODO: GradientColorSource for continuous features.
- napari_track_edit.data_views.colormap.color_feature_available(tracks: funtracks.data_model.Tracks | None, feature_key: str | None) bool
Whether feature_key’s values can actually be read off tracks.
tracks.features only describes features; it is a plain dict that nothing keeps in sync with the graph (FeatureDict.from_json rebuilds it from saved metadata without consulting the graph), so a feature can be described without having a column to read. Coloring by one of those raises KeyError in get_nodes_attr, so callers check here first - the tree view does the same thing inline (see extract_sorted_tracks).
None (one flat color) reads nothing, so it is always available.
- napari_track_edit.data_views.colormap.categorical_feature_keys(tracks: funtracks.data_model.Tracks | None) list[str]
The node features that can currently drive coloring: tracklet id, lineage id, and every group (solution is excluded for now).
Features with no column on the graph are left out - see color_feature_available. The column set is fetched once here rather than per key, since reading it can hit the database.
- napari_track_edit.data_views.colormap.feature_display_name(tracks: funtracks.data_model.Tracks | None, feature_key: str | None) str
The label to show for a feature in the UI.
- class napari_track_edit.data_views.colormap.TrackColormap(color_source: ColorSource | None = None, feature_key: str | None = None, default_alpha: float = 1.0)
Node -> display color for a Tracks object, with color and alpha as independently updatable state (unlike DirectLabelColormap, which conflates them in one color_dict).
to_direct_colormap() builds a fresh napari colormap on every call via construct_direct_colormap, which skips pydantic’s per-color validation entirely (the expensive part of a normal DirectLabelColormap( …) call) - see that method. This replaces three copies of a mutate-in-place-then-clear-cache trick that used to live in TrackLabels, custom_table_widget, and ortho_views.py, working around that validation cost; with it gone, there’s nothing left to work around.
Color is a two-step composition, node -> feature value -> RGB: feature_key names the Tracks node attribute to read (default: the track id attribute, tracks.features.tracklet_key) via tracks.get_nodes_attr, and color_source maps that value to a color. Swapping feature_key (e.g. to an area/volume attribute) or color_source (e.g. to a continuous colormap) are independent, composable changes - neither needs to know about the other.
There’s no per-node color setter, since a node’s color should always be a pure function of its feature value: recoloring happens by changing color_source (e.g. shuffle) and calling set_tracks() again to re-derive colors. add_node is the exception, for nodes that need a color before Tracks knows about them.
set_tracks() does the full O(node count) node/color recompute immediately - not lazily. set_alpha never triggers it: it only ever touches alpha, so the hot path (every selection/hover change) stays cheap.
- _color_source: ColorSource
- _feature_key = None
- _default_alpha = 1.0
- _tracks: funtracks.data_model.Tracks | None = None
- _node_colors: dict[int, numpy.ndarray]
- _alpha: dict[int, float]
- _pending: set[int]
- _lookup: tuple[numpy.ndarray, numpy.ndarray] | None = None
- property color_source: ColorSource
- property feature_key: str | None
- set_feature(feature_key: str | None, color_source: ColorSource | None = None) None
Color by feature_key, rendered with color_source.
- property colors_by_track_id: bool
Whether the feature being colored by is the track id.
- _feature_values(tracks: funtracks.data_model.Tracks, nodes) list
- map(values: numpy.ndarray) numpy.ndarray
Map feature values to base RGBA (no per-node alpha, no cache lookup). Delegates straight to color_source, so this is a drop-in replacement anywhere a napari colormap’s .map() is used for track-id coloring.
Unlike get_color/get_colors, this never consults _node_colors - values here are feature values (e.g. track ids), not node ids, so there’s no “unknown node” case to fall back on.
- set_tracks(tracks: funtracks.data_model.Tracks | None) None
Point this colormap at a Tracks object and recompute node colors from it immediately (O(node count) - color_source.map + one vectorized feature lookup). Existing per-node alpha overrides for nodes that are still present are preserved; overrides for removed nodes are dropped and new nodes default to default_alpha.
Nodes added by add_node keep the color it gave them until Tracks knows about them.
- add_node(node: int, track_id: int) None
Color a node not yet known to self._tracks, so that it can be painted with (TrackLabels._new_label). Alpha defaults to default_alpha, and set_tracks leaves the color alone until the node reaches the graph, where its own feature value takes over.
The node is given the color it will keep wherever the feature’s value for it can be worked out in advance, so that what you paint with is what you end up with. A grey color is used when the color cannot be known in advance.
- remove_node(node: int) None
- set_alpha(nodes, value: float) None
Set alpha for many nodes at once - the hot path, fired on every selection/hover change. Only ever touches alpha, never node colors.
- get_alpha(node: int, default: float = 0.0) float
A node’s display alpha, only needed by labels layers and included via to_direct_colormap.
- get_color(node: int) numpy.ndarray
A node’s own RGBA color, fully opaque; transparent black if this colormap doesn’t know the node. For its display alpha see get_alpha.
- get_colors(nodes: numpy.ndarray) numpy.ndarray
Vectorized get_color: each node’s own RGBA color, in order, fully opaque; transparent black for nodes this colormap doesn’t know. For napari-independent consumers (the points and tracks layers, the tree, the table, an export) that need many colors at once without going through to_direct_colormap().
Alpha is left out, since only the labels layers need it, and they get it via to_direct_colormap.
Vectorized because the points layer, the tree and the tracks layer each ask for every node on every refresh: on a 37k-node graph this is ~2ms against ~50ms for a get_color call per node, plus ~13ms on the first call after the node set changes, which rebuilds _color_lookup.
- _color_lookup() tuple[numpy.ndarray, numpy.ndarray]
_node_colors as (sorted node ids, matching RGB rows), for get_colors. Built on demand and kept until the node set changes.
- property nodes
- to_direct_colormap() napari.utils.DirectLabelColormap
Build a fresh napari DirectLabelColormap for the current color/alpha state.
Skips pydantic’s per-color validation - see construct_direct_colormap.
- _colored(node: int) numpy.ndarray
RGB from _node_colors plus current alpha, as one RGBA array.