Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
437d08c
replaced nx in conftest and worked on test regarding adding/deleting …
TeunHuijben Dec 5, 2025
5b1d0a4
merge with main
TeunHuijben Dec 5, 2025
b4ce277
fixed all tests in Actions
TeunHuijben Dec 11, 2025
49ebc8b
Merge branch 'main' into tracksdata
TeunHuijben Dec 11, 2025
567cefb
make compatible with latest tracksdata api
TeunHuijben Dec 11, 2025
e99768f
more tests passing
TeunHuijben Dec 12, 2025
05fdf43
:fire: Remove tracks controller!
cmalinmayor Dec 12, 2025
9cbf7f7
Remove NodeAttrs, EdgeAttrs enums
cmalinmayor Dec 12, 2025
ffba9b4
Don't allow setting time on nodes
cmalinmayor Dec 12, 2025
6aca2f6
Remove/rename functions for saving and loading internal format
cmalinmayor Dec 12, 2025
d6f5904
Remove _compute_node_attrs
cmalinmayor Dec 12, 2025
078ee03
Remove getters and setters for non-standard features and mutliple attrs
cmalinmayor Dec 12, 2025
6c93c71
Remove all other deprecated tracks and solution tracks functions
cmalinmayor Dec 12, 2025
27f5400
Merge branch 'v2-dev' into tracksdata
cmalinmayor Dec 12, 2025
34bf99b
fixed minor bugs resulting from merge with v2-branch
TeunHuijben Dec 12, 2025
6410e87
fixed due to merge with v2
TeunHuijben Dec 12, 2025
65791b0
user_actions tests passing
TeunHuijben Dec 12, 2025
4958ae7
Merge branch 'main' into v2-dev
cmalinmayor Dec 15, 2025
bd45a60
update tracks.add_feature to work on any feature (nodes/edges)
TeunHuijben Dec 15, 2025
4c466dc
Merge branch 'v2-dev' into tracksdata
TeunHuijben Dec 15, 2025
fa4965b
check if graph.column exists when adding feature
TeunHuijben Dec 16, 2025
1c126ff
working towards fixing from_tracks_df
TeunHuijben Dec 16, 2025
d4d17cc
working on geff tests etc.
TeunHuijben Dec 17, 2025
406bddf
fix annotator tests and working on input/output
TeunHuijben Dec 18, 2025
6d05787
remove all graph.nodes and replace td_graph_edge_list with graph.edge…
TeunHuijben Dec 18, 2025
a56a468
replace ancestors by rx.ancestors
TeunHuijben Dec 18, 2025
90088b4
all tests passing! (before adding segmentation to graph)
TeunHuijben Dec 18, 2025
17aa6a7
clean up: del deepcopy(graph), del NodeAttr/EdgeAttr, tracks.delete_f…
TeunHuijben Dec 18, 2025
14b246d
fix iou type
TeunHuijben Dec 18, 2025
2eea4d3
wip tests passing with segmetation on graph, todo is import/export
TeunHuijben Jan 8, 2026
fe767ae
updated tracksdata version
TeunHuijben Jan 8, 2026
736daeb
use graph.has_node, instead of: node in graph.node_ids()
TeunHuijben Jan 8, 2026
ec952bc
all tests passing with td backend and segmentation on graph!
TeunHuijben Jan 8, 2026
3c4c535
function name change
TeunHuijben Jan 8, 2026
c8d819f
fix github testing ci
TeunHuijben Jan 8, 2026
f55268c
minor textual changes
TeunHuijben Jan 8, 2026
e020d74
latest tracksdata
TeunHuijben Jan 16, 2026
fb84122
answered Carolines comments, use Mask methods, iou from masks
TeunHuijben Jan 20, 2026
ba6bc10
UserUpdateSegmentation uses masks, not segmentation
TeunHuijben Jan 20, 2026
17088b6
regionprops computed from masks
TeunHuijben Jan 21, 2026
33635ec
use regionprops on mask from td
TeunHuijben Jan 21, 2026
518df4e
add segmentation_shape to graph.metadata, and remove all its instances
TeunHuijben Jan 21, 2026
ed27f84
remove computations from AddNode._apply and rely on annotators
TeunHuijben Jan 22, 2026
13a1bf4
use features.default in AddEdge
TeunHuijben Jan 22, 2026
e28d99b
updated api in docs/features.md
TeunHuijben Jan 22, 2026
176cd77
tracksdata now fully handles default attribute values!
TeunHuijben Jan 27, 2026
1f63bc5
removed set_pixels from add_node
TeunHuijben Jan 27, 2026
30dfaa9
remove set_pixels
TeunHuijben Jan 30, 2026
d04e122
update tracksdata to latest versions
TeunHuijben Jan 30, 2026
9552a65
UserActions use pixels, but all underlying actions use masks. Trying …
TeunHuijben Jan 30, 2026
3d68dfe
removed todo
TeunHuijben Jan 31, 2026
eb72ccb
remove default stuff from AddNode
TeunHuijben Jan 31, 2026
6d48a8c
make update_segmentation_cache take a mask, not pixels
TeunHuijben Jan 31, 2026
3d7bca9
remove unnecessary utils functions regarding node attributes from pix…
TeunHuijben Jan 31, 2026
0b2b859
improve coverage of tracks.py
TeunHuijben Jan 31, 2026
2d32f30
update Teun Todos
TeunHuijben Feb 2, 2026
1a53c4b
single node/edge api from tracksdata
TeunHuijben Feb 11, 2026
1159d17
latest tracksdata version + resolved last todo
TeunHuijben Feb 12, 2026
d761f9b
handled last comment
TeunHuijben Feb 12, 2026
d98012c
Merge pull request #126 from funkelab/tracksdata
TeunHuijben Feb 25, 2026
fcbccd6
fix divide by zero in regionprops and edgeannotator double work in up…
TeunHuijben Feb 26, 2026
a10af7b
Merge branch 'main' into v2-dev
TeunHuijben Feb 26, 2026
e8672f0
add test for get_new_node_ids and removed unneccesary td_utils
TeunHuijben Feb 26, 2026
bdd4e9f
Merge branch 'main' into v2-dev
TeunHuijben Mar 6, 2026
4d56a5b
graph.nodes has to be graph.node_ids()
TeunHuijben Mar 6, 2026
16b096e
update_segmentation_cache of tracks.segmentation upon adding a node
TeunHuijben Mar 9, 2026
7ef082c
also invalidate cache for deleting node
TeunHuijben Mar 9, 2026
6b4b80d
fix bug in create_empty_graphview_graph that adds the solution attrib…
TeunHuijben Mar 10, 2026
27b4420
validate_graph_seg_match handles case where seg_id does not exist + a…
TeunHuijben Mar 10, 2026
2fbdcfc
fix bug where gefftracksbuilder.read_header ignored axes metadata and…
TeunHuijben Mar 10, 2026
74320f7
fix geff round-trip: use axes metadata for import mapping and handle …
TeunHuijben Mar 10, 2026
95d39c6
fix geff round-trip: infer axes from metadata, handle missing segment…
TeunHuijben Mar 10, 2026
dc3a708
create_empty_graph uses sqlgraph on disc (temp_file), and not memory,…
TeunHuijben Mar 12, 2026
ee23ac4
Merge branch 'main' into v2-dev
TeunHuijben Mar 12, 2026
9b6d0b2
add mask/bbox/metadata to graph when it has segmentations
TeunHuijben Mar 13, 2026
795a6fc
Fix attribute mutation bug in AddEdge and AddNode by copying self.att…
TeunHuijben Mar 16, 2026
7f4c61e
use mask utils in Tracks.get_pixels
TeunHuijben Mar 17, 2026
7d50a23
remove required from features
TeunHuijben Mar 17, 2026
96031fb
Merge pull request #173 from funkelab/remove-required
TeunHuijben Mar 17, 2026
35c6404
remove lingering "TODO Teun"
TeunHuijben Mar 17, 2026
6961b17
Move graph schema registration from GraphAnnotator.activate_features …
TeunHuijben Mar 17, 2026
9a36c3d
Fix AddEdge crash when graph has edge attrs not in tracks.features
TeunHuijben Mar 17, 2026
f2dd180
add flag to not save separate segmentation zarr in export_to_geff
TeunHuijben Mar 17, 2026
64c75e3
export segmentation helper function + tiff and zarr options + relabel…
TeunHuijben Mar 18, 2026
13c355b
allow geff exporter to save seg as tiff
TeunHuijben Mar 19, 2026
e7be079
tiff dtype
TeunHuijben Mar 19, 2026
9bac46b
save tiff next to geff, instead of within
TeunHuijben Mar 19, 2026
a6b10f4
Merge pull request #176 from funkelab/export-reformat
TeunHuijben Mar 19, 2026
a356f09
Merge branch 'main' into v2-dev
TeunHuijben Mar 19, 2026
56d6ba5
TracksBuilder should default to time_attr="t"
TeunHuijben Mar 19, 2026
8f96faf
export to geff includes segmentation_shape in zarr metadata
TeunHuijben Mar 20, 2026
483faa6
save geff as tracks.geff
TeunHuijben Mar 20, 2026
adcdf28
normalize feature key case in tracks_from_df to prevent duplicate col…
TeunHuijben Mar 20, 2026
0642703
activate regionpropsannotator when building tracks from geff after se…
TeunHuijben Mar 20, 2026
98a8aa6
update load_v1_format to handle both "time" and "t"
TeunHuijben Mar 23, 2026
43bfc9f
add missing edge/node attributes to load_v1_tracks
TeunHuijben Mar 23, 2026
0657026
load_v1_tracks again
TeunHuijben Mar 23, 2026
cf44730
preserve int types in convert_graph_nx_to_td
TeunHuijben Mar 23, 2026
14d0f16
fix lineage_id
TeunHuijben Mar 23, 2026
961be56
update to latest tracksdata api
TeunHuijben Mar 25, 2026
6b6fb4b
Merge pull request #179 from funkelab/update-tracksdata
TeunHuijben Mar 25, 2026
605a974
latest release of tracksdata
TeunHuijben Mar 25, 2026
16e40a6
new tracksdata, resolving unpickling bug
TeunHuijben Mar 26, 2026
84a7f4c
wrong tracksdata branch
TeunHuijben Mar 26, 2026
1dda3b7
2 optimizations in tracksdata (mask compression, update_node_attrs)
TeunHuijben Mar 26, 2026
1ad6974
speed up of get_max_id_and_map
TeunHuijben Mar 26, 2026
3028106
speedup of GeffTracksBuilder
TeunHuijben Mar 26, 2026
9be0cbf
speed up get_positions(), get_times(), get_track_ids()
TeunHuijben Mar 27, 2026
f059a54
Merge branch 'main' into v2-dev
cmalinmayor Mar 27, 2026
97df69a
latest trackdata with schema caching and update_node_attrs bulk read
TeunHuijben Mar 27, 2026
73df729
Optim compute graph from seg (#181)
TeunHuijben Mar 27, 2026
21f65c8
trying rxgraph instead of sql
TeunHuijben Mar 30, 2026
5012270
give import_from_geff the option to specify where to save the sql db …
TeunHuijben Mar 30, 2026
902bf51
revert create_empty_graph back to sql backend
TeunHuijben Mar 30, 2026
3b8e332
speedup RegionpropsAnnotator by bulk updating the graph
TeunHuijben Mar 30, 2026
4db0fa2
speedup TrackAnnotator by bulk updating the graph
TeunHuijben Mar 30, 2026
93802e9
Merge branch 'main' into v2-dev
TeunHuijben Mar 31, 2026
a984aa9
update tracksdata: cache schema upon attr fetch + handle bboxes that …
TeunHuijben Mar 31, 2026
99d97f4
use td.graph.IndexedRXGraph as default
TeunHuijben Mar 31, 2026
3b79c86
fix bug in tracksdata
TeunHuijben Mar 31, 2026
2b277e3
now the correct tracksdata...
TeunHuijben Mar 31, 2026
fb7de05
tracksbuilder missed ndim in construct graph, which was a problem whe…
TeunHuijben Mar 31, 2026
0c60887
fix regionprops centroid when scale is not unit
TeunHuijben Apr 2, 2026
055f446
precommit fixes
TeunHuijben Apr 2, 2026
ceb6cd7
add UserActions for bulk UserUpdateNodesAttrs
TeunHuijben Apr 3, 2026
9a7c47a
Merge branch 'user-update-nodes-attrs-bulk' into v2-dev
TeunHuijben Apr 3, 2026
60db1af
adjust UserUpdateNodesAttr for td
TeunHuijben Apr 3, 2026
1d5ee61
allow option of different dict per node
TeunHuijben Apr 3, 2026
e0ca39e
Merge branch 'user-update-nodes-attrs-bulk' into v2-dev
TeunHuijben Apr 3, 2026
ca30622
update tests for attrs dict per node
TeunHuijben Apr 3, 2026
ef2d9df
fix: respect multi-axis pos_attr when segmentation is present (#188)
TeunHuijben Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,4 @@ uv.lock

# Claude
CLAUDE.md
.claude/
34 changes: 20 additions & 14 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ classDiagram
+value_type: Literal
+num_values: int
+display_name: str|Sequence
+required: bool
+default_value: Any
}

Expand Down Expand Up @@ -150,7 +149,7 @@ classDiagram
}

class Tracks {
+graph: nx.DiGraph
+graph: td.graph.GraphView
+segmentation: ndarray|None
+features: FeatureDict
+annotators: AnnotatorRegistry
Expand Down Expand Up @@ -203,32 +202,40 @@ These features are **automatically checked** during initialization:

**Scenario 1: Loading tracks from CSV with pre-computed features**
```python
# CSV has columns: id, time, y, x, area, track_id
graph = load_graph_from_csv(df) # Nodes already have area, track_id
tracks = SolutionTracks(graph, segmentation=seg)
from funtracks.import_export import tracks_from_df

# CSV/DataFrame has columns: id, time, y, x, area, track_id, parent_id
tracks = tracks_from_df(df, segmentation=seg)
# Auto-detection: pos, area, track_id exist → activate without recomputing
```

**Scenario 2: Creating tracks from raw segmentation**
```python
# Graph has no features yet
graph = nx.DiGraph()
graph.add_node(1, time=0)
from funtracks.utils import create_empty_graphview_graph
from funtracks.data_model import Tracks

# Create empty graph and add nodes
graph = create_empty_graphview_graph()
graph.add_node(index=1, attrs={"t": 0})
tracks = Tracks(graph, segmentation=seg)
# Auto-detection: pos, area don't exist → compute them
# Auto-detection: pos, area don't exist → compute them from segmentation
```

**Scenario 3: Explicit feature control with FeatureDict**
```python
from funtracks.features import FeatureDict, Time, Position, Area
from funtracks.data_model import Tracks

# Bypass auto-detection entirely
feature_dict = FeatureDict({"t": Time(), "pos": Position(), "area": Area()})
tracks = Tracks(graph, segmentation=seg, features=feature_dict)
# All features in feature_dict are activated, none are computed
```

**Scenario 4: Enable a new feature**

```python
from funtracks.data_model import Tracks

tracks = Tracks(graph, segmentation)
# Initially has: time, pos, area (auto-detected or computed)

Expand All @@ -240,8 +247,7 @@ print(tracks.features.keys()) # All features in FeatureDict (including static)
print(tracks.annotators.features.keys()) # Only active computed features
```

**Scenario 4: Disable a feature**

**Scenario 5: Disable a feature**
```python
tracks.disable_features(["area"])
# Removes from FeatureDict, deactivates in annotators
Expand Down Expand Up @@ -272,9 +278,9 @@ tracks.disable_features(["area"])
def compute(self, feature_keys=None):
# Compute feature values in bulk
if "custom" in self.features:
for node in self.tracks.graph.nodes():
for node in self.tracks.graph.node_ids():
value = self._compute_custom(node)
self.tracks.graph.nodes[node]["custom"] = value
self.tracks[node]["custom"] = value

def update(self, action):
# Incremental update when graph changes
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ dependencies =[
"pandas>=2.3.3",
"zarr>=2.18,<4",
"numcodecs>=0.13,<0.16",
#TODO Teun: replace after release
"tracksdata[spatial] @ git+https://github.com/royerlab/tracksdata@3efb92a66b39e597c498e1f88077911c206427f2",
"tqdm>=4.66.1",
]

Expand Down
23 changes: 21 additions & 2 deletions src/funtracks/actions/add_delete_edge.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from ._base import BasicAction

Expand All @@ -10,6 +10,8 @@
from funtracks.data_model import Tracks
from funtracks.data_model.tracks import Edge

import tracksdata as td


class AddEdge(BasicAction):
"""Action for adding a new edge. Endpoints must exist already."""
Expand Down Expand Up @@ -52,7 +54,24 @@ def _apply(self) -> None:
f"Cannot add edge {self.edge}: endpoint {node} not in graph yet"
)

self.tracks.graph.add_edge(self.edge[0], self.edge[1], **self.attributes)
if self.tracks.graph.has_edge(*self.edge):
raise ValueError(f"Edge {self.edge} already exists in the graph")

# Add required solution attribute
attrs = dict(self.attributes)
attrs[td.DEFAULT_ATTR_KEYS.SOLUTION] = 1

schemas = self.tracks.graph._edge_attr_schemas()
for attr in self.tracks.graph.edge_attr_keys():
if attr not in attrs:
attrs[attr] = schemas[attr].default_value

# Create edge attributes for this specific edge
self.tracks.graph.add_edge(
source_id=self.edge[0],
target_id=self.edge[1],
attrs=attrs,
)

# Notify annotators to recompute features (will overwrite computed ones)
self.tracks.notify_annotators(self)
Expand Down
74 changes: 48 additions & 26 deletions src/funtracks/actions/add_delete_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
if TYPE_CHECKING:
from typing import Any

from funtracks.data_model import SolutionTracks
from funtracks.data_model.tracks import Node, SegMask
from funtracks.data_model.solution_tracks import SolutionTracks
from funtracks.data_model.tracks import Node

import tracksdata as td
from tracksdata.nodes._mask import Mask


class AddNode(BasicAction):
"""Action for adding new nodes. If a segmentation should also be added, the
pixels for each node should be provided. The label to set the pixels will
mask for the node should be provided. The label to set the mask will
be taken from the node id. The existing pixel values are assumed to be
zero - you must explicitly update any other segmentations that were overwritten
using an UpdateNodes action if you want to be able to undo the action.
Expand All @@ -24,20 +27,20 @@ def __init__(
tracks: SolutionTracks,
node: Node,
attributes: dict[str, Any],
pixels: SegMask | None = None,
mask: Mask | None = None,
):
"""Create an action to add a new node, with optional segmentation

Args:
tracks (Tracks): The Tracks to add the node to
node (Node): A node id
attributes (Attrs): Includes times, track_ids, and optionally positions
pixels (SegMask | None, optional): The segmentation associated with
mask (Mask | None, optional): The segmentation mask associated with
the node. Defaults to None.
Raises:
ValueError: If time attribute is not in attributes.
ValueError: If track_id is not in attributes.
ValueError: If pixels is None and position is not in attributes.
ValueError: If mask is None and position is not in attributes.
"""
super().__init__(tracks)
self.tracks: SolutionTracks # Narrow type from base class
Expand All @@ -55,7 +58,7 @@ def __init__(
raise ValueError(f"Must provide a {track_id_key} attribute for node {node}")

# Check for position - handle both single key and list of keys
if pixels is None:
if mask is None:
if isinstance(pos_key, list):
# Multi-axis position keys
if not all(key in attributes for key in pos_key):
Expand All @@ -68,7 +71,7 @@ def __init__(
raise ValueError(
f"Must provide position or segmentation for node {node}"
)
self.pixels = pixels
self.mask = mask
self.attributes = attributes
self._apply()

Expand All @@ -77,31 +80,35 @@ def inverse(self) -> BasicAction:
return DeleteNode(self.tracks, self.node)

def _apply(self) -> None:
"""Apply the action, and set segmentation if provided in self.pixels"""
if self.pixels is not None:
self.tracks.set_pixels(self.pixels, self.node)
attrs = self.attributes
self.tracks.graph.add_node(self.node)
"""Apply the action, and set segmentation if provided in self.mask"""
attrs = dict(self.attributes)

if self.tracks.segmentation is not None and self.mask is not None:
attrs[td.DEFAULT_ATTR_KEYS.MASK] = self.mask
attrs[td.DEFAULT_ATTR_KEYS.BBOX] = self.mask.bbox

# set all user provided attributes including time and position
for attr, value in attrs.items():
self.tracks._set_node_attr(self.node, attr, value)
self.tracks.graph.add_node(attrs=attrs, index=self.node, validate_keys=False)

# Invalidate cache for the new node's region so the GraphArrayView reflects it
if self.tracks.segmentation is not None and self.mask is not None:
time = self.tracks.get_time(self.node)
self.tracks._update_segmentation_cache(mask=self.mask, time=time)

# Always notify annotators - they will check their own preconditions
self.tracks.notify_annotators(self)


class DeleteNode(BasicAction):
"""Action of deleting existing nodes
"""Action of deleting existing node
If the tracks contain a segmentation, this action also constructs a reversible
operation for setting involved pixels to zero
operation for setting involved masks to zero
"""

def __init__(
self,
tracks: SolutionTracks,
node: Node,
pixels: SegMask | None = None,
mask: Mask | None = None,
):
super().__init__(tracks)
self.tracks: SolutionTracks # Narrow type from base class
Expand All @@ -114,24 +121,39 @@ def __init__(
if val is not None:
self.attributes[key] = val

self.pixels = self.tracks.get_pixels(node) if pixels is None else pixels
if td.DEFAULT_ATTR_KEYS.MASK in self.tracks.graph.node_attr_keys():
self.attributes[td.DEFAULT_ATTR_KEYS.MASK] = self.tracks.get_nodes_attr(
[self.node], td.DEFAULT_ATTR_KEYS.MASK
)[0]
self.attributes[td.DEFAULT_ATTR_KEYS.BBOX] = self.tracks.get_nodes_attr(
[self.node], td.DEFAULT_ATTR_KEYS.BBOX
)[0]
self.attributes[td.DEFAULT_ATTR_KEYS.SOLUTION] = self.tracks.get_nodes_attr(
[self.node], td.DEFAULT_ATTR_KEYS.SOLUTION
)[0]

mask = self.tracks.get_mask(node) if mask is None else mask

self.mask = mask
self._apply()

def inverse(self) -> BasicAction:
"""Invert this action, and provide inverse segmentation operation if given"""

return AddNode(self.tracks, self.node, self.attributes, pixels=self.pixels)
return AddNode(self.tracks, self.node, self.attributes, mask=self.mask)

def _apply(self) -> None:
"""ASSUMES THERE ARE NO INCIDENT EDGES - raises valueerror if an edge will be
removed by this operation
Steps:
- For each node
set pixels to 0 if self.pixels is provided
- Remove nodes from graph
- Update annotators
"""
if self.pixels is not None:
self.tracks.set_pixels(self.pixels, 0)

self.tracks.graph.remove_node(self.node)

# Invalidate cache for the deleted node's region so the GraphArrayView reflects it
if self.tracks.segmentation is not None and self.mask is not None:
time = self.attributes[self.tracks.features.time_key]
self.tracks._update_segmentation_cache(mask=self.mask, time=time)

self.tracks.notify_annotators(self)
53 changes: 43 additions & 10 deletions src/funtracks/actions/update_segmentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,40 @@

from typing import TYPE_CHECKING

import tracksdata as td
from tracksdata.nodes._mask import Mask

from ._base import BasicAction

if TYPE_CHECKING:
from funtracks.data_model import Tracks
from funtracks.data_model.tracks import Node, SegMask
from funtracks.data_model.tracks import Node


class UpdateNodeSeg(BasicAction):
"""Action for updating the segmentation associated with a node.

New nodes call AddNode with pixels instead of this action.
New nodes call AddNode with mask instead of this action.
"""

def __init__(
self,
tracks: Tracks,
node: Node,
pixels: SegMask,
mask: Mask,
added: bool = True,
):
"""
Args:
tracks (Tracks): The tracks to update the segmenatations for
node (Node): The node with updated segmenatation
pixels (SegMask): The pixels that were updated for the node
added (bool, optional): If the provided pixels were added (True) or deleted
tracks (Tracks): The tracks to update the segmentations for
node (Node): The node with updated segmentation
mask (Mask): The mask that was updated for the node
added (bool, optional): If the provided mask were added (True) or deleted
(False) from this node. Defaults to True
"""
super().__init__(tracks)
self.node = node
self.pixels = pixels
self.mask = mask
self.added = added
self._apply()

Expand All @@ -41,12 +44,42 @@ def inverse(self) -> BasicAction:
return UpdateNodeSeg(
self.tracks,
self.node,
pixels=self.pixels,
mask=self.mask,
added=not self.added,
)

def _apply(self) -> None:
"""Set new attributes"""
value = self.node if self.added else 0
self.tracks.set_pixels(self.pixels, value)

mask_new = self.mask

if value == 0:
# val=0 means deleting (part of) the mask
mask_old = self.tracks.graph.nodes[self.node][td.DEFAULT_ATTR_KEYS.MASK]
mask_subtracted = mask_old.__isub__(mask_new)
self.tracks.graph.update_node_attrs(
attrs={
td.DEFAULT_ATTR_KEYS.MASK: [mask_subtracted],
td.DEFAULT_ATTR_KEYS.BBOX: [mask_subtracted.bbox],
},
node_ids=[self.node],
)

elif self.tracks.graph.has_node(value):
# if node already exists:
mask_old = self.tracks.graph.nodes[value][td.DEFAULT_ATTR_KEYS.MASK]
mask_combined = mask_old.__or__(mask_new)
self.tracks.graph.update_node_attrs(
attrs={
td.DEFAULT_ATTR_KEYS.MASK: [mask_combined],
td.DEFAULT_ATTR_KEYS.BBOX: [mask_combined.bbox],
},
node_ids=[value],
)

# Invalidate cache for affected chunks
time = self.tracks.get_time(self.node)
self.tracks._update_segmentation_cache(mask=mask_new, time=time)

self.tracks.notify_annotators(self)
Loading
Loading