napari_track_edit.data_views.views.tree_view.tree_plot_fpl
Attributes
Classes
fastplotlib (pygfx/wgpu) canvas for the lineage tree. |
Functions
|
Mark a rendercanvas canvas as closed once its C++ widget is gone. |
Module Contents
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._SELECT_COLOR
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._EDGE_NONE
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._SELECT_EDGE_WIDTH = 2.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._BASE_SIZE = 10.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._SELECT_BUMP = 6.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._SELECT_BUMP_MIN = 2.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._MIN_SIZE = 3.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._MAX_SIZE = 100.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._MARKER_FILL = 0.9
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._SIZE_SCALE_EPS = 0.02
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._NODE_LABEL_MIN_SIZE = 20.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._NODE_LABEL_MAX = 400
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._NODE_LABEL_COLOR = (0.0, 0.0, 0.0, 1.0)
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._NODE_LABEL_FONT_FRACTION = 0.5
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._NODE_LABEL_DIGIT_WIDTH = 0.55
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._DOCK_TRACK_ID_PX = 22.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._TRACK_LABEL_MIN_PX = 30.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._TRACK_LABEL_MAX = 200
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._TOOLTIP_OFFSET
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._SYMBOL_SIZE_FACTOR
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._RIGHT_CLICK_DRAG_PX = 4.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._MARKER_CODES
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._DEFAULT_MARKER = 101
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._AXIS_COLOR = (0.6, 0.6, 0.6, 1.0)
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._DOCK_LEFT_PX = 54.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._DOCK_BOTTOM_PX = 34.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._DOCK_MINIMAL_PX = 2.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._DOCK_WORLD = 100.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._AXIS_LINE = 88.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._AXIS_EDGE = 10.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._DOCK_LEFT_MAX_PX = 120.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._TICK_LABEL_CHAR_PX = 0.55
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._TICK_LABEL_MARGIN_PX = 14.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._DOCK_RESIZE_EPS_PX = 2.0
- napari_track_edit.data_views.views.tree_view.tree_plot_fpl._flag_canvas_closed(canvas_ref: weakref.ref) None
Mark a rendercanvas canvas as closed once its C++ widget is gone.
- class napari_track_edit.data_views.views.tree_view.tree_plot_fpl.TreePlot
Bases:
qtpy.QtWidgets.QWidgetfastplotlib (pygfx/wgpu) canvas for the lineage tree.
Drop-in replacement for the pyqtgraph
TreePlot: exposes the same signals (node_clicked,jump_to_node,pick_track_id,nodes_selected,update_selection) and the same public methods (update,set_selection,set_view,_update_viewed_data,center_on_node,setMouseEnabled) soTreeWidgetneeds no changes beyond which class it instantiates.- node_clicked
- jump_to_node
- pick_track_id
- nodes_selected
- update_selection
- _closed = False
- _scrolled = False
- view_direction = 'vertical'
- plot_type = 'tree'
- feature = None
- track_df
- _node_ids
- _id_to_row: dict[int, int]
- _positions
- _base_colors
- _base_sizes
- _edge_colors = None
- _selected_rows: list[int] = []
- _size_scale = 1.0
- _row_track_ids
- _row_lineage_ids = None
- _row_times
- _lane_track_ids: dict[int, int]
- show_track_ids = False
- show_hover_info = True
- _drag_start: tuple[float, float] | None = None
- _rubber = None
- _rpress_xy: tuple | None = None
- _figure
- _subplot
- _scatter = None
- _edges = None
- _dock_left
- _dock_bottom
- _ruler_time
- _ruler_feature
- _time_label
- _track_labels
- _node_labels
- _animations
- _canvas_handlers
- property scrolled: bool
Whether the user scrolled since the last reset_scrolled call.
X and Y double as zoom modifiers (held down while scrolling) and as plain keyboard shortcuts, so TreeWidget resets this when such a key goes down and checks it when the key comes back up.
- reset_scrolled() None
Forget any scrolling seen so far.
- _on_canvas_wheel(ev) None
Record that the user scrolled over the canvas.
- setMouseEnabled(x: bool, y: bool) None
Restrict zoom/pan to the given axes (X or Y key held). Reconfigures the subplot’s own PanZoomController so it stays correctly event-registered.
- close_figure() None
Close the fastplotlib figure while its Qt widgets are still alive. Closing the figure ourselves, before the widgets are deleted, unregisters the canvas and releases the wgpu/pygfx resources, so it does not crash with “wrapped C/C++ object of type QRenderWidget has been deleted”.
- closeEvent(event) None
- update(track_df: pandas.DataFrame, view_direction: str, plot_type: str, feature: str, selected_nodes: list[Any], reset_view: bool | None = False, allow_flip: bool | None = True) None
- set_view(view_direction: str, plot_type: str, reset_view: bool | None = False, allow_flip: bool | None = True) None
Store the view direction / plot type. Axis rulers update themselves each frame in
_update_rulers;allow_flipis accepted for interface parity with the old pyqtgraph TreePlot but is not needed here (orientation is derived fromview_directionin_compute_positions).
- _update_viewed_data(view_direction: str) None
Re-apply positions for the given view direction (used by flip_axes).
The rebuild drops the scatter and its selection buffers, so carry the selected node ids across it — a flip changes the layout, not what is selected.
- _configure_docks() None
Assign the two rulers to their docks and size both docks. Both axes are always shown (the perpendicular axis shows tick marks even in tree mode; its number labels are toggled per-mode in
_update_rulers). Time axis: left dock (vertical) / bottom dock (horizontal); feature axis: the other dock.
- _track_id_axis_shown() bool
The track-id axis is a tree-mode thing: a feature plot’s perpendicular axis is the feature value, which needs its own numbers.
- set_show_hover_info(show: bool) None
Enable/disable the hover tooltip (Visualization tab toggle).
- set_show_track_ids(show: bool) None
Show/hide track ids along the lane axis (Visualization tab toggle).
- _update_rulers() None
Each frame, sync both rulers’ dock cameras to the main camera’s matching range and lay each ruler along its dock edge next to the plot. The dock is a separate viewport, so the tree is clipped at the axis for free. Number labels show on the time axis always, and on the feature axis only in feature mode (tree mode shows the perpendicular axis as tick marks only, like pyqtgraph).
- _sync_left(ruler, bottom, top, minimal, label=None, negated=True) None
Lay a vertical ruler in the left dock. Normal: axis line just inside the right edge (next to the plot), tick marks toward the plot, numbers in the margin to the left. Minimal (tree-mode perpendicular axis): no line, no numbers, just tick marks near the far-left edge of the canvas.
The ruler’s value always increases from
start_postowardend_pos.negated=True(time axis / tree lanes, whose world y = -value) lays it top->bottom so values increase downward (parent-at-top).negated=False(a numeric feature axis, whose world y = +value) lays it bottom->top so feature values increase upward.
- _prepare_ruler(dock, ruler, minimal, rect)
Set up a dock to draw one of the two rulers: frame its camera on
rect(the visible range along the ruler, across the dock’s own world width) and pick where the axis sits inside it — next to the plot for a numbered axis, at the canvas edge for a minimal one.Returns
(viewport_size, axis_position), or None if the dock’s viewport is transiently degenerate, in which case the caller must leave the ruler alone (keep the last-good geometry rather than hide it).
- _finish_ruler(dock, ruler, dv, minimal)
Lay out the ruler’s ticks for the range its dock camera now shows, and drop the parts a minimal axis does without (its line and its numbers). Returns the stats dict from
Ruler.update(tick_stepandtick_values).
- _fit_left_dock(ruler, tick_values) None
Widen the left dock when its tick labels no longer fit in it (and let it shrink back when they do). Only the vertical rulers need this: their labels grow sideways, into the dock’s narrow dimension.
Resizing here cannot feed back on itself — the dock’s width does not affect the vertical span the ruler labels, so the labels stay put and the size settles after one frame.
- _sync_bottom(ruler, left, right, minimal, label=None) None
Lay a horizontal ruler in the bottom dock. Normal: axis line just inside the top edge (next to the plot), tick marks toward the plot, numbers in the margin below. Minimal (tree-mode perpendicular axis): no line, no numbers, just tick marks near the bottom edge of the canvas.
- _update_track_labels(left, right, bottom, top) None
Lay one track id per visible lane in the perpendicular axis’ dock, thinning them out so neighbouring labels stay at least
_TRACK_LABEL_MIN_PXapart.Lanes are one unit apart, so the step is a lane count, and lanes that fall between two labels are simply skipped rather than drawn on top of each other.
- static _ensure_text_blocks(multi_text, count: int) None
Grow a MultiText to at least
countblocks. Blocks are never removed — they are reused across frames, and surplus ones are blanked by the caller.
- _update_node_labels() None
Write each on-screen node’s id inside its marker, once the markers are drawn big enough to hold the text and few enough of them are visible.
- _axis_value_column() str
- _compute_positions(df: pandas.DataFrame) numpy.ndarray
(N, 3) float32 positions. Vertical: (axis_value, -t). Horizontal swaps.
In horizontal view the perpendicular (y) axis is negated so tree lanes read top-to-bottom like the old pyqtgraph tree. But for a numeric feature the y-axis should increase upward (larger values higher), so feature positions are NOT negated. Feature plots are always horizontal (see TreeWidget), so this only affects the horizontal branch.
- _rebuild() None
Full rebuild of the scene from
self.track_df. Only called when the data or view direction changes — not on selection.
- _build_track_lookups(df: pandas.DataFrame) None
Per-row track ids (and lineage ids, where derivable) for the hover tooltip, plus the lane -> track id map behind the track-id axis.
A lineage id is the track id at the root of the lineage: follow parent_track_id (0 means “no parent”) up from each track. Deriving it here keeps the tree view independent of funtracks’ optional lineage feature.
- _build_edges(df: pandas.DataFrame, colors: numpy.ndarray)
Vectorized edge build for a single NaN-separated
gfx.Line.Returns
(positions, colors)where positions is(3E, 3)laid out as[parent, child, NaN]per edge (the NaN row breaks the polyline into independent segments), and colors matches. Returns(None, None)if there are no edges.
- _compute_size_scale() float
Multiplier on
_base_sizesat the current zoom, clamped to[_MIN_SIZE, _MAX_SIZE]. Tree mode only; feature mode returns 1.0, and a transiently degenerate viewport/camera keeps the current scale.The two axes are not symmetric. Lanes are what must not collide, so the lane spacing alone decides how far the markers shrink — a track densely sampled in time is meant to read as a continuous run of overlapping dots, and letting the time spacing shrink things too would leave the markers permanently at the floor. Growing is the other way round: a marker only grows past its base size once both spacings can take it, so zooming x alone stretches the tree without inflating the dots into their vertical neighbours.
- _update_marker_scale() None
Per frame: resize the markers if the zoom changed the lane width enough.
- _select_bump() float
Extra size for a selected node: shrinks with the markers, but never below
_SELECT_BUMP_MIN, so the selection stays visible when zoomed out.
- set_selection(selected_nodes: list[Any], plot_type: str) None
- _set_edge_color(row: int, rgba: numpy.ndarray) None
Set one node’s per-vertex outline color and upload just that element.
- _on_canvas_pointer_down(ev) None
- _on_canvas_pointer_move(ev) None
- _on_canvas_pointer_up(ev) None
- _ensure_rubber()
- _update_rubber(start: tuple, cur: tuple) None
- _clear_rubber() None
- _reset_view() None
Fit the view to the node positions, filling the canvas.
Framed directly from the scatter data via
camera.show_rectrather thanauto_scale: auto_scale unions the bounds of all scene objects (the rulers, the NaN-separated edge line, the idle rubber rectangle), so it can’t reliably tighten to the tree — e.g. when zoomed out, the rulers sit at the wide viewport edges. Framing fromself._positionsignores everything else. maintain_aspect=False fills the wide canvas; request_draw is required because a camera change doesn’t repaint on its own between interactions.
- _on_click(ev) None
- _on_hover(ev) None
Tooltip with the identity of the node under the pointer.
- _on_hover_leave(ev) None
- _describe_node(row: int) str
Tooltip text for one row: node, track, lineage (when derivable) and time.
- select_points_in_rect(x0, x1, y0, y1) None
Box-select: emit all node ids whose positions fall in the rect.
- center_on_node(node_id: int) None
- _rows_fit(rows: list[int]) bool
Whether all the given rows are inside the current viewport. True (nothing to do) for an empty list, and if the camera can’t be read.
- _center_on_rows(rows: list[int]) None
Bring the given rows into view.
Keeps the current zoom if the rows already fit in the viewport; otherwise zooms out just enough (per axis) to fit them, and never zooms in. Used for single-node centering and for multi-node selection — e.g. the two endpoints of a just-broken edge, which may now sit far apart: rather than pan to their midpoint at the current zoom (leaving both off-screen), we widen the view so both are visible.