API Reference
A note on Node and Edge types
The following types are used throughout the docs
Nodes are integers
Node: TypeAlias = int
Edges are 2-tuples of
Node.
Edge: TypeAlias = tuple[Node, Node]Example:
(0, 1)is an edge from node 0 to node 1.
All attributes in a graph (for both
Nodes andEdges) are dictionaries mapping string attribute names to values. For example, a node’s attributes might be{ "x": 0.5, "y": 0.5, "t": 0 }
Attributes: TypeAlias = Mapping[str, Any]
Track Graph
- class motile.TrackGraph(nx_graph=None, frame_attribute='t')
A graph of nodes in time & space, with edges connecting them across time.
This wraps a
networkx.DiGraphobject.Both
nodesandedgesare represented by a dictionary of properties.Provides a few convenience methods for time series graphs in addition to all the methods inherited from
networkx.DiGraph.- Parameters:
- add_edge(edge_id, data)
Adds an edge to this TrackGraph.
- add_from_nx_graph(nx_graph)
Add nodes/edges from
nx_graphto this TrackGraph.- Parameters:
nx_graph (networkx.DiGraph) –
A directed networkx graph representing a TrackGraph to be added.
Duplicate nodes and edges will not be added again but new attributes associated to nodes and edges added. If attributes of existing nodes or edges do already exist, the values set in the given
nx_graphwill be updating the previously set values.- Return type:
- add_node(node_id, data)
Adds a new node to this TrackGraph.
- get_frames()
Return tuple with first and last (exclusive) frame this graph has nodes for.
Returns
(t_begin, t_end)wheret_endis exclusive. Returns(0, 0)for empty graph.
- to_nx_graph()
Convert this TrackGraph into a networkx DiGraph.
- Returns:
- Directed networkx graph with same nodes, edges, and
attributes.
- Return type:
Solver
- class motile.Solver(track_graph)
Create a solver for a given track graph.
- Parameters:
track_graph (
TrackGraph) – TheTrackGraphof objects to track over time.
- add_constraint(constraint)
Add linear constraints to the solver.
- Parameters:
constraint (
Constraint) – TheConstraintto add.- Return type:
- add_cost(cost, name=None)
Add linear cost to the value of variables in this solver.
- add_variable_cost(index, value, weight)
Add cost for an individual variable.
To be used within implementations of
motile.costs.Cost.- Return type:
- property costs: ndarray
Returns the costs as a
numpy.ndarray.
- fit_weights(gt_attribute, regularizer_weight=0.1, max_iterations=1000, eps=1e-06)
Fit weights of ILP costs to ground truth with structured SVM.
Updates the weights in the solver object to the found solution.
See https://github.com/funkelab/structsvm for details.
- Parameters:
gt_attribute (
str) –Node/edge attribute that marks the ground truth for fitting. gt_attribute is expected to be set to:
1for objects labaled as ground truth.0for objects explicitly labeled as not part of the ground truth.Noneor not set for unlabeled objects.
regularizer_weight (
float) – The weight of the quadratic regularizer.max_iterations (
int) – Maximum number of gradient steps in the structured SVM.eps (
float) – Convergence threshold.
- Return type:
- get_selected_subgraph(solution=None)
Return TrackGraph with only the selected nodes/edges from the solution.
- Parameters:
solution (
Solution|None) – The solution to use. If not provided, the last solution is used.- Return type:
- Returns:
A new TrackGraph with only the selected nodes and edges.
- Raises:
RuntimeError – If no solution is provided and the solver has not been solved
yet. –
- get_variables(cls)
Get variables by their class name.
If the solver does not yet contain those variables, they will be created.
- Parameters:
cls (type of
motile.variables.Variable) – A subclass ofmotile.variables.Variable.- Return type:
- Returns:
A singleton instance of
Variable(of whatever type was passed in ascls), mimicking a dictionary that can be used to look up variable indices by their keys. SeeVariablefor details.
- solve(timeout=0.0, num_threads=1, verbose=False, backend=Preference.Any, on_event=None)
Solve the global optimization problem.
- Parameters:
timeout (
float) – The timeout for the ILP solver, in seconds. Default (0.0) is no timeout. If the solver times out, the best solution encountered so far is returned (if any has been found at all).num_threads (
int) – The number of threads the ILP solver uses.verbose (
bool) – If true, print more information from ILP solver. Defaults to False.backend (
Preference) – The ILP solver backend to use. Defaults to Any.on_event (
Callable[[Mapping],None] |None) – A callback function that will be called when the solver emits an event. Should accept an event data dict. (see ilpy.EventData for typing info which may be imported inside of a TYPE_CHECKING block.) Defaults to None.
- Return type:
Solution- Returns:
ilpy.Solution, a vector of variable values. Useget_variables()to find the indices of variables in this vector.
Variables
Solver variables are introduced by inheriting from the following abstract base class:
- class motile.variables.Variable(solver, index_map)
Base class for solver variables.
New variables can be introduced by inheriting from this base class and overwriting
instantiate()and optionallyinstantiate_constraints().Variable classes should not be instantiated by a user. Instead, the
Solverprovides access to concrete variables through the class name. The following example shows how to obtain the variable values after optimization:solver = Solver(graph) # add costs, constraints... solution = solver.solve() # here `node_selected` is an instance of a Variable subclass # specifically, it will be an instance of NodeSelected, which # maps node Ids to variables in the solver. node_selected = solver.get_variables(NodeSelected) for node in graph.nodes: if solution[node_selected[node]] > 0.5: print(f"Node {node} was selected")
This allows variables to be added lazily to the solver.
Constraintsandmotile.costs.Costcan ask for variables.- abstractmethod static instantiate(solver)
Create and return keys for the variables.
For example, to create a variable for each node, this function would return a list of all nodes:
@staticmethod def instantiate(solver): return solver.graph.nodes
The solver will create one variable for each key. The index of that variable can be accessed through a dictionary returned by
motile.Solver.get_variables():solver = Solver(graph) node_selected = solver.get_variables(NodeSelected) for node in graph.nodes: index = node_selected[node] print(f"Selection indicator of node {node} has index {index}")
- Parameters:
solver (
Solver) – TheSolverinstance to create variables for.- Return type:
Collection[TypeVar(_KT, bound=Hashable)]- Returns:
A collection of keys (anything that is hashable, e.g., nodes of a graph), one for each variable to create.
- static instantiate_constraints(solver)
Add constraints for this variable to the solver.
This ensures that these variables are coupled to other variables of the solver.
- Parameters:
solver (
Solver) – TheSolverinstance to create variable constraints for.- Return type:
Iterable[Constraint|Expression]- Returns:
A iterable of
ilpy.Constraintorilpy.expressions.Expression.Seemotile.constraints.Constraintfor how to create linear constraints.
The following lists all variables that are already implemented in motile.
Node Variables
- class motile.variables.NodeSelected(solver, index_map)
Binary variable indicating whether a node is part of the solution or not.
- class motile.variables.NodeSplit(solver, index_map)
Binary variable indicating whether a node has more than one child.
(i.e., the node is selected and has more than one selected outgoing edge).
This variable is coupled to the edge selection variables through the following linear constraints:
\[ \begin{align}\begin{aligned}2 s_v\; - &\sum_{e\in\text{out_edges}(v)} x_e &\leq&\;\; 0\\(|\text{out_edges}(v)| - 1) s_v\; - &\sum_{e\in\text{out_edges}(v)} x_e &\geq&\;\; -1\end{aligned}\end{align} \]where \(x_e\) are selection indicators for edge \(e\), and \(s_v\) is the split indicator for node \(v\).
- class motile.variables.NodeMerge(solver, index_map)
Binary variable indicating whether a node has more than one parent.
(i.e., the node is selected and has more than one selected incoming edge).
This variable is coupled to the edge selection variables through the following linear constraints:
\[ \begin{align}\begin{aligned}2 m_v\; - &\sum_{e\in\text{in_edges}(v)} x_e &\leq&\;\; 0\\(|\text{in_edges}(v)| - 1) m_v\; - &\sum_{e\in\text{in_edges}(v)} x_e &\geq&\;\; -1\end{aligned}\end{align} \]where \(x_e\) are selection indicators for edge \(e\), and \(m_v\) is the merge indicator for node \(v\).
- class motile.variables.NodeAppear(solver, index_map)
Binary variable indicating whether a node is the start of a track.
(i.e., the node is selected and has no selected incoming edges).
This variable is coupled to the node and edge selection variables through the following linear constraints:
\[ \begin{align}\begin{aligned}|\text{in_edges}(v)|\cdot x_v - &\sum_{e \in \text{in_edges}(v)} x_e - a_v &\leq&\;\; |\text{in_edges}(v)| - 1\\|\text{in_edges}(v)|\cdot x_v - &\sum_{e \in \text{in_edges}(v)} x_e - a_v\cdot |\text{in_edges}(v)| &\geq&\;\; 0\end{aligned}\end{align} \]where \(x_v\) and \(x_e\) are selection indicators for node \(v\) and edge \(e\), and \(a_v\) is the appear indicator for node \(v\).
- class motile.variables.NodeDisappear(solver, index_map)
Binary variable to indicate whether a node disappears.
This variable indicates whether the node is the end of a track (i.e., the node is selected and has no selected outgoing edges).
This variable is coupled to the node and edge selection variables through the following linear constraints:
\[ \begin{align}\begin{aligned}\begin{split}|\\text{out_edges}(v)|\cdot x_v - &\sum_{e \in \\text{out_edges}(v)} x_e - d_v &\leq&\;\; |\\text{out_edges}(v)| - 1\end{split}\\\begin{split}|\\text{out_edges}(v)|\cdot x_v - &\sum_{e \in \\text{out_edges}(v)} x_e - d_v\cdot |\\text{out_edges}(v)| &\geq&\;\; 0\end{split}\end{aligned}\end{align} \]where \(x_v\) and \(x_e\) are selection indicators for node \(v\) and edge \(e\), and \(d_v\) is the disappear indicator for node \(v\).
Edge Variables
- class motile.variables.EdgeSelected(solver, index_map)
Binary variable indicates whether an edge is part of the solution or not.
This is the base edge variable. All constraints operate on this variable. Costs on this variable affect all edges, while costs on the semantic edge variables (EdgeContinuation, EdgeSplit, EdgeMerge) affect only a subset.
- class motile.variables.EdgeContinuation(solver, index_map)
Binary variable indicating whether an edge is a continuation.
A continuation edge is one that is selected and whose source node is not a split node and whose target node is not a merge node. In other words, it represents a simple one-to-one linking between frames.
This variable is coupled to the edge selection, node split, and node merge variables through the following linear constraints:
\[ \begin{align}\begin{aligned}c_e &\leq x_e\\c_e &\leq 1 - s_{\text{src}(e)}\\c_e &\leq 1 - m_{\text{tgt}(e)}\\c_e &\geq x_e - s_{\text{src}(e)} - m_{\text{tgt}(e)}\end{aligned}\end{align} \]where \(x_e\) is the edge selection indicator, \(s_v\) is the node split indicator, \(m_v\) is the node merge indicator, and \(c_e\) is the edge continuation indicator.
- class motile.variables.EdgeSplit(solver, index_map)
Binary variable indicating whether an edge is part of a split.
An edge is a split edge if it is selected and its source node has more than one selected outgoing edge (i.e., the source node is a split node).
This variable is coupled to the edge selection and node split variables through the following linear constraints:
\[ \begin{align}\begin{aligned}y_e &\leq x_e\\y_e &\leq s_{\text{src}(e)}\\y_e &\geq x_e + s_{\text{src}(e)} - 1\end{aligned}\end{align} \]where \(x_e\) is the edge selection indicator, \(s_v\) is the node split indicator, and \(y_e\) is the edge split indicator.
- class motile.variables.EdgeMerge(solver, index_map)
Binary variable indicating whether an edge is part of a merge.
An edge is a merge edge if it is selected and its target node has more than one selected incoming edge (i.e., the target node is a merge node).
This variable is coupled to the edge selection and node merge variables through the following linear constraints:
\[ \begin{align}\begin{aligned}y_e &\leq x_e\\y_e &\leq m_{\text{tgt}(e)}\\y_e &\geq x_e + m_{\text{tgt}(e)} - 1\end{aligned}\end{align} \]where \(x_e\) is the edge selection indicator, \(m_v\) is the node merge indicator, and \(y_e\) is the edge merge indicator.
Edge Pair Variables
- class motile.variables.EdgeSplitPair(solver, index_map)
Binary variable for each pair of edges that could form a split.
For every node with at least two outgoing edges, and for every pair of those outgoing edges
(e1, e2), this variable is 1 iff both edges are selected (i.e., the pair is active as a split).This is coupled to the edge selection variables as a product of two binaries:
\[ \begin{align}\begin{aligned}g_{e_1,e_2} &\leq x_{e_1}\\g_{e_1,e_2} &\leq x_{e_2}\\g_{e_1,e_2} &\geq x_{e_1} + x_{e_2} - 1\end{aligned}\end{align} \]where \(x_e\) are edge selection indicators and \(g_{e_1,e_2}\) is the split group indicator.
- class motile.variables.EdgeMergePair(solver, index_map)
Binary variable for each pair of edges that could form a merge.
For every node with at least two incoming edges, and for every pair of those incoming edges
(e1, e2), this variable is 1 iff both edges are selected (i.e., the pair is active as a merge).This is coupled to the edge selection variables as a product of two binaries:
\[ \begin{align}\begin{aligned}g_{e_1,e_2} &\leq x_{e_1}\\g_{e_1,e_2} &\leq x_{e_2}\\g_{e_1,e_2} &\geq x_{e_1} + x_{e_2} - 1\end{aligned}\end{align} \]where \(x_e\) are edge merge indicators and \(g_{e_1,e_2}\) is the merge group indicator.
Costs
All costs inherit from the following base class:
- class motile.costs.Cost
A base class for a cost that can be added to a solver.
Weights should be initialized in the __init__ and added to a instance variable so that the Solver can discover them.
- abstractmethod apply(solver)
Apply a cost to the given solver.
Use
motile.Solver.get_variables()andmotile.Solver.add_variable_cost().
The following lists all costs that are already implemented in motile.
Node Costs
- class motile.costs.NodeSelectedCost(weight=1, attribute=None, constant=0.0)
Cost for
NodeSelectedvariables.- Parameters:
weight (
float) – The weight to apply to the cost given by the provided attribute of each node. Default is1.0attribute (
str|None) – The name of the node attribute to use to look up cost, or None if a constant cost is desired. Default isNone.constant (
float) – A constant cost for each selected node. Default is0.0.
- class motile.costs.NodeSplitCost(weight=1, attribute=None, constant=0)
Cost for
NodeSplitvariables.- Parameters:
weight (
float) – The weight to apply to the cost of each split. Default is1.attribute (
str|None) – The name of the attribute to use to look up the cost. Default isNone, which means that a constant cost is used.constant (
float) – A constant cost for each node that has more than one selected child. Default is0.
- class motile.costs.NodeMergeCost(weight=1, attribute=None, constant=0)
Cost for
NodeMergevariables.- Parameters:
weight (
float) – The weight to apply to the cost of each split. Default is1.attribute (
str|None) – The name of the attribute to use to look up the cost. Default isNone, which means that a constant cost is used.constant (
float) – A constant cost for each node that has more than one selected parent. Default is0.
- class motile.costs.NodeAppearCost(weight=1, attribute=None, constant=0, ignore_attribute=None)
Cost for
NodeAppearvariables.This is cost is not applied to nodes in the first frame of the graph.
- Parameters:
weight (
float) – The weight to apply to the cost attribute of each starting track. Defaults to 1.attribute (
str|None) – The name of the attribute to use to look up cost. Default isNone, which means that a constant cost is used.constant (
float) – A constant cost for each node that starts a track. Defaults to 0.ignore_attribute (
str|None) – The name of an optional node attribute that, if it is set and evaluates toTrue, will not set the appear cost for that node. Defaults to None
- class motile.costs.NodeDisappearCost(weight=1, attribute=None, constant=0, ignore_attribute=None)
Cost for
motile.variables.NodeDisappearvariables.This is cost is not applied to nodes in the last frame of the graph.
- Parameters:
weight (
float) – The weight to apply to the cost of each ending track. Defaults to 1.attribute (
str|None) – The name of the attribute to use to look up cost. Default isNone, which means that a constant cost is used.constant (
float) – A constant cost for each node that starts a track. Defaults to 0.ignore_attribute (
str|None) – The name of an optional node attribute that, if it is set and evaluates toTrue, will not set the disappear cost for that node. Defaults to None.
Edge Costs
- class motile.costs.EdgeSelectedCost(weight=1, attribute=None, constant=0.0)
Cost for
EdgeSelectedvariables.This cost is applied to all selected edges regardless of edge type.
- Parameters:
weight (
float) – The weight to apply to the cost given by the provided attribute of each edge. Default is1.0.attribute (
str|None) – The name of the edge attribute to use to look up cost. Default is None, which means only a constant cost is used.constant (
float) – A constant cost for each selected edge. Default is0.0.
- class motile.costs.EdgeDistanceCost(position_attribute, weight=1.0, constant=0.0)
Cost for
EdgeContinuationvariables.Cost is based on the spatial distance of the incident nodes. Applied only to continuation edges (not split or merge edges).
- Parameters:
position_attribute (
str|tuple[str,...]) – The name of the node attribute that corresponds to the spatial position. Can also be given as a tuple of multiple coordinates, e.g.,('z', 'y', 'x').weight (
float) – The weight to apply to the distance to convert it into a cost.constant (
float) – A constant cost for each selected node. Default is0.0.
- class motile.costs.EdgeSplitCost(weight=1, attribute=None, constant=0)
Cost for
EdgeSplitvariables.This cost is applied to edges that are part of a split (i.e., edges whose source node has more than one selected outgoing edge).
- Parameters:
Edge Pair Costs
- class motile.costs.SymmetricSplitCost(position_attribute, weight=1.0, constant=0.0)
Cost for
EdgeSplitPairvariables.For each pair of edges forming a split, the cost is the euclidean distance from the parent node’s position to the midpoint of the two children’s positions. A perfectly symmetric division (children equidistant from parent) has zero cost; asymmetric divisions have higher cost.
- Parameters:
position_attribute (
str|tuple[str,...]) – The name of the node attribute that corresponds to the spatial position. Can also be given as a tuple of multiple coordinates, e.g.,('z', 'y', 'x').weight (
float) – The weight to apply to the distance. Default is1.0.constant (
float) – A constant cost for each active split group. Default is0.0.
- class motile.costs.SymmetricMergeCost(position_attribute, weight=1.0, constant=0.0)
Cost for
EdgeMergePairvariables.For each pair of edges forming a merge, the cost is the euclidean distance from the child node’s position to the midpoint of the two parents’ positions. A perfectly symmetric merge (parents equidistant from child) has zero cost; asymmetric merges have higher cost.
- Parameters:
position_attribute (
str|tuple[str,...]) – The name of the node attribute that corresponds to the spatial position. Can also be given as a tuple of multiple coordinates, e.g.,('z', 'y', 'x').weight (
float) – The weight to apply to the distance. Default is1.0.constant (
float) – A constant cost for each active merge group. Default is0.0.
Features
Weights
Weight
- class motile.costs.Weight(initial_value)
A single Weight with observer/callback pattern on update.
See also
motile.costs.weights.Weights.
- Parameters:
initial_value (
float) – The initial value of the weight.
- register_modify_callback(callback)
Register a
callbackto be called when the weight is modified.
Weights
- class motile.costs.Weights
A simple container for weights with observer/callback pattern on update.
A
motile.Solverhas aWeightsinstance that is used to store the weights of the model. Changes to the weights can be observed withSolver.weights.register_modify_callback
- add_weight(weight, name)
Add a weight to the container.
- register_modify_callback(callback)
Register
callbackto be called when a weight is modified.
Constraints
All constraints inherit from the following base class:
- class motile.constraints.Constraint
A base class for a constraint that can be added to a solver.
The following lists all constraints that are already implemented in motile.
MaxChildren
- class motile.constraints.MaxChildren(max_children)
Bases:
ConstraintEnsures that every selected node has no more than
max_children.Where a “child” is a selected edges to the next frame.
Adds the following linear constraint for each node \(v\):
\[\sum_{e \in \text{out_edges}(v)} x_e \leq \text{max_children}\]
- Parameters:
max_children (
int) – The maximum number of children allowed.
MaxParents
- class motile.constraints.MaxParents(max_parents)
Bases:
ConstraintEnsures that every selected node has no more than
max_parents.Where a “parent” is defined as an incoming selected edge from the previous frame.
Adds the following linear constraint for each node \(v\):
\[\sum_{e \in \text{in_edges}(v)} x_e \leq \text{max_parents}\]
- Parameters:
max_parents (
int) – The maximum number of parents allowed.
ExpressionConstraint
- class motile.constraints.ExpressionConstraint(expression, eval_nodes=True, eval_edges=True)
Bases:
ConstraintEnforce selection of nodes/edges based on an expression.
The expression string is evaluated with the node/edge dict as a namespace.
This is a powerful general constraint that allows you to select nodes/edges based on any combination of node/edge attributes. The expression string is evaluated for each node/edge (assuming eval_nodes/eval_edges is True) using the actual node object as a namespace to populate any variables names used in the provided expression. If the expression evaluates to True, the node/edge is selected; otherwise, it is excluded.
This takes advantaged of python’s eval function, like this:
my_expression = "some_attribute == True" eval(my_expression, None, {"some_attribute": True}) # returns True (select) eval(my_expression, None, {"some_attribute": False}) # returns False (exclude) eval(my_expression, None, {}) # raises NameError (do nothing)
- Parameters:
expression (
str) – An expression to evaluate for each node/edge. The expression must evaluate to a boolean value. The expression can use any names of node/edge attributes as variables.eval_nodes (
bool) – Whether to evaluate the expression for nodes. By default, True.eval_edges (
bool) – Whether to evaluate the expression for edges. By default, True.Example
If the nodes of a graph are:
>>> cells = [ ... {"id": 0, "t": 0, "color": "red", "score": 1.0}, ... {"id": 1, "t": 0, "color": "green", "score": 1.0}, ... {"id": 2, "t": 1, "color": "blue", "score": 1.0}, ... ]Then the following constraint will select node 0:
>>> expr = "t == 0 and color != 'green'" >>> solver.add_constraints(ExpressionConstraint(expr))
Pin
- class motile.constraints.Pin(attribute)
Bases:
ExpressionConstraintEnforces the selection of nodes/edges based on truthiness of a given attribute.
Every node or edge that has the given attribute will be selected if the attribute value is
True(and not selected if the attribute value isFalse). The solver will only determine the selection of nodes and edges that do not have this attribute.This constraint is useful to complete partial tracking solutions, e.g., when users have already manually selected (or unselected) certain nodes or edges.
- Parameters:
attribute (
str) – The name of the node/edge attribute to use.