Human generated update: This is related to the API change in 9bfbf8f change single node|edge api - passing in the lists is now actually wrong and leads to this specific error. So, might just be that we need to update our callers, but I didn't want to go down the rabbit hole far enough to verify.
Warning: Claude generated summary, I was in the middle of something else and didn't have time to verify, but wanted to make the issue so I didn't forget. Hopefully this points us in the right direction.
Summary
After #287 (Fix graphview signals, 14b92ec), calling update_node_attrs on a GraphView that has a GraphArrayView connected crashes with polars.exceptions.InvalidOperationError: cannot cast Array to a different width when the updated attributes include a pl.Array-typed column (e.g. a position column of type Array(Float64, 2)).
This did not happen before #287 because update_node_attrs did not read back attribute values for the signal payload.
Root cause
GraphView.update_node_attrs (line 703–717 in _graph_view.py) builds signal_keys from DEFAULT_ATTR_KEYS plus attrs.keys(). It then calls node_attrs(attr_keys=signal_keys) to capture old/new values for the node_updated signal.
The problem is in _list_to_pl_series (_rustworkx_graph.py:71–82). When a pl.Array(Float64, 2) column stores a value like [50.0, 50.0], rustworkx returns it as [[50.0, 50.0]] for a single-node query. np.asarray([[50.0, 50.0]]) produces shape (1, 2), and pl.Series(values=..., dtype=Array(Float64, 2)) handles this correctly.
However, when the value was stored via __setitem__ as graph.nodes[node][attr] = [[50.0, 50.0]] (double-wrapped — the caller wraps the value in a list as the tracksdata API expects), rustworkx returns [[[50.0, 50.0]]] for a single node. np.asarray([[[50.0, 50.0]]]) produces shape (1, 1, 2), and polars infers this as Array(Float64, (1, 2)), then fails to cast it to the schema's Array(Float64, 2).
Reproduction
import numpy as np
import polars as pl
from tracksdata.graph import RustWorkXGraph
# Create a graph with an Array-typed position column
graph = RustWorkXGraph()
graph.add_node_attr_key("t", dtype=pl.Int64, default_value=0)
graph.add_node_attr_key("pos", dtype=pl.Array(pl.Float64, 2), default_value=[0.0, 0.0])
graph.add_node_attr_key("mask", dtype=pl.Object)
graph.add_node_attr_key("bbox", dtype=pl.Array(pl.Int64, 4), default_value=[0, 0, 0, 0])
# Add a node
from tracksdata.nodes import Mask
mask = Mask(np.ones((3, 3), dtype=bool), bbox=np.array([0, 0, 3, 3]))
graph.add_node(attrs={"t": 0, "pos": [50.0, 50.0], "mask": mask, "bbox": [0, 0, 3, 3]})
# Create a GraphView + attach a GraphArrayView (this connects node_updated signal)
from tracksdata.graph import GraphView
from tracksdata.array import GraphArrayView
view = GraphView(graph)
gav = GraphArrayView(graph=view, shape=(1, 100, 100), attr_key="node_id", offset=0)
# This crashes: update an attribute on the node
# The signal emission path tries to read back "pos" and hits the polars Array width error
node_id = list(view.node_ids())[0]
view.update_node_attrs(attrs={"pos": [[60.0, 60.0]]}, node_ids=[node_id])
Error:
polars.exceptions.InvalidOperationError: cannot cast Array to a different width
Where it matters
This breaks any funtracks workflow where RegionpropsAnnotator updates node attributes (pos, area, etc.) after a GraphArrayView is created — for example, AddNode followed by annotator recomputation, or enable_features(["area"]).
Environment
- tracksdata:
main at or after 14b92ec
- polars: 1.40.1
- Python: 3.12
Possible fixes
-
In _list_to_pl_series: Handle the case where np.asarray(values) produces more dimensions than expected for the schema's pl.Array type. For example, squeeze or reshape to (n_rows, width).
-
In GraphView.update_node_attrs: Only include attrs.keys() in signal_keys — don't also include the default spatial keys. The signal receivers can fetch any additional attrs they need from the graph directly. (This would also avoid the separate issue where custom bbox_attr_key like nuc_bbox is missing from the signal payload.)
Human generated update: This is related to the API change in 9bfbf8f
change single node|edge api- passing in the lists is now actually wrong and leads to this specific error. So, might just be that we need to update our callers, but I didn't want to go down the rabbit hole far enough to verify.Warning: Claude generated summary, I was in the middle of something else and didn't have time to verify, but wanted to make the issue so I didn't forget. Hopefully this points us in the right direction.
Summary
After #287 (
Fix graphview signals,14b92ec), callingupdate_node_attrson aGraphViewthat has aGraphArrayViewconnected crashes withpolars.exceptions.InvalidOperationError: cannot cast Array to a different widthwhen the updated attributes include apl.Array-typed column (e.g. a position column of typeArray(Float64, 2)).This did not happen before #287 because
update_node_attrsdid not read back attribute values for the signal payload.Root cause
GraphView.update_node_attrs(line 703–717 in_graph_view.py) buildssignal_keysfromDEFAULT_ATTR_KEYSplusattrs.keys(). It then callsnode_attrs(attr_keys=signal_keys)to capture old/new values for thenode_updatedsignal.The problem is in
_list_to_pl_series(_rustworkx_graph.py:71–82). When apl.Array(Float64, 2)column stores a value like[50.0, 50.0], rustworkx returns it as[[50.0, 50.0]]for a single-node query.np.asarray([[50.0, 50.0]])produces shape(1, 2), andpl.Series(values=..., dtype=Array(Float64, 2))handles this correctly.However, when the value was stored via
__setitem__asgraph.nodes[node][attr] = [[50.0, 50.0]](double-wrapped — the caller wraps the value in a list as the tracksdata API expects), rustworkx returns[[[50.0, 50.0]]]for a single node.np.asarray([[[50.0, 50.0]]])produces shape(1, 1, 2), and polars infers this asArray(Float64, (1, 2)), then fails to cast it to the schema'sArray(Float64, 2).Reproduction
Error:
Where it matters
This breaks any funtracks workflow where
RegionpropsAnnotatorupdates node attributes (pos, area, etc.) after aGraphArrayViewis created — for example,AddNodefollowed by annotator recomputation, orenable_features(["area"]).Environment
mainat or after14b92ecPossible fixes
In
_list_to_pl_series: Handle the case wherenp.asarray(values)produces more dimensions than expected for the schema'spl.Arraytype. For example, squeeze or reshape to(n_rows, width).In
GraphView.update_node_attrs: Only includeattrs.keys()insignal_keys— don't also include the default spatial keys. The signal receivers can fetch any additional attrs they need from the graph directly. (This would also avoid the separate issue where custombbox_attr_keylikenuc_bboxis missing from the signal payload.)