napari_track_edit.data_views ============================ .. py:module:: napari_track_edit.data_views Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/napari_track_edit/data_views/colormap/index /autoapi/napari_track_edit/data_views/dims_utils/index /autoapi/napari_track_edit/data_views/keybindings_config/index /autoapi/napari_track_edit/data_views/lazy_array_wrapper/index /autoapi/napari_track_edit/data_views/node_type/index /autoapi/napari_track_edit/data_views/views/index /autoapi/napari_track_edit/data_views/views_coordinator/index Classes ------- .. autoapisummary:: napari_track_edit.data_views.TreeWidget napari_track_edit.data_views.TrackGraph napari_track_edit.data_views.TrackLabels napari_track_edit.data_views.TrackPoints napari_track_edit.data_views.NodeSelectionHistory napari_track_edit.data_views.TracksViewer Package Contents ---------------- .. py:class:: TreeWidget(viewer: napari.Viewer) Bases: :py:obj:`qtpy.QtWidgets.QWidget` fastplotlib-based widget for lineage tree visualization and navigation .. py:attribute:: lineage_df .. py:attribute:: graph :value: None .. py:attribute:: mode :value: 'all' .. py:attribute:: plot_type :value: 'tree' .. py:attribute:: view_direction :value: 'vertical' .. py:attribute:: tracks_viewer .. py:attribute:: selected_nodes .. py:attribute:: tree_widget :type: napari_track_edit.data_views.views.tree_view.tree_plot_fpl.TreePlot .. py:attribute:: mode_widget .. py:attribute:: plot_type_widget .. py:attribute:: navigation_widget .. py:attribute:: flip_widget .. py:attribute:: options_widget .. py:method:: cleanup() -> None Tear down the tree view: stop reacting to TracksViewer signals, then close the wgpu canvas while its Qt widgets are still alive. Idempotent. Called by MenuManager when the widget is destroyed, and by the event hooks below when napari's main window goes away. Disconnecting comes first, so that nothing tries to redraw a figure that is already closed. .. py:method:: eventFilter(obj: qtpy.QtCore.QObject, event: qtpy.QtCore.QEvent) -> bool Trigger clean up when napari's main window is being destroyed .. py:method:: closeEvent(event) -> None .. py:method:: keyPressEvent(event: qtpy.QtGui.QKeyEvent) -> None Handle key press events. Priority order: 1. Tree-widget-specific keybinds (highest priority) - call TreeWidget methods 2. Modifier keybinds (mouse zoom constraints) 3. General keybinds (work in table widget too) - call tracks_viewer methods 4. Navigation (arrow keys) .. py:method:: delete_node() Delete a node. .. py:method:: connect_nodes_with_divisions() Connect or disconnect the selected nodes, keeping existing divisions. .. py:method:: connect_nodes_linearly() Connect or disconnect the selected nodes into a single linear track. .. py:method:: swap_nodes() Swap the nodes by swapping upstream edges .. py:method:: set_division() Make or break a division between the three selected nodes .. py:method:: undo() Undo action. .. py:method:: redo() Redo action. .. py:method:: deselect() Deselect all nodes .. py:method:: restore_selection() Restore previous selection .. py:method:: toggle_display_mode() Toggle display mode. .. py:method:: toggle_feature_mode() Toggle feature mode. .. py:method:: flip_axes() Flip the axes of the plot .. py:method:: set_mouse_enabled(x: bool, y: bool) Enable or disable mouse zoom scrolling in X or Y direction. .. py:method:: keyReleaseEvent(ev) Reset the mouse scrolling when releasing a zoom modifier (X/Y) key. A zoom modifier that is also a general keybind (Y, which sets a division) was only meant as a zoom modifier if the user scrolled while holding it down; a plain tap runs the general action instead. .. py:method:: _update_selected() Called whenever the selection list is updated. Only re-computes the full graph information when the new selection is not in the lineage df (and in lineage mode) .. py:method:: _update_track_data(reset_view: bool | None = None) -> None Called when the TracksViewer emits the tracks_updated signal, indicating that a new set of tracks should be viewed. .. py:method:: _set_mode(mode: str) -> None Set the display mode to all or lineage view. Currently, linage view is always horizontal and all view is always vertical. :param mode: The mode to set the view to. Options are "all" or "lineage" :type mode: str .. py:method:: _set_plot_type(plot_type: str) -> None Set the plot_type mode to 'tree' or 'feature', and adjust view direction. Also update the feature on the navigation_widget. :param plot_type: The plot type to display. Options are "tree" or "feature" :type plot_type: str .. py:method:: _update_lineage_df() -> None Subset dataframe to include only nodes belonging to the current lineage .. py:class:: TrackGraph(name: str, tracks_viewer: napari_track_edit.data_views.views_coordinator.tracks_viewer.TracksViewer) Bases: :py:obj:`napari.layers.Tracks` Extended tracks layer that holds the track information and emits and responds to dynamics visualization signals .. py:attribute:: _type_string :value: 'tracks' .. py:attribute:: tracks_viewer .. py:attribute:: full_division_edges .. py:attribute:: visible_tracks :type: object :value: 'all' .. py:method:: _refresh() Refreshes the displayed tracks based on the graph in the current tracks_viewer.tracks .. py:method:: _apply_node_colors(node_ids: list[int]) -> None Color the track lines per node instead of per track id. napari colors a Tracks layer by mapping one vertex property array through a colormap. We need to color by node id instead of tracklet_id to ensure that this colormap follows the other views. `node_ids` must be in the same row order as the data that was just assigned: napari sorts the vertices by (track id, time) and reorders the properties to match, using the order it recorded for that data. .. py:method:: _set_division_edges(division_edges: dict[int, list[int]]) -> None Hand the graph to napari. The `Tracks.graph` setter revalidates every entry and then rebuilds every graph vertex, looking each track id up against all points: 2.0 s for 34k entries over 325k points. Only called by `update_track_visibility` once it has confirmed the visible set actually changed, so that cost is paid only when it must be. .. py:method:: _set_track_alpha(visible: list[int] | str) -> None Set track opacity so that only `visible` tracks are drawn. .. py:method:: update_track_visibility(visible: list[int] | str) -> None Optionally show only the tracks of a current lineage. Do nothing if the set is already visible, to avoid unnecessary computation. .. py:class:: TrackLabels(viewer: napari.Viewer, data: numpy.array, name: str, opacity: float, scale: tuple, tracks_viewer: napari_track_edit.data_views.views_coordinator.tracks_viewer.TracksViewer) Bases: :py:obj:`napari_track_edit.data_views.views.layers.contour_labels.ContourLabels` Extended labels layer that holds the track information and emits and responds to dynamics visualization signals .. py:property:: _type_string :type: str .. py:attribute:: tracks_viewer .. py:attribute:: track_colormap .. py:attribute:: viewer .. py:attribute:: highlight_opacity :value: 1 .. py:attribute:: foreground_opacity :value: 0.6 .. py:attribute:: background_opacity :value: 0.3 .. py:attribute:: highlight_contour :value: False .. py:attribute:: foreground_contour :value: False .. py:method:: click(_, event) .. py:method:: new_label() -> None Select a valid new label to paint a new track with. Called by TracksViewer.request_new_track, which owns the "start a new track" action for all views. The label is new by construction, guard can be skipped. .. py:method:: process_click(event: napari.utils.events.Event, value: int | None = None, side_button: int | None = None, layer: napari_track_edit.data_views.views.layers.contour_labels.ContourLabels | None = None) Process the click event to update the selected nodes. :param event: The click event. :type event: Event :param value: The label value (node) at the clicked position. :type value: int :param side_button: the integer for the mouse side buttons (4: back, 5: forward) :type side_button: int | None :param layer: The (ortho view) layer from which the click originated. If provided, it is used to check label visibility in that layer's colormap. :type layer: ContourLabels | None .. py:method:: _check_mode() Check if the mode is valid and call the ensure_valid_label function .. py:method:: redo() Overwrite the redo functionality of the labels layer and invoke redo action on the tracks_viewer.tracks first .. py:method:: undo() Overwrite undo function and invoke undo action on the tracks_viewer.tracks first .. py:method:: _parse_paint_event(event_val) :staticmethod: Turn a paint event into the segmentation updates funtracks expects. napari reports the atoms of an event in one of two forms, never mixed within an event: the mask form of napari >= 0.8, and the multi-index form that ``data_setitem`` still uses. :param event_val: the paint "atoms" the labels layer recorded for this event. :type event_val: list[tuple] :returns: the updates for every label painted over, in whichever of the two forms ``UserUpdateSegmentation`` was handed. Empty when the event changed nothing. :rtype: list[tuple] .. py:method:: _revert_paint(_, source_layer: napari.layers.Labels | None = None) Revert a paint event after it fails validation (no actions have been created). This keeps the view synced with the backend data. been created). If a source_layer is provided, the paint event will be reverted on this layer (this is necessary for orthoviews). This keeps the view synced with the backend data. .. py:method:: _on_paint(event) Listen to the paint event and check which track_ids have changed .. py:method:: _refresh() Refresh the data in the labels layer .. py:method:: update_label_colormap(visible: list[int] | str) -> None Updates the opacity for the highlighted, foreground, and background labels, and adds labels to the filled_labels if necessary. .. py:method:: new_colormap() Override existing function to generate new colormap on tracks_viewer and emit refresh signal to update colors in all layers/widgets .. py:method:: update_selected_label() Update the selected label in the labels layer .. py:method:: _ensure_valid_label(event: napari.utils.events.Event | None = None) Make sure a valid label is selected, because it is not allowed to paint with a label that already exists at a different timepoint. Scenarios: 1. If a node with the selected label value (node id) exists at a different time point, check if there is any node with the same track_id at the current time point. a. If there is a node with the same track id, select that one, so that it can be used to update an existing node. b. If there is no node with the same track id, create a new node id and paint with the track_id of the selected label. This can be used to add a new node with the same track id at a time point where it does not (yet) exist (anymore). 2. If there is no existing node with this value in the graph, it is assumed that you want to add a node with the current track id. Retrieve the track_id from self.current_track_id and use it to find if there are any nodes of this track id at current time point. 3. If no node with this label exists yet, it is valid and can be used to start a new track id. Therefore, create a new node id and map a new color. Add it to the dictionary. 4. If a node with the label exists at the current time point, it is valid and can be used to update the existing node in a paint event. No action is needed. .. py:class:: TrackPoints(name: str, tracks_viewer: napari_track_edit.data_views.views_coordinator.tracks_viewer.TracksViewer) Bases: :py:obj:`napari_track_edit.data_views.views.layers.out_of_slice_points.ZOnlyPoints` Extended points layer that holds the track information and emits and responds to dynamics visualization signals .. py:attribute:: _drag_modes .. py:attribute:: data_updated .. py:property:: _type_string :type: str .. py:attribute:: tracks_viewer .. py:attribute:: nodes .. py:attribute:: node_index_dict .. py:attribute:: default_size :value: 5 .. py:method:: add(coords: list[float]) Block the current_size event before calling the 'add' function to avoid calling set_point_size (triggered by the current_size event) with a new point size. .. py:property:: selected_data :type: psygnal.containers.Selection[int] Set of currently selected point indices. .. py:method:: process_click(event: napari.utils.events.Event, value: int | None = None, side_button: int | None = None, layer: napari.layers.Points | None = None) Select the clicked point(s) :param event: The mouse event :type event: Event :param value: The index of the clicked point, or None if no point was clicked :type value: int | None :param side_button: the button index (4: back, 5: forward) if a mouse side button was used, or None if no side button was used. :type side_button: int | None :param layer: Optional, unused. The (ortho view) layer on which the click occurred, which is forwarded by default. :type layer: napari.layers.Points | None .. py:method:: set_point_size(size: int) -> None Sets a new default point size. NOTE: This function call is triggered by the current_size event, which is emitted when the user moves the 'point size' slider in the layer controls. However, this event is also emitted in the 'add' and 'select' functions, so we have to block the signals there to avoid increasing the point size by accident, since new or selected points are displayed at a 30% bigger size. .. py:method:: _refresh() Refresh the data in the points layer .. py:method:: _create_node_attrs(new_point: numpy.array) -> tuple[numpy.array, dict] Create attributes for a new node at given time point .. py:method:: _update_data(event: napari.utils.events.Event) Calls the UserActions to update the data in the Tracks object and dispatch the update .. py:method:: _update_selection() Replaces the list of selected_nodes with the selection provided by the user .. py:method:: _map_node_colors(nodes: list[int]) -> numpy.ndarray Look up an (N, 4) array of face colors, one per node. Per node rather than per track id, so the points follow whichever feature the colormap is set to. With no nodes (an empty tracks graph, e.g. when tracking from scratch) a single white color is returned instead of a (0, 4) array: napari's ColorManager treats the color argument as *the* current color when the layer holds no data, and feeding it an empty array raises in `transform_color`. .. py:method:: get_symbols(tracks: funtracks.data_model.Tracks, symbolmap: dict[napari_track_edit.data_views.node_type.NodeType, str]) -> list[str] .. py:method:: update_point_outline(visible_nodes: list[int] | str) -> None Update the outline color of the selected points and visibility according to display mode :param visible_nodes: A list of node ids, or "all" :type visible_nodes: list[int] | str .. py:class:: NodeSelectionHistory History-aware selection list for nodes with signals on updates. Records sets of selected nodes and allows navigating backwards/forwards through the selection history. Consecutive duplicates or empty selections are skipped when navigating. Deleted nodes are never returned in the _current selection list, but they are allowed to exist in the history, so that they can be 'reactivated' if placed back. When selecting next/previous node sets, sets that are empty after filtering are skipped automatically. The last selection is stored on _last_shown_set, and can be restored with the restore function, irrespective of the pointer in history. For convenience, there are helper properties to check if a next or previous node set is available for selection, and whether a valid prev_set exists (used to enable/ disable buttons). .. py:attribute:: selection_updated .. py:attribute:: _history :type: list[set[int]] :value: [] .. py:attribute:: _last_shown_set :type: set[int] .. py:attribute:: deleted_items :type: set[int] .. py:attribute:: _pointer :value: 0 .. py:attribute:: _iter_index :value: 0 .. py:property:: _current :type: set[int] Get the current selection set, minus deleted items. .. py:property:: as_list :type: list[int] Return the selected nodes as a list (arbitrary order). .. py:property:: _history_size :type: int .. py:property:: _is_at_start :type: bool Return True if the pointer is 0, the history is not empty. .. py:property:: _is_at_end :type: bool Return True if the pointer is at the last possible index of the history, if it is not empty .. py:property:: has_next_set :type: bool Return True if a next set is available for selection (non-empty after filtering). .. py:property:: has_previous_set :type: bool Return True if a previous set is available for selection (non-empty after filtering). .. py:property:: has_valid_last_shown_set :type: bool Return True if previous selection contains at least one valid item. .. py:method:: _reset_iterator() -> None Reset iteration pointer whenever selection changes. .. py:method:: _add_to_history(new_set: set[int]) -> None Add a new selection state to history if it is different from current. :param new_set: The new selection set to potentially add to history. .. py:method:: _find_next_valid_index(start: int, step: int) -> int | None Find next index (forward/backward) with non-empty filtered set. Skips indices that: - Have empty sets after filtering deleted items - Have the same filtered set as the current selection (consecutive duplicates) :param start: starting index, e.g. the current pointer :type start: int :param step: the value by which to increment (can be negative) :type step: int :returns: The next index in the history that contains at least one existing node and differs from the current selection, or None if there is no such index. .. py:method:: add(item: int, append: bool = False) -> None Append or replace an item in the selection. :param item: The node ID to add or toggle. :param append: If True, toggle item in existing selection (add if absent, remove if present). If False, replace selection with just this item. .. py:method:: add_list(items: list[int], append: bool = False) -> None Add multiple items to the selection. :param items: List of node IDs to add. :param append: If True, toggle items in existing selection. If False, replace selection with these items. .. py:method:: reset() -> None Clear all elements from the selection and update history. .. py:method:: clear() -> None Forget the selection and its history. Unlike reset, which records an empty selection as a new history entry, this drops the history itself: the node ids in it belong to a tracks object that is no longer loaded, so restoring them is meaningless. .. py:method:: restore() -> None Restore the previous selection, independent of where you are in history. .. py:method:: select_node_set_from_history(previous: bool) -> None Move forwards or backwards in selection history, skipping invalid selections. .. py:method:: __contains__(item: int) -> bool .. py:method:: __len__() -> int .. py:method:: __iter__() .. py:method:: __getitem__(index: int) -> int Convert to list on demand for indexing. .. py:method:: next_node(forward: bool = True) -> int | None Advance/regress the pointer and return the next/previous node. :param forward: If True, move to next node; if False, move to previous. :returns: The node ID, or None if selection is empty. .. py:class:: TracksViewer(viewer: napari.Viewer) Purposes of the TracksViewer: - Emit signals that all widgets should use to update selection or update the currently displayed Tracks object - Storing the currently displayed tracks - Store shared rendering information like colormaps (or symbol maps) .. py:attribute:: tracks_updated .. py:attribute:: update_track_id .. py:attribute:: mode_updated .. py:attribute:: colormap_updated .. py:attribute:: center_node .. py:attribute:: node_selection_updated .. py:method:: get_instance(viewer=None) :classmethod: .. py:attribute:: viewer .. py:attribute:: menu_manager :value: None .. py:attribute:: tree_widget_present :value: False .. py:attribute:: table_widget_present :value: False .. py:attribute:: colormap .. py:attribute:: color_feature_key :type: str | None :value: None .. py:attribute:: symbolmap :type: dict[napari_track_edit.data_views.node_type.NodeType, str] .. py:attribute:: mode :value: 'all' .. py:attribute:: tracks :type: funtracks.data_model.Tracks | None :value: None .. py:attribute:: visible :type: list | str :value: [] .. py:attribute:: tracking_layers .. py:attribute:: selected_nodes .. py:attribute:: track_df .. py:attribute:: axis_order :type: list[int] :value: [] .. py:attribute:: tracks_list .. py:attribute:: selected_track :value: None .. py:attribute:: track_id_color :value: [0, 0, 0, 0] .. py:attribute:: force :value: False .. py:attribute:: interacting_with_canvas :value: False .. py:attribute:: collection_widget :value: None .. py:property:: tracks_dims :type: napari_track_edit.data_views.dims_utils.TracksDims How the tracks' axes sit compared to the viewer's world axes. :raises RuntimeError: If no tracks are loaded. .. py:method:: set_axis_labels() -> None Name the viewer's sliders after the axes the tracks use. .. py:method:: get_collection_widget() -> napari_track_edit.data_views.views_coordinator.groups.CollectionWidget Return a reference to the groups widget .. py:method:: _on_collection_widget_destroyed() .. py:method:: set_colormap_to_trackslist() Set the current colormap on the TracksList, so that it can be exported. .. py:method:: set_color_feature(feature_key: str | None, refresh: bool = True) -> None Color every view by the given node feature and trigger refresh so that the update is immediately visible. :param feature_key: A node feature key, or None for the default (the tracklet id - see `TrackColormap.feature_key`). :param refresh: Set False while tracks are being swapped in, where the caller rebuilds the views itself anyway. .. py:method:: _validate_color_feature() -> None Fall back to track ids if the feature being colored by is gone. .. py:method:: set_keybinds() .. py:method:: request_new_track(event=None) -> None Request a new track id (with new segmentation label if a seg layer is present) Takes an (unused) event argument so it can be bound as a napari keybinding. .. py:method:: set_new_track_id() -> None Set a new track id (if needed), update the color, and emit signal. Only updates the track id if the tracks.max_track_id value is used already. .. py:method:: set_track_id_color(track_id: int) -> None Update self.track_id_color with the rgba color of the given track_id. Shows the tracklet ID color when the colormap feature is set to tracklet ID, transparent otherwise to avoid confusion. .. py:method:: update_track_df(initialization: bool | None = False, refresh_view: bool | None = False) -> None Create or update the pandas dataframe used by the TreeWidget and TableWidget. The track_df should be updated when: - a tree or table widget is being initialized (initialization=True) and no tree or table widget exists yet - a normal update event happens (initialization = False) AND a tree widget and/or table widget exists on menu_manager :param initialization: whether or not this is called by a tree or table widget that is initializing. :type initialization: bool | None = False :param refresh_view: whether or not we should not pass on the previous axis_order. Should be False if we want to use the previous axis order (current tracks got updated). Should be True if we have a new tracks object and should therefore recompute the axis_order. :type refresh_view: bool | None = False .. py:method:: _refresh(node: str | None = None, refresh_view: bool = False) -> None Call refresh function on napari layers and the submit signal that tracks are updated. Restore the selected_nodes, if possible .. py:method:: _disconnect_tracks() -> None Stop listening to the currently displayed tracks object. The connections below live on the Tracks object, not on this TracksViewer, so they outlive both the viewer and the singleton reference unless they are explicitly removed. Because one Tracks object can be handed to more than one viewer over a session, leaving them in place means an edit notifies every TracksViewer that ever displayed those tracks. .. py:method:: update_tracks(tracks: funtracks.data_model.Tracks, name: str) -> None Stop viewing a previous set of tracks and replace it with a new one. Will create new segmentation and tracks layers and add them to the viewer. :param tracks: The tracks to visualize in napari. :type tracks: funtracks.data_model.Tracks :param name: The name of the tracks to display in the layer names :type name: str .. py:method:: clear_tracks() -> None Stop displaying any tracks at all: the mirror of update_tracks. Called when the last entry leaves the results list. Without it the napari layers, the tree plot and the table keep rendering a tracks object that the application no longer holds. Input layers that update_tracks hid stay hidden: the user may have hidden them themselves, and we do not record which ones were ours. .. py:method:: toggle_display_mode(event=None) -> None Toggle the display mode between available options. Skips 'group' mode when no groups exist, alternating only between 'all' and 'lineage' in that case. .. py:method:: set_display_mode(mode: str) -> None Update the display mode and call to update colormaps for points, labels, and tracks .. py:method:: filter_visible_nodes() -> list[int] | str Construct a list of node_ids that should be displayed according to the display mode: 'all', 'lineage', or 'group'). Note that whether a node is truly displayed also depends on whether it is in the current selection (not computed here). Additionally, if the mode is 'lineage' and the selection is cleared we keep the previous list of nodes visible to not have an entirely empty viewer. .. py:method:: viewer_interaction() Mark everything that happens inside this block as originating from the napari canvas, to suppress node centering when the seg or points layer is not in pan_zoom mode. .. py:method:: center_on_node(node: int) -> None Request all views to center on the given node. Emits the center_node signal which is listened to by the tracking layers and tree view to synchronize centering. :param node: The node ID to center on. .. py:method:: select_track_id_from_node(node: int) -> None Adopt the tracklet id of the given node as the current track id, without selecting or centering on that node. This is similar to the pipette behavior on the labels layer, but now available on all views, and not bound to the current time point. With a segmentation present, the pick goes through the labels layer's ``selected_label``, so ``_ensure_valid_label`` decides the label value that should be painted with that is consistent with the picked tracklet id. :param node: The node ID whose tracklet id should become the current one. .. py:method:: _on_action_applied(action: funtracks.actions.BasicAction) -> None Handle action_applied signal from tracks. Updates the deleted_items set to track which nodes have been deleted, and clears nodes that were added back (via undo or re-addition). :param action: The action that was applied (from funtracks) .. py:method:: update_selection(set_view: bool = True, update_counts: bool = False) -> None Sets the view and triggers visualization updates in other components .. py:method:: delete_node(event=None) Calls the UserAction to delete currently selected nodes .. py:method:: swap_nodes(event=None) Calls the UserAction to swap the predecessors of the two currently selected nodes .. py:method:: set_division(event=None) Calls the UserAction to make or break a division between the three currently selected nodes .. py:method:: merge_horizontally(event=None) Merge every set of selected nodes that shares a time point into one node. The user picks which of the tracklet ids in a set the merged node should keep. Sets that offer the same tracklet ids are asked about only once. Cancelling any of the dialogs cancels the whole merge. .. py:method:: _ask_merge_track_ids(options: dict[int, list[int]], colormap) -> dict[int, int] | None :staticmethod: Ask the user which tracklet id to keep in each set of nodes to merge. :param options: A mapping from time point to the tracklet ids to choose from. :param colormap: The colormap used to color the tracklet id options. :returns: A mapping from time point to the chosen tracklet id, or None if the user cancelled any of the dialogs. .. py:method:: connect_nodes(event=None, linear: bool | None = None) Connect the currently selected nodes into a single track. The nodes are sorted by time and connected pairwise, leaving the pairs that are connected already alone. See :meth:`disconnect_nodes` for the inverse. :param event: Unused, present so this can be used as a keybinding callback. :param linear: If True, existing outgoing edges of the selected nodes are broken so that the result is one linear track. If False, they are kept and divisions are created instead. If None (the default), the user is asked which of the two they want, but only when the choice makes a difference for this selection. .. py:method:: connect_nodes_with_divisions(event=None) Connect the selected nodes, keeping existing outgoing edges as divisions. .. py:method:: connect_nodes_linearly(event=None) Connect the selected nodes into one linear track, breaking the existing outgoing edges of the nodes that get a new child. .. py:method:: disconnect_nodes(event=None) Break every existing edge in the currently selected nodes, including skip edges. .. py:method:: undo(event=None) .. py:method:: redo(event=None) .. py:method:: hide_panels(event=None) Show/hide menu and tree view panels without destroying .. py:method:: deselect(event=None) .. py:method:: restore_selection(event=None) .. py:method:: select_node_set_from_history(previous: bool) Move forwards or backwards through selection history. .. py:method:: select_next(event=None) Select next node set from history .. py:method:: select_previous(event=None) Select previous node set from history