Skip to content

lineagetree

Modules:

Name Description
lineage_tree
lineage_tree_manager
measure
plot
tree_approximation

Classes:

Name Description
LineageTree

A lineage tree data structure with comprehensive analysis capabilities.

LineageTreeManager

Functions:

Name Description
read_from_ASTEC

Read an xml or pkl file produced by the ASTEC algorithm.

read_from_binary

Reads a binary LineageTree file name.

read_from_bmf

Read a lineage tree from a bmf file.

read_from_csv

Read a lineage tree from a csv file with the following format:

read_from_mamut_xml

Read a lineage tree from a MaMuT xml.

read_from_mastodon

Read a maston lineage tree.

read_from_mastodon_csv

Read a lineage tree from a mastodon csv.

read_from_tgmm_xml

Reads a lineage tree from TGMM xml output.

read_from_txt_for_celegans

Read a C. elegans lineage tree

read_from_txt_for_celegans_BAO

Read a C. elegans Bao file from http://digital-development.org

read_from_txt_for_celegans_CAO

Read a C. elegans lineage tree from Cao et al.

LineageTree

LineageTree(
    *,
    successor: dict[int, Sequence] | None = None,
    predecessor: dict[int, int | Sequence] | None = None,
    time: dict[int, int] | None = None,
    starting_time: int | None = None,
    pos: dict[int, Iterable] | None = None,
    name: str | None = None,
    root_leaf_value: Sequence | None = None,
    spatial_resolution: Sequence | None = None,
    **kwargs
)

Bases: PropertiesMixin, ModifierMixin, NavigationMixin, PlotMixin, SpatialMixin, AnalysisMixin, IOMixin

A lineage tree data structure with comprehensive analysis capabilities.

Create a LineageTree object from minimal information, without reading from a file. Either successor or predecessor should be specified.

Parameters:

Name Type Description Default

successor

dict mapping int to Iterable

Dictionary assigning nodes to their successors.

None

predecessor

dict mapping int to int or Iterable

Dictionary assigning nodes to their predecessors.

None

time

dict mapping int to int

Dictionary assigning nodes to the time point they were recorded to. Defaults to None, in which case all times are set to starting_time.

None

starting_time

int

Starting time of the lineage tree. Defaults to 0.

None

pos

dict mapping int to Iterable

Dictionary assigning nodes to their positions. Defaults to None.

None

name

str

Name of the lineage tree. Defaults to None.

None

root_leaf_value

Iterable

Iterable of values of roots' predecessors and leaves' successors in the successor and predecessor dictionaries. Defaults are [None, (), [], set()].

None

**kwargs

Supported keyword arguments are dictionaries assigning nodes to any custom property. The property must be specified for every node, and named differently from LineageTree's own attributes.

{}

Methods:

Name Description
get_subtree

Create a new lineage tree that has the same edges and properties

load

Loading a lineage tree from a '.lT' file.

Source code in src/lineagetree/lineage_tree.py
def __init__(
    self,
    *,
    successor: dict[int, Sequence] | None = None,
    predecessor: dict[int, int | Sequence] | None = None,
    time: dict[int, int] | None = None,
    starting_time: int | None = None,
    pos: dict[int, Iterable] | None = None,
    name: str | None = None,
    root_leaf_value: Sequence | None = None,
    spatial_resolution: Sequence | None = None,
    **kwargs,
):
    """Create a LineageTree object from minimal information, without reading from a file.
    Either `successor` or `predecessor` should be specified.

    Parameters
    ----------
    successor : dict mapping int to Iterable
        Dictionary assigning nodes to their successors.
    predecessor : dict mapping int to int or Iterable
        Dictionary assigning nodes to their predecessors.
    time : dict mapping int to int, optional
        Dictionary assigning nodes to the time point they were recorded to.
        Defaults to None, in which case all times are set to `starting_time`.
    starting_time : int, optional
        Starting time of the lineage tree. Defaults to 0.
    pos : dict mapping int to Iterable, optional
        Dictionary assigning nodes to their positions. Defaults to None.
    name : str, optional
        Name of the lineage tree. Defaults to None.
    root_leaf_value : Iterable, optional
        Iterable of values of roots' predecessors and leaves' successors in the successor and predecessor dictionaries.
        Defaults are `[None, (), [], set()]`.
    **kwargs:
        Supported keyword arguments are dictionaries assigning nodes to any custom property.
        The property must be specified for every node, and named differently from LineageTree's own attributes.
    """
    self.__version__ = importlib.metadata.version("lineagetree")
    self.name = str(name) if name is not None else None

    self._validator = TreeValidator(self)

    if successor is not None and predecessor is not None:
        raise ValueError(
            "You cannot have both successors and predecessors."
        )

    if root_leaf_value is None:
        root_leaf_value = [None, (), [], set()]
    elif not isinstance(root_leaf_value, Iterable):
        raise TypeError(
            f"root_leaf_value is of type {type(root_leaf_value)}, expected Iterable."
        )
    elif len(root_leaf_value) < 1:
        raise ValueError(
            "root_leaf_value should have at least one element."
        )
    self._successor = {}
    self._predecessor = {}
    if successor is not None:
        for pred, succs in successor.items():
            if succs in root_leaf_value:
                self._successor[pred] = ()
            else:
                if not isinstance(succs, Iterable):
                    raise TypeError(
                        f"Successors should be Iterable, got {type(succs)}."
                    )
                if len(succs) == 0:
                    raise ValueError(
                        f"{succs} was not declared as a leaf but was found as a successor.\n"
                        "Please lift the ambiguity."
                    )
                self._successor[pred] = tuple(succs)
                for succ in succs:
                    if succ in self._predecessor:
                        raise ValueError(
                            "Node can have at most one predecessor."
                        )
                    self._predecessor[succ] = (pred,)
    elif predecessor is not None:
        for succ, pred in predecessor.items():
            if pred in root_leaf_value:
                self._predecessor[succ] = ()
            else:
                if isinstance(pred, Sequence):
                    if len(pred) == 0:
                        raise ValueError(
                            f"{pred} was not declared as a leaf but was found as a successor.\n"
                            "Please lift the ambiguity."
                        )
                    if 1 < len(pred):
                        raise ValueError(
                            "Node can have at most one predecessor."
                        )
                    pred = pred[0]
                self._predecessor[succ] = (pred,)
                self._successor.setdefault(pred, ())
                self._successor[pred] += (succ,)
    for root in set(self._successor).difference(self._predecessor):
        self._predecessor[root] = ()
    for leaf in set(self._predecessor).difference(self._successor):
        self._successor[leaf] = ()

    if self._validator.check_for_cycles():
        raise ValueError(
            "Cycles were found in the tree, there should not be any."
        )

    if pos is None or len(pos) == 0:
        self.pos = {}
    else:
        if self.nodes.difference(pos) != set():
            raise ValueError("Please provide the position of all nodes.")
        self.pos = {
            node: np.array(position) for node, position in pos.items()
        }
    if "labels" in kwargs:
        self._labels = kwargs["labels"]
        kwargs.pop("labels")
    if time is None:
        if starting_time is None:
            starting_time = 0
        if not isinstance(starting_time, int):
            warnings.warn(
                f"Attribute `starting_time` was a `{type(starting_time)}`, has been casted as an `int`.",
                stacklevel=2,
            )
            starting_time = int(starting_time)
        self._time = dict.fromkeys(self.roots, starting_time)
        queue = list(self.roots)
        for node in queue:
            for succ in self._successor[node]:
                self._time[succ] = self._time[node] + 1
                queue.append(succ)
    else:
        if starting_time is not None:
            warnings.warn(
                "Both `time` and `starting_time` were provided, `starting_time` was ignored.",
                stacklevel=2,
            )
        self._time = {n: int(time[n]) for n in self.nodes}
        if self._time != time:
            if len(self._time) != len(time):
                warnings.warn(
                    "The provided `time` dictionary had keys that were not nodes. "
                    "They have been removed",
                    stacklevel=2,
                )
            else:
                warnings.warn(
                    "The provided `time` dictionary had values that were not `int`. "
                    "These values have been truncated and converted to `int`",
                    stacklevel=2,
                )
        if self.nodes.symmetric_difference(self._time) != set():
            raise ValueError(
                "Please provide the time of all nodes and only existing nodes."
            )
        if not all(
            self._time[node] < self._time[s]
            for node, succ in self._successor.items()
            for s in succ
        ):
            raise ValueError(
                "Provided times are not strictly increasing. Setting times to default."
            )
    # custom properties
    self._custom_property_list = []
    for name, d in kwargs.items():
        if name in self.__dict__:
            warnings.warn(
                f"Attribute name {name} is reserved.", stacklevel=2
            )
            continue
        setattr(self, name, d)
        self._custom_property_list.append(name)
    if not hasattr(self, "_comparisons"):
        self._comparisons = {}

    spatia_dimension = (
        len(self.pos[next(iter(self.nodes))])
        if self.nodes and self.pos
        else 3
    )
    if self.nodes and spatial_resolution is not None:
        if len(spatial_resolution) == spatia_dimension:
            self.spatial_resolution = np.array(spatial_resolution)
        else:
            raise ValueError(
                "The spatial resolution should have the same dimension as the one of the positions:\n"
                f"{len(spatial_resolution)=}, spatial dimension={spatia_dimension}"
            )
    else:
        self.spatial_resolution = spatial_resolution or np.ones(
            spatia_dimension
        )

get_subtree

get_subtree(node_list: set[int]) -> LineageTree

Create a new lineage tree that has the same edges and properties as the given lineage tree. Only the nodes in node_list are considered.

Parameters:

Name Type Description Default

node_list

Iterator of int

Iterator over the nodes to keep

required

Returns:

Type Description
LineageTree

The subtree lineage tree

Source code in src/lineagetree/lineage_tree.py
def get_subtree(self, node_list: set[int]) -> LineageTree:
    """Create a new lineage tree that has the same edges and properties
    as the given lineage tree. Only the nodes in `node_list` are considered.

    Parameters
    ----------
    node_list : Iterator of int
        Iterator over the nodes to keep

    Returns
    -------
    LineageTree
        The subtree lineage tree
    """
    new_successors = {
        n: tuple(vi for vi in self.successor[n] if vi in node_list)
        for n in node_list
    }
    return LineageTree(
        successor=new_successors,
        time=self._time,
        pos=self.pos,
        name=self.name,
        root_leaf_value=[
            (),
        ],
        **{
            name: self.__dict__[name]
            for name in self._custom_property_list
        },
    )

load classmethod

load(clf, fname: str)

Loading a lineage tree from a '.lT' file.

Parameters:

Name Type Description Default

fname

str

path to and name of the file to read

required

Returns:

Type Description
LineageTree

loaded file

Source code in src/lineagetree/lineage_tree.py
@classmethod
def load(clf, fname: str):
    """Loading a lineage tree from a '.lT' file.

    Parameters
    ----------
    fname : str
        path to and name of the file to read

    Returns
    -------
    LineageTree
        loaded file
    """
    with open(fname, "br") as f:
        lT = CompatibleUnpickler(f).load()
        f.close()
    if not hasattr(lT, "__version__") or Version(lT.__version__) < Version(
        "2.0.0"
    ):
        properties = {
            prop_name: prop
            for prop_name, prop in lT.__dict__.items()
            if (isinstance(prop, dict) or prop_name == "_time_resolution")
            and prop_name
            not in [
                "successor",
                "predecessor",
                "time",
                "_successor",
                "_predecessor",
                "_time",
                "pos",
                "labels",
            ]
            + LineageTree._dynamic_properties
            + LineageTree._protected_dynamic_properties
        }
        lT = LineageTree(
            successor=lT._successor,
            time=lT._time,
            pos=lT.pos,
            name=lT.name if hasattr(lT, "name") else None,
            **properties,
        )
    if not hasattr(lT, "time_resolution"):
        lT.time_resolution = 1
    if not hasattr(lT, "spatial_resolution"):
        lT.spatial_resolution = np.ones(3)

    return lT

LineageTreeManager

LineageTreeManager(
    lineagetree_list: Iterable[LineageTree] = (),
)

Creates a LineageTreeManager :TODO: write the docstring

Parameters:

Name Type Description Default

lineagetree_list

Iterable[LineageTree]

List of lineage trees to be in the LineageTreeManager

()

Methods:

Name Description
__len__

Returns how many lineagetrees are in the manager.

add

Function that adds a new lineagetree object to the class.

cross_lineage_edit_distance

Compute the unordered tree edit backtrace from Zhang 1996 between the trees spawned

labelled_mappings

Returns the labels or IDs of all the nodes in the subtrees compared.

load

Loading a lineage tree Manager from a ".ltm" file.

plot_tree_distance_graphs

Plots the subtrees compared and colors them according to the quality of the matching of their subtree.

remove_embryo

Removes the embryo from the manager.

write

Saves the manager

Attributes:

Name Type Description
gcd int

Calculates the greatesτ common divisor between all lineagetree resolutions in the manager.

Source code in src/lineagetree/lineage_tree_manager.py
def __init__(self, lineagetree_list: Iterable[LineageTree] = ()):
    """Creates a LineageTreeManager
    :TODO: write the docstring

    Parameters
    ----------
    lineagetree_list: Iterable of LineageTree
        List of lineage trees to be in the LineageTreeManager
    """
    self.lineagetrees: dict[str, LineageTree] = {}
    self.lineageTree_counter: int = 0
    self._comparisons: dict = {}
    for lT in lineagetree_list:
        self.add(lT)

gcd property

gcd: int

Calculates the greatesτ common divisor between all lineagetree resolutions in the manager.

Returns:

Type Description
int

The overall greatest common divisor.

__calculate_distance_of_sub_tree

__calculate_distance_of_sub_tree(
    node1: int,
    lT1: LineageTree,
    node2: int,
    lT2: LineageTree,
    alignment: Alignment,
    corres1: dict,
    corres2: dict,
    delta_tmp: Callable,
    norm: Callable,
    norm1: int | float,
    norm2: int | float,
) -> float

Calculates the distance of the subtree of a node matched in a comparison. DOES NOT CALCULATE THE DISTANCE FROM SCRATCH BUT USING THE ALIGNMENT.

TODO ITS BOUND TO CHANGE

Parameters:

Name Type Description Default

node1

int

The root of the first subtree

required

lT1

LineageTree

The dataset the first lineage exists

required

node2

int

The root of the first subtree

required

lT2

LineageTree

The dataset the second lineage exists

required

alignment

Alignment

The alignment of the subtree

required

corres1

dict

The correspndance dictionary of the first lineage

required

corres2

dict

The correspondance dictionary of the second lineage

required

delta_tmp

Callable

The delta function for the comparisons

required

norm

Callable

How should the lineages be normalized

required

norm1

int or float

The result of the normalization of the first tree

required

norm2

int or float

The result of the normalization of the second tree

required

Returns:

Type Description
float

The result of the comparison of the subtree

Source code in src/lineagetree/lineage_tree_manager.py
def __calculate_distance_of_sub_tree(
    self,
    node1: int,
    lT1: LineageTree,
    node2: int,
    lT2: LineageTree,
    alignment: Alignment,
    corres1: dict,
    corres2: dict,
    delta_tmp: Callable,
    norm: Callable,
    norm1: int | float,
    norm2: int | float,
) -> float:
    """Calculates the distance of the subtree of a node matched in a comparison.
    DOES NOT CALCULATE THE DISTANCE FROM SCRATCH BUT USING THE ALIGNMENT.

    TODO ITS BOUND TO CHANGE

    Parameters
    ----------
    node1 : int
        The root of the first subtree
    lT1 : LineageTree
        The dataset the first lineage exists
    node2 : int
        The root of the first subtree
    lT2 : LineageTree
        The dataset the second lineage exists
    alignment : Alignment
        The alignment of the subtree
    corres1 : dict
        The correspndance dictionary of the first lineage
    corres2 : dict
        The correspondance dictionary of the second lineage
    delta_tmp : Callable
        The delta function for the comparisons
    norm : Callable
        How should the lineages be normalized
    norm1 : int or float
        The result of the normalization of the first tree
    norm2 : int or float
        The result of the normalization of the second tree

    Returns
    -------
    float
        The result of the comparison of the subtree
    """
    sub_tree_1 = set(lT1.get_subtree_nodes(node1))
    sub_tree_2 = set(lT2.get_subtree_nodes(node2))
    res = 0
    for m in alignment:
        if (
            corres1.get(m._left, -1) in sub_tree_1
            or corres2.get(m._right, -1) in sub_tree_2
        ):
            res += delta_tmp(
                m._left if m._left != -1 else None,
                m._right if m._right != -1 else None,
            )
    return res / norm([norm1, norm2])

__cross_lineage_edit_backtrace

__cross_lineage_edit_backtrace(
    n1: int,
    embryo_1: str,
    n2: int,
    embryo_2: str,
    end_time1: int | None = None,
    end_time2: int | None = None,
    style: (
        Literal[
            "simple",
            "normalized_simple",
            "full",
            "downsampled",
        ]
        | type[TreeApproximationTemplate]
    ) = "simple",
    norm: Literal["max", "sum", None] = "max",
    downsample: int = 2,
) -> dict[
    str,
    Alignment
    | tuple[
        TreeApproximationTemplate, TreeApproximationTemplate
    ],
]

Compute the unordered tree edit distance from Zhang 1996 between the trees spawned by two nodes n1 from lineagetree1 and n2 lineagetree2. The topology of the trees are compared and the matching cost is given by the function delta (see edist doc for more information).The distance is normed by the function norm that takes the two list of nodes spawned by the trees n1 and n2.

Parameters:

Name Type Description Default

n1

int

Node of the first Lineagetree

required

embryo_1

str

The key/name of the first Lineagetree

required

n2

int

The key/name of the first Lineagetree

required

embryo_2

str

Node of the second Lineagetree

required

end_time1

int

The final time point the comparison algorithm will take into account for the first dataset. If None or not provided all nodes will be taken into account.

None

end_time2

int

The final time point the comparison algorithm will take into account for the second dataset. If None or not provided all nodes will be taken into account.

None

style

('simple', 'normalized_simple', 'full', 'downsampled')

The approximation used to calculate the tree.

"simple"

norm

('max', 'sum', 'None')

The normalization method used (Not important for this function)

"max","sum"

downsample

int

The downsample factor for the downsampled tree approximation. Used only when style="downsampled".

=2

Returns:

Type Description
dict mapping str to Alignment or tuple of [TreeApproximationTemplate, TreeApproximationTemplate]
  • 'alignment' The alignment between the nodes by the subtrees spawned by the nodes n1,n2 and the normalization function.`
  • 'trees' A list of the two trees that have been mapped to each other.
Source code in src/lineagetree/lineage_tree_manager.py
def __cross_lineage_edit_backtrace(
    self,
    n1: int,
    embryo_1: str,
    n2: int,
    embryo_2: str,
    end_time1: int | None = None,
    end_time2: int | None = None,
    style: (
        Literal["simple", "normalized_simple", "full", "downsampled"]
        | type[TreeApproximationTemplate]
    ) = "simple",
    norm: Literal["max", "sum", None] = "max",
    downsample: int = 2,
) -> dict[
    str,
    Alignment
    | tuple[TreeApproximationTemplate, TreeApproximationTemplate],
]:
    """Compute the unordered tree edit distance from Zhang 1996 between the trees spawned
    by two nodes `n1` from lineagetree1 and `n2` lineagetree2. The topology of the trees
    are compared and the matching cost is given by the function delta (see edist doc for
    more information).The distance is normed by the function norm that takes the two list
    of nodes spawned by the trees `n1` and `n2`.

    Parameters
    ----------
    n1 : int
        Node of the first Lineagetree
    embryo_1 : str
        The key/name of the first Lineagetree
    n2 : int
        The key/name of the first Lineagetree
    embryo_2 : str
        Node of the second Lineagetree
    end_time1 : int, optional
        The final time point the comparison algorithm will take into account for the first dataset.
        If None or not provided all nodes will be taken into account.
    end_time2 : int, optional
         The final time point the comparison algorithm will take into account for the second dataset.
        If None or not provided all nodes will be taken into account.
    style : {"simple", "normalized_simple", "full", "downsampled"} or TreeApproximationTemplate subclass, default="simple"
        The approximation used to calculate the tree.
    norm : {"max","sum", "None"}, default="max"
        The normalization method used (Not important for this function)
    downsample : int, default==2
        The downsample factor for the downsampled tree approximation.
        Used only when `style="downsampled"`.

    Returns
    -------
    dict mapping str to Alignment or tuple of [TreeApproximationTemplate, TreeApproximationTemplate]
        - 'alignment'
            The alignment between the nodes by the subtrees spawned by the nodes n1,n2 and the normalization function.`
        - 'trees'
            A list of the two trees that have been mapped to each other.
    """
    if (
        self[embryo_1].time_resolution <= 0
        or self[embryo_2].time_resolution <= 0
    ):
        raise Warning("Resolution cannot be <=0 ")
    parameters = (
        (end_time1, end_time2),
        convert_style_to_number(style, downsample),
    )
    n1_embryo, n2_embryo = sorted(
        ((n1, embryo_1), (n2, embryo_2)), key=lambda x: x[0]
    )
    self._comparisons.setdefault(parameters, {})
    if isinstance(style, str):
        tree = tree_style[style].value
    elif issubclass(style, TreeApproximationTemplate):
        tree = style
    else:
        raise Warning("Use a valid approximation.")
    time_res = tree.handle_resolutions(
        time_resolution1=self.lineagetrees[embryo_1]._time_resolution,
        time_resolution2=self.lineagetrees[embryo_2]._time_resolution,
        gcd=self.gcd,
        downsample=downsample,
    )
    tree1 = tree(
        lT=self.lineagetrees[embryo_1],
        downsample=downsample,
        end_time=end_time1,
        root=n1,
        time_scale=time_res[0],
    )
    tree2 = tree(
        lT=self.lineagetrees[embryo_2],
        downsample=downsample,
        end_time=end_time2,
        root=n2,
        time_scale=time_res[1],
    )
    delta = tree1.delta
    _, times1 = tree1.tree
    _, times2 = tree2.tree

    nodes1, adj1, corres1 = tree1.edist
    nodes2, adj2, corres2 = tree2.edist
    if len(nodes1) == len(nodes2) == 0:
        self._comparisons[parameters][(n1_embryo, n2_embryo)] = {
            "alignment": (),
            "trees": (),
        }
        return self._comparisons[parameters][(n1_embryo, n2_embryo)]
    delta_tmp = partial(
        delta,
        corres1=corres1,
        times1=times1,
        corres2=corres2,
        times2=times2,
    )
    btrc = uted.uted_backtrace(nodes1, adj1, nodes2, adj2, delta=delta_tmp)

    self._comparisons[parameters][(n1_embryo, n2_embryo)] = {
        "alignment": btrc,
        "trees": (tree1, tree2),
    }
    return self._comparisons[parameters][(n1_embryo, n2_embryo)]

__len__

__len__() -> int

Returns how many lineagetrees are in the manager.

Returns:

Type Description
int

The number of trees inside the manager

Source code in src/lineagetree/lineage_tree_manager.py
def __len__(self) -> int:
    """Returns how many lineagetrees are in the manager.

    Returns
    -------
    int
        The number of trees inside the manager
    """
    return len(self.lineagetrees)

add

add(other_tree: LineageTree, name: str = '')

Function that adds a new lineagetree object to the class. Can be added either by .add or by using the + operator. If a name is specified it will also add it as this specific name, otherwise it will use the already existing name of the lineagetree.

Parameters:

Name Type Description Default

other_tree

LineageTree

Thelineagetree to be added.

required

name

str

Then name of the lineagetree to be added, defaults to ''. (Usually lineageTrees have the name of the path they are read from, so this is going to be the name most of the times.)

""
Source code in src/lineagetree/lineage_tree_manager.py
def add(self, other_tree: LineageTree, name: str = ""):
    """Function that adds a new lineagetree object to the class.
    Can be added either by .add or by using the + operator. If a name is
    specified it will also add it as this specific name, otherwise it will
    use the already existing name of the lineagetree.

    Parameters
    ----------
    other_tree : LineageTree
        Thelineagetree to be added.
    name : str, default=""
        Then name of the lineagetree to be added, defaults to ''.
        (Usually lineageTrees have the name of the path they are read from,
        so this is going to be the name most of the times.)
    """
    if isinstance(other_tree, LineageTree):
        for tree in self.lineagetrees.values():
            if tree == other_tree:
                return False
        if name:
            self.lineagetrees[name] = other_tree
        else:
            if other_tree.name:
                name = other_tree.name
                self.lineagetrees[name] = other_tree
            else:
                name = f"Lineagetree {next(self)}"
                self.lineagetrees[name] = other_tree
                self.lineagetrees[name].name = name
    else:
        raise Exception(
            "Please add a LineageTree object or add time resolution to the LineageTree added."
        )

cross_lineage_edit_distance

cross_lineage_edit_distance(
    n1: int,
    embryo_1: str,
    n2: int,
    embryo_2: str,
    end_time1: int | None = None,
    end_time2: int | None = None,
    norm: Literal["max", "sum", None] = "max",
    style: (
        Literal[
            "simple",
            "normalized_simple",
            "full",
            "downsampled",
        ]
        | type[TreeApproximationTemplate]
    ) = "simple",
    downsample: int = 2,
    return_norms: bool = False,
) -> float | tuple[float, tuple[float, float]]

Compute the unordered tree edit backtrace from Zhang 1996 between the trees spawned by two nodes n1 and n2. The topology of the trees are compared and the matching cost is given by the function delta (see edist doc for more information). There are 5 styles available (tree approximations) and the user may add their own.

Parameters:

Name Type Description Default

n1

int

id of the first node to compare

required

embryo_1

str

the name of the first embryo to be used. (from lTm.lineagetrees.keys())

required

n2

int

id of the second node to compare

required

embryo_2

str

the name of the second embryo to be used. (from lTm.lineagetrees.keys())

required

end_time_1

int

the final time point the comparison algorithm will take into account for the first dataset. If None or not provided all nodes will be taken into account.

required

end_time_2

int

the final time point the comparison algorithm will take into account for the second dataset. If None or not provided all nodes will be taken into account.

required

norm

('max', 'sum')

The normalization method to use, defaults to 'max'.

"max"

style

('simple', 'normalized_simple', 'full', 'downsampled')

Which tree approximation is going to be used for the comparisons, defaults to 'simple'.

"simple"

downsample

int

The downsample factor for the downsampled tree approximation. Used only when style="downsampled".

2

return_norms

bool

Decide if the norms will be returned explicitly (mainly used for the napari plugin)

False

Returns:

Type Description
Alignment

The alignment between the nodes by the subtrees spawned by the nodes n1,n2 and the normalization function.`

(tuple(tree, tree), optional)

The two trees that have been mapped to each other. Returned if return_norms is True

Source code in src/lineagetree/lineage_tree_manager.py
def cross_lineage_edit_distance(
    self,
    n1: int,
    embryo_1: str,
    n2: int,
    embryo_2: str,
    end_time1: int | None = None,
    end_time2: int | None = None,
    norm: Literal["max", "sum", None] = "max",
    style: (
        Literal["simple", "normalized_simple", "full", "downsampled"]
        | type[TreeApproximationTemplate]
    ) = "simple",
    downsample: int = 2,
    return_norms: bool = False,
) -> float | tuple[float, tuple[float, float]]:
    """
    Compute the unordered tree edit backtrace from Zhang 1996 between the trees spawned
    by two nodes `n1` and `n2`. The topology of the trees are compared and the matching
    cost is given by the function delta (see edist doc for more information). There are
    5 styles available (tree approximations) and the user may add their own.

    Parameters
    ----------
    n1 : int
        id of the first node to compare
    embryo_1 : str
        the name of the first embryo to be used. (from lTm.lineagetrees.keys())
    n2 : int
        id of the second node to compare
    embryo_2 : str
        the name of the second embryo to be used. (from lTm.lineagetrees.keys())
    end_time_1 : int, optional
        the final time point the comparison algorithm will take into account for the first dataset.
        If None or not provided all nodes will be taken into account.
    end_time_2 : int, optional
        the final time point the comparison algorithm will take into account for the second dataset.
        If None or not provided all nodes will be taken into account.
    norm : {"max", "sum"}, default="max"
        The normalization method to use, defaults to 'max'.
    style : {"simple", "normalized_simple", "full", "downsampled"} or TreeApproximationTemplate subclass, default="simple"
        Which tree approximation is going to be used for the comparisons, defaults to 'simple'.
    downsample : int, default=2
        The downsample factor for the downsampled tree approximation.
        Used only when `style="downsampled"`.
    return_norms : bool
        Decide if the norms will be returned explicitly (mainly used for the napari plugin)

    Returns
    -------
    Alignment
        The alignment between the nodes by the subtrees spawned by the nodes n1,n2 and the normalization function.`
    tuple(tree,tree), optional
        The two trees that have been mapped to each other.
        Returned if `return_norms` is `True`
    """

    parameters = (
        (end_time1, end_time2),
        convert_style_to_number(style, downsample),
    )
    n1_embryo, n2_embryo = sorted(
        ((n1, embryo_1), (n2, embryo_2)), key=lambda x: x[0]
    )
    self._comparisons.setdefault(parameters, {})
    if self._comparisons[parameters].get((n1, n2)):
        tmp = self._comparisons[parameters][(n1_embryo, n2_embryo)]
    else:
        tmp = self.__cross_lineage_edit_backtrace(
            n1,
            embryo_1,
            n2,
            embryo_2,
            end_time1,
            end_time2,
            style,
            norm,
            downsample,
        )
    if len(self._comparisons) > 100:
        warnings.warn(
            "More than 100 comparisons are saved, use clear_comparisons() to delete them.",
            stacklevel=2,
        )
    btrc = tmp["alignment"]
    tree1, tree2 = tmp["trees"]
    _, times1 = tree1.tree
    _, times2 = tree2.tree
    (
        nodes1,
        adj1,
        corres1,
    ) = tree1.edist
    (
        nodes2,
        adj2,
        corres2,
    ) = tree2.edist
    if len(nodes1) == len(nodes2) == 0:
        self._comparisons[hash(frozenset(parameters))] = {
            "alignment": (),
            "trees": (),
        }
        return self._comparisons[hash(frozenset(parameters))]
    delta_tmp = partial(
        tree1.delta,
        corres1=corres1,
        corres2=corres2,
        times1=times1,
        times2=times2,
    )
    if norm not in self.norm_dict:
        raise ValueError(
            "Select a viable normalization method (max, sum, None)"
        )
    cost = btrc.cost(nodes1, nodes2, delta_tmp)
    norm_values = (tree1.get_norm(n1), tree2.get_norm(n2))
    if return_norms:
        return cost, norm_values
    return cost / self.norm_dict[norm](norm_values)

labelled_mappings

labelled_mappings(
    n1: int,
    embryo_1: str,
    n2: int,
    embryo_2: str,
    end_time1: int | None = None,
    end_time2: int | None = None,
    norm: Literal["max", "sum", None] = "max",
    style: (
        Literal[
            "simple",
            "normalized_simple",
            "full",
            "downsampled",
        ]
        | type[TreeApproximationTemplate]
    ) = "simple",
    downsample: int = 2,
) -> dict[str, list[str]]

Returns the labels or IDs of all the nodes in the subtrees compared.

Parameters:

Name Type Description Default

n1

int

id of the first node to compare

required

embryo_1

str

the name of the first lineage

required

n2

int

id of the second node to compare

required

embryo_2

str

the name of the second lineage

required

end_time1

int

the final time point the comparison algorithm will take into account for the first dataset. If None or not provided all nodes will be taken into account.

None

end_time2

int

the final time point the comparison algorithm will take into account for the first dataset. If None or not provided all nodes will be taken into account.

None

norm

('max', 'sum')

The normalization method to use.

"max"

style

('simple', 'normalized_simple', 'full', 'downsampled')

Which tree approximation is going to be used for the comparisons.

"simple"

downsample

int

The downsample factor for the downsampled tree approximation. Used only when style="downsampled".

2

Returns:

Type Description
dict mapping str to lists of str
  • 'matched' The labels of the matched nodes of the alignment.
  • 'unmatched' The labels of the unmatched nodes of the alginment.
Source code in src/lineagetree/lineage_tree_manager.py
def labelled_mappings(
    self,
    n1: int,
    embryo_1: str,
    n2: int,
    embryo_2: str,
    end_time1: int | None = None,
    end_time2: int | None = None,
    norm: Literal["max", "sum", None] = "max",
    style: (
        Literal["simple", "normalized_simple", "full", "downsampled"]
        | type[TreeApproximationTemplate]
    ) = "simple",
    downsample: int = 2,
) -> dict[str, list[str]]:
    """
    Returns the labels or IDs of all the nodes in the subtrees compared.

    Parameters
    ----------
    n1 : int
        id of the first node to compare
    embryo_1 : str
        the name of the first lineage
    n2 : int
        id of the second node to compare
    embryo_2: str
        the name of the second lineage
    end_time1 : int, optional
        the final time point the comparison algorithm will take into account for the first dataset.
        If None or not provided all nodes will be taken into account.
    end_time2 : int, optional
        the final time point the comparison algorithm will take into account for the first dataset.
        If None or not provided all nodes will be taken into account.
    norm : {"max", "sum"}, default="max"
        The normalization method to use.
    style : {"simple", "normalized_simple", "full", "downsampled"} or TreeApproximationTemplate subclass, default="simple"
        Which tree approximation is going to be used for the comparisons.
    downsample : int, default=2
        The downsample factor for the downsampled tree approximation.
        Used only when `style="downsampled"`.

    Returns
    -------
    dict mapping str to lists of str
        - 'matched' The labels of the matched nodes of the alignment.
        - 'unmatched' The labels of the unmatched nodes of the alginment.
    """

    parameters = (
        (end_time1, end_time2),
        convert_style_to_number(style, downsample),
    )
    n1_embryo, n2_embryo = sorted(
        ((n1, embryo_1), (n2, embryo_2)), key=lambda x: x[0]
    )
    self._comparisons.setdefault(parameters, {})
    if self._comparisons[parameters].get((n1, n2)):
        tmp = self._comparisons[parameters][(n1_embryo, n2_embryo)]
    else:
        tmp = self.__cross_lineage_edit_backtrace(
            n1,
            embryo_1,
            n2,
            embryo_2,
            end_time1,
            end_time2,
            style,
            norm,
            downsample,
        )
    btrc = tmp["alignment"]
    tree1, tree2 = tmp["trees"]
    _, times1 = tree1.tree
    _, times2 = tree2.tree
    (
        *_,
        corres1,
    ) = tree1.edist
    (
        *_,
        corres2,
    ) = tree2.edist
    if norm not in self.norm_dict:
        raise Warning(
            "Select a viable normalization method (max, sum, None)"
        )
    matched = []
    unmatched = []
    if style not in ("full", "downsampled"):
        for m in btrc:
            if m._left != -1 and m._right != -1:
                cyc1 = tree1.lT.get_chain_of_node(corres1[m._left])
                if len(cyc1) > 1:
                    node_1, *_ = cyc1
                elif len(cyc1) == 1:
                    node_1 = cyc1.pop()

                cyc2 = tree2.lT.get_chain_of_node(corres2[m._right])
                if len(cyc2) > 1:
                    node_2, *_ = cyc2

                elif len(cyc2) == 1:
                    node_2 = cyc2.pop()

                matched.append(
                    (
                        tree1.lT.labels.get(node_1, node_1),
                        tree2.lT.labels.get(node_2, node_2),
                    )
                )
            else:
                if m._left != -1:
                    tmp_node = tree1.lT.get_chain_of_node(
                        corres1.get(m._left, "-")
                    )[0]
                    node_1 = (
                        tree1.lT.labels.get(tmp_node, tmp_node),
                        tree1.lT.name,
                    )
                else:
                    tmp_node = tree2.lT.get_chain_of_node(
                        corres2.get(m._right, "-")
                    )[0]
                    node_1 = (
                        tree2.lT.labels.get(tmp_node, tmp_node),
                        tree2.lT.name,
                    )
                unmatched.append(node_1)
    else:
        for m in btrc:
            if m._left != -1 and m._right != -1:
                node_1 = corres1[m._left]
                node_2 = corres2[m._right]
                matched.append(
                    (
                        tree1.lT.labels.get(node_1, node_1),
                        tree2.lT.labels.get(node_2, node_2),
                    )
                )
            else:
                if m._left != -1:
                    tmp_node = tree1.lT.get_chain_of_node(
                        corres1.get(m._left, "-")
                    )[0]
                    node_1 = (
                        tree1.lT.labels.get(tmp_node, tmp_node),
                        tree1.lT.name,
                    )
                else:
                    tmp_node = tree2.lT.get_chain_of_node(
                        corres2.get(m._right, "-")
                    )[0]
                    node_1 = (
                        tree2.lT.labels.get(tmp_node, tmp_node),
                        tree2.lT.name,
                    )
                unmatched.append(node_1)
    return {"matched": matched, "unmatched": unmatched}

load classmethod

load(fname: str) -> LineageTreeManager

Loading a lineage tree Manager from a ".ltm" file.

Parameters:

Name Type Description Default

fname

str

path to and name of the file to read

required

Returns:

Type Description
LineageTreeManager

loaded file

Source code in src/lineagetree/lineage_tree_manager.py
@classmethod
def load(cls, fname: str) -> LineageTreeManager:
    """Loading a lineage tree Manager from a ".ltm" file.

    Parameters
    ----------
    fname : str
        path to and name of the file to read

    Returns
    -------
    LineageTreeManager
        loaded file
    """
    with open(fname, "br") as f:
        ltm = pkl.load(f)
        f.close()
    return ltm

plot_tree_distance_graphs

plot_tree_distance_graphs(
    n1: int,
    embryo_1: str,
    n2: int,
    embryo_2: str,
    end_time1: int | None = None,
    end_time2: int | None = None,
    norm: Literal["max", "sum"] | None = "max",
    style: (
        Literal[
            "simple",
            "normalized_simple",
            "full",
            "downsampled",
        ]
        | type[TreeApproximationTemplate]
    ) = "simple",
    downsample: int = 2,
    colormap: str = "cool",
    default_color: str = "black",
    size: float = 10,
    lw: float = 0.3,
    ax: ndarray | None = None,
    vmin=None,
    vmax=None,
) -> tuple[plt.figure, plt.Axes]

Plots the subtrees compared and colors them according to the quality of the matching of their subtree.

Parameters:

Name Type Description Default

n1

int

id of the first node to compare

required

embryo_1

str

the name of the first embryo

required

n2

int

id of the second node to compare

required

embryo_2

str

the name of the second embryo

required

end_time1

int

the final time point the comparison algorithm will take into account for the first dataset. If None or not provided all nodes will be taken into account.

None

end_time2

int

the final time point the comparison algorithm will take into account for the second dataset. If None or not provided all nodes will be taken into account.

None

norm

('max', 'sum')

The normalization method to use.

"max"

style

('simple', 'normalized_simple', 'full', 'downsampled')

Which tree approximation is going to be used for the comparisons.

"simple"

downsample

int

The downsample factor for the downsampled tree approximation. Used only when style="downsampled".

2

colormap

str

The colormap used for matched nodes, defaults to "cool"

"cool"

default_color

str

The color of the unmatched nodes, defaults to "black"

'black'

size

float

The size of the nodes, defaults to 10

10

lw

float

The width of the edges, defaults to 0.3

0.3

ax

ndarray

The axes used, if not provided another set of axes is produced, defaults to None

None

vmin

Values within the range [vmin, vmax] from the input data will be linearly mapped to [0, 1]. vmin defaults to the 0.05 quantile of the values of the unordered tree edist distances of the subtrees. vmax defaults to the 0.95 quantile of the values of the unordered tree edist distances of the subtrees.

None

vmax

Values within the range [vmin, vmax] from the input data will be linearly mapped to [0, 1]. vmin defaults to the 0.05 quantile of the values of the unordered tree edist distances of the subtrees. vmax defaults to the 0.95 quantile of the values of the unordered tree edist distances of the subtrees.

None

Returns:

Type Description
Figure

The matplotlib figure

Axes

The matplotlib axes

Source code in src/lineagetree/lineage_tree_manager.py
def plot_tree_distance_graphs(
    self,
    n1: int,
    embryo_1: str,
    n2: int,
    embryo_2: str,
    end_time1: int | None = None,
    end_time2: int | None = None,
    norm: Literal["max", "sum"] | None = "max",
    style: (
        Literal["simple", "normalized_simple", "full", "downsampled"]
        | type[TreeApproximationTemplate]
    ) = "simple",
    downsample: int = 2,
    colormap: str = "cool",
    default_color: str = "black",
    size: float = 10,
    lw: float = 0.3,
    ax: np.ndarray | None = None,
    vmin=None,
    vmax=None,
) -> tuple[plt.figure, plt.Axes]:
    """
    Plots the subtrees compared and colors them according to the quality of the matching of their subtree.

    Parameters
    ----------
    n1 : int
        id of the first node to compare
    embryo_1 : str
        the name of the first embryo
    n2 : int
        id of the second node to compare
    embryo_2 : str
        the name of the second embryo
    end_time1 : int, optional
        the final time point the comparison algorithm will take into account for the first dataset.
        If None or not provided all nodes will be taken into account.
    end_time2 : int, optional
        the final time point the comparison algorithm will take into account for the second dataset.
        If None or not provided all nodes will be taken into account.
    norm : {"max", "sum"}, default="max"
        The normalization method to use.
    style : {"simple", "normalized_simple", "full", "downsampled"} or TreeApproximationTemplate subclass, default="simple"
        Which tree approximation is going to be used for the comparisons.
    downsample : int, default=2
        The downsample factor for the downsampled tree approximation.
        Used only when `style="downsampled"`.
    colormap : str, default="cool"
        The colormap used for matched nodes, defaults to "cool"
    default_color : str
        The color of the unmatched nodes, defaults to "black"
    size : float
        The size of the nodes, defaults to 10
    lw : float
        The width of the edges, defaults to 0.3
    ax : np.ndarray, optional
        The axes used, if not provided another set of axes is produced, defaults to None
    vmin, vmax: float, optional
        Values within the range ``[vmin, vmax]`` from the input data will be
        linearly mapped to ``[0, 1]``.
        *vmin* defaults to the 0.05 quantile of the values of the unordered tree edist distances of the subtrees.
        *vmax* defaults to the 0.95 quantile of the values of the unordered tree edist distances of the subtrees.

    Returns
    -------
    plt.Figure
        The matplotlib figure
    plt.Axes
        The matplotlib axes
    """

    parameters = (
        (end_time1, end_time2),
        convert_style_to_number(style, downsample),
    )
    n1_embryo, n2_embryo = sorted(
        ((n1, embryo_1), (n2, embryo_2)), key=lambda x: x[0]
    )
    self._comparisons.setdefault(parameters, {})
    if self._comparisons[parameters].get((n1, n2)):
        tmp = self._comparisons[parameters][(n1_embryo, n2_embryo)]
    else:
        tmp = self.__cross_lineage_edit_backtrace(
            n1,
            embryo_1,
            n2,
            embryo_2,
            end_time1,
            end_time2,
            style,
            norm,
            downsample,
        )
    btrc = tmp["alignment"]
    tree1, tree2 = tmp["trees"]
    _, times1 = tree1.tree
    _, times2 = tree2.tree
    (
        *_,
        corres1,
    ) = tree1.edist
    (
        *_,
        corres2,
    ) = tree2.edist
    delta_tmp = partial(
        tree1.delta,
        corres1=corres1,
        corres2=corres2,
        times1=times1,
        times2=times2,
    )
    if norm not in self.norm_dict:
        raise Warning(
            "Select a viable normalization method (max, sum, None)"
        )
    matched_right = []
    matched_left = []
    colors1 = {}
    colors2 = {}
    if style not in ("full", "downsampled"):
        for m in btrc:
            if m._left != -1 and m._right != -1:
                cyc1 = tree1.lT.get_chain_of_node(corres1[m._left])
                if len(cyc1) > 1:
                    node_1, *_, l_node_1 = cyc1
                    matched_left.append(node_1)
                    matched_left.append(l_node_1)
                elif len(cyc1) == 1:
                    node_1 = l_node_1 = cyc1.pop()
                    matched_left.append(node_1)

                cyc2 = tree2.lT.get_chain_of_node(corres2[m._right])
                if len(cyc2) > 1:
                    node_2, *_, l_node_2 = cyc2
                    matched_right.append(node_2)
                    matched_right.append(l_node_2)

                elif len(cyc2) == 1:
                    node_2 = l_node_2 = cyc2.pop()
                    matched_right.append(node_2)

                colors1[node_1] = self.__calculate_distance_of_sub_tree(
                    node_1,
                    tree1.lT,
                    node_2,
                    tree2.lT,
                    btrc,
                    corres1,
                    corres2,
                    delta_tmp,
                    self.norm_dict[norm],
                    tree1.get_norm(node_1),
                    tree2.get_norm(node_2),
                )
                colors2[node_2] = colors1[node_1]
                colors1[l_node_1] = colors1[node_1]
                colors2[l_node_2] = colors2[node_2]

    else:
        for m in btrc:
            if m._left != -1 and m._right != -1:
                node_1 = tree1.lT.get_chain_of_node(corres1[m._left])[0]
                node_2 = tree2.lT.get_chain_of_node(corres2[m._right])[0]
                if (
                    tree1.lT.get_chain_of_node(node_1)[0] == node_1
                    or tree2.lT.get_chain_of_node(node_2)[0] == node_2
                    and (node_1 not in colors1 or node_2 not in colors2)
                ):
                    matched_left.append(node_1)
                    l_node_1 = tree1.lT.get_chain_of_node(node_1)[-1]
                    matched_left.append(l_node_1)
                    matched_right.append(node_2)
                    l_node_2 = tree2.lT.get_chain_of_node(node_2)[-1]
                    matched_right.append(l_node_2)
                    colors1[node_1] = (
                        self.__calculate_distance_of_sub_tree(
                            node_1,
                            tree1.lT,
                            node_2,
                            tree2.lT,
                            btrc,
                            corres1,
                            corres2,
                            delta_tmp,
                            self.norm_dict[norm],
                            tree1.get_norm(node_1),
                            tree2.get_norm(node_2),
                        )
                    )
                    colors2[node_2] = colors1[node_1]
                    colors1[tree1.lT.get_chain_of_node(node_1)[-1]] = (
                        colors1[node_1]
                    )
                    colors2[tree2.lT.get_chain_of_node(node_2)[-1]] = (
                        colors2[node_2]
                    )

                    if tree1.lT.get_chain_of_node(node_1)[-1] != node_1:
                        matched_left.append(
                            tree1.lT.get_chain_of_node(node_1)[-1]
                        )
                    if tree2.lT.get_chain_of_node(node_2)[-1] != node_2:
                        matched_right.append(
                            tree2.lT.get_chain_of_node(node_2)[-1]
                        )
    if ax is None:
        fig, ax = plt.subplots(nrows=1, ncols=2)
    cmap = colormaps[colormap]
    if vmin is None:
        vmin = np.percentile(list(colors1.values()), 5)
    if vmax is None:
        vmax = np.percentile(list(colors1.values()), 95)
    c_norm = mcolors.Normalize(vmin, vmax)
    colors1 = {c: cmap(c_norm(v)) for c, v in colors1.items()}
    colors2 = {c: cmap(c_norm(v)) for c, v in colors2.items()}
    tree1.lT.plot_subtree(
        tree1.lT.get_ancestor_at_t(n1),
        end_time=end_time1,
        size=size,
        color_of_nodes=colors1,
        color_of_edges=colors1,
        default_color=default_color,
        lw=lw,
        ax=ax[0],
    )
    tree2.lT.plot_subtree(
        tree2.lT.get_ancestor_at_t(n2),
        end_time=end_time2,
        size=size,
        color_of_nodes=colors2,
        color_of_edges=colors2,
        default_color=default_color,
        lw=lw,
        ax=ax[1],
    )
    return ax[0].get_figure(), ax

remove_embryo

remove_embryo(key: str)

Removes the embryo from the manager.

Parameters:

Name Type Description Default

key

str

The name of the lineagetree to be removed

required

Raises:

Type Description
IndexError

If there is no such lineagetree

Source code in src/lineagetree/lineage_tree_manager.py
def remove_embryo(self, key: str):
    """Removes the embryo from the manager.

    Parameters
    ----------
    key : str
        The name of the lineagetree to be removed

    Raises
    ------
    IndexError
        If there is no such lineagetree
    """
    self.lineagetrees.pop(key, None)

write

write(fname: str)

Saves the manager

Parameters:

Name Type Description Default

fname

str

The path and name of the file that is to be saved.

required
Source code in src/lineagetree/lineage_tree_manager.py
def write(self, fname: str):
    """Saves the manager

    Parameters
    ----------
    fname : str
        The path and name of the file that is to be saved.
    """
    if os.path.splitext(fname)[-1].upper() != ".LTM":
        fname = os.path.extsep.join((fname, "lTM"))
    for _, lT in self:
        if hasattr(lT, "_protected_predecessor"):
            del lT._protected_predecessor
        if hasattr(lT, "_protected_successor"):
            del lT._protected_successor
        if hasattr(lT, "_protected_time"):
            del lT._protected_time
    with open(fname, "bw") as f:
        pkl.dump(self, f)
        f.close()

read_from_ASTEC

read_from_ASTEC(
    file_path: str,
    eigen: bool = False,
    name: None | str = None,
) -> LineageTree

Read an xml or pkl file produced by the ASTEC algorithm.

Parameters:

Name Type Description Default

file_path

str

path to an output generated by ASTEC

required

eigen

bool

whether or not to read the eigen values, default False

False

name

None or str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_ASTEC(
    file_path: str, eigen: bool = False, name: None | str = None
) -> LineageTree:
    """
    Read an `xml` or `pkl` file produced by the ASTEC algorithm.

    Parameters
    ----------
    file_path : str
        path to an output generated by ASTEC
    eigen : bool, default=False
        whether or not to read the eigen values, default False
    name : None or str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """

    if os.path.splitext(file_path)[-1] == ".xml":
        tmp_data = _read_from_ASTEC_xml(file_path)
    else:
        tmp_data = _read_from_ASTEC_pkl(file_path, eigen)

    # make sure these are all named liked they are in tmp_data (or change dictionary above)
    properties = {}
    if "cell_volume" in tmp_data:
        properties["volume"] = {}
    if "cell_fate" in tmp_data:
        properties["fate"] = {}
    if "cell_barycenter" in tmp_data:
        pos = {}
    if "cell_name" in tmp_data:
        properties["label"] = {}
    lT2pkl = {}
    pkl2lT = {}
    image_label = {}

    lt = tmp_data["cell_lineage"]

    if "cell_contact_surface" in tmp_data:
        properties["contact"] = {}
        do_surf = True
        surfaces = tmp_data["cell_contact_surface"]
    else:
        do_surf = False

    inv = {vi: [c] for c, v in lt.items() for vi in v}
    nodes = set(lt).union(inv)

    unique_id = 0
    time = {}

    for unique_id, n in enumerate(nodes):
        t = n // 10**4
        image_label[unique_id] = n % 10**4
        lT2pkl[unique_id] = n
        pkl2lT[n] = unique_id
        time[unique_id] = t
        if "cell_volume" in tmp_data:
            properties["volume"][unique_id] = tmp_data["cell_volume"].get(
                n, 0.0
            )
        if "cell_fate" in tmp_data:
            properties["fate"][unique_id] = tmp_data["cell_fate"].get(n, "")
        if "cell_barycenter" in tmp_data:
            pos[unique_id] = tmp_data["cell_barycenter"].get(n, np.zeros(3))
        if "cell_name" in tmp_data:
            properties["label"][unique_id] = tmp_data["cell_name"].get(n, "")

    if do_surf:
        for c in nodes:
            if c in surfaces and c in pkl2lT:
                properties["contact"][pkl2lT[c]] = {
                    pkl2lT.get(n, -1): s
                    for n, s in surfaces[c].items()
                    if n % 10**4 == 1 or n in pkl2lT
                }

    successor = {}
    for n, new_id in pkl2lT.items():
        if n in lt:
            successor[new_id] = [pkl2lT[ni] for ni in lt[n] if ni in pkl2lT]

    # do this in the end of the process, skip lineage tree and whatever is stored already
    discard = {
        "cell_volume",  # already stored
        "cell_fate",  # already stored
        "cell_barycenter",  # already stored
        "cell_contact_surface",  # already stored
        "cell_lineage",  # already stored
        "cell_name",  # already stored
        "all_cells",  # not a property
        "cell_history",  # redundant
        "problematic_cells",  # not useful here
        "cell_labels_in_time",  # redundant
    }
    for prop_name, prop_values in tmp_data.items():
        if prop_name not in discard and isinstance(prop_values, dict):
            dictionary = {pkl2lT.get(k, -1): v for k, v in prop_values.items()}
            # is it a regular dictionary or a dictionary with dictionaries inside?
            for key, value in dictionary.items():
                if isinstance(value, dict):
                    # rename all ids from old to new
                    dictionary[key] = {
                        pkl2lT.get(k, -1): v for k, v in value.items()
                    }
            properties[prop_name] = dictionary
    if not name:
        tmp_name = Path(file_path).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name
    return LineageTree(
        successor=successor, time=time, pos=pos, name=name, **properties
    )

read_from_binary

read_from_binary(
    fname: str, name: None | str = None
) -> LineageTree

Reads a binary LineageTree file name. Format description: see LineageTree.to_binary

Parameters:

Name Type Description Default

fname

string

path to the binary file

required

name

None or str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_binary(fname: str, name: None | str = None) -> LineageTree:
    """
    Reads a binary LineageTree file name.
    Format description: see LineageTree.to_binary

    Parameters
    ----------
    fname : string
        path to the binary file
    name : None or str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """
    q_size = struct.calcsize("q")
    H_size = struct.calcsize("H")
    d_size = struct.calcsize("d")

    with open(fname, "rb") as f:
        len_tree = struct.unpack("q", f.read(q_size))[0]
        len_time = struct.unpack("q", f.read(q_size))[0]
        len_pos = struct.unpack("q", f.read(q_size))[0]
        number_sequence = list(
            struct.unpack("q" * len_tree, f.read(q_size * len_tree))
        )
        time_sequence = list(
            struct.unpack("H" * len_time, f.read(H_size * len_time))
        )
        pos_sequence = np.array(
            struct.unpack("d" * len_pos, f.read(d_size * len_pos))
        )

        f.close()

    successor = {}
    time = {}
    pos = {}
    is_root = {}
    waiting_list = []
    i = 0
    done = False
    t = 0
    if max(number_sequence[::2]) == -1:
        tmp = number_sequence[1::2]
        if len(tmp) * 3 == len(pos_sequence) == len(time_sequence) * 3:
            time = dict(list(zip(tmp, time_sequence, strict=True)))
            pos = dict(
                list(
                    zip(
                        tmp,
                        np.reshape(pos_sequence, (len_time, 3)),
                        strict=True,
                    )
                )
            )
            is_root = dict.fromkeys(tmp, True)
            done = True
    while (
        i < len(number_sequence) and not done
    ):  # , c in enumerate(number_sequence[:-1]):
        c = number_sequence[i]
        if c == -1:
            if waiting_list != []:
                prev_mother = waiting_list.pop()
                successor[prev_mother].insert(0, number_sequence[i + 1])
                t = time[prev_mother] + 1
            else:
                t = time_sequence.pop(0)

        elif c == -2:
            successor[waiting_list[-1]] = [number_sequence[i + 1]]
            is_root[number_sequence[i + 1]] = False
            pos[waiting_list[-1]] = pos_sequence[:3]
            pos_sequence = pos_sequence[3:]
            time[waiting_list[-1]] = t
            t += 1

        elif number_sequence[i + 1] >= 0:
            successor[c] = [number_sequence[i + 1]]
            pos[c] = pos_sequence[:3]
            pos_sequence = pos_sequence[3:]
            time[c] = t
            t += 1

        elif number_sequence[i + 1] == -2:
            waiting_list += [c]

        elif number_sequence[i + 1] == -1:
            pos[c] = pos_sequence[:3]
            pos_sequence = pos_sequence[3:]
            time[c] = t
            t += 1
            i += 1
            if waiting_list != []:
                prev_mother = waiting_list.pop()
                successor[prev_mother].insert(0, number_sequence[i + 1])
                t = time[prev_mother] + 1
            else:
                if len(time_sequence) > 0:
                    t = time_sequence.pop(0)
        i += 1
    if not name:
        tmp_name = Path(fname).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name
    return LineageTree(successor=successor, time=time, pos=pos, name=name)

read_from_bmf

read_from_bmf(
    file_path: str,
    store_meshes: bool = True,
    pos_multipliers: tuple[float, float, float] = (
        1.0,
        1.0,
        1.0,
    ),
    translation: tuple[float, float, float] = (
        0.0,
        0.0,
        0.0,
    ),
    name: None | str = None,
) -> LineageTree

Read a lineage tree from a bmf file.

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_bmf(
    file_path: str,
    store_meshes: bool = True,
    pos_multipliers: tuple[float, float, float] = (1.0, 1.0, 1.0),
    translation: tuple[float, float, float] = (0.0, 0.0, 0.0),
    name: None | str = None,
) -> LineageTree:
    """Read a lineage tree from a bmf file.

    Parameters
    ----------
        file_path : str
            path to the bmf file
        store_meshes : bool, default=False
            whether to stores the meshes in the LineageTree or not.
        pos_multipliers : tuple of float, default=(1.0, 1.0, 1.0)
            multipliers for the x, y, z coordinates
        translation : tuple of float, default=(0.0, 0.0, 0.0)
            translation for the x, y, z coordinates
        name : None or str, optional
           The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
           will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
        LineageTree
            lineage tree
    """
    tracks = bmf.loadMeshTracks(file_path)
    predecessor = {}
    times = {}
    pos = {}
    lT_mesh = {}
    cell_id = 1
    for track in tracks:
        pred = None
        for t, mesh in track.meshes.items():
            mesh = _load_meshdict_from_bmfmesh(
                mesh, pos_multipliers, translation
            )
            pos[cell_id] = mesh["center_mass"]

            if store_meshes:
                lT_mesh[cell_id] = mesh

            predecessor[cell_id] = (pred,)
            pred = cell_id
            times[cell_id] = t

            cell_id += 1

    kwargs = {"mesh": lT_mesh} if store_meshes else {}

    lT = LineageTree(
        predecessor=predecessor,
        time=times,
        pos=pos,
        root_leaf_value=[(None,)],
        name=name if name else Path(file_path).stem,
        **kwargs,
    )

    return lT

read_from_csv

read_from_csv(
    file_path: str,
    z_mult: float = 1,
    link: int = 1,
    delim: str = ",",
    name: None | str = None,
) -> LineageTree

Read a lineage tree from a csv file with the following format: id, time, z, y, x, id, pred_id, lin_id

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_csv(
    file_path: str,
    z_mult: float = 1,
    link: int = 1,
    delim: str = ",",
    name: None | str = None,
) -> LineageTree:
    """Read a lineage tree from a csv file with the following format:
    id, time, z, y, x, id, pred_id, lin_id

    Parameters
    ----------
        file_path : str
            path to the csv file
        z_mult : float
            aspect ratio
        link : int
            1 if the csv file is ordered by id, 2 if ordered by pred_id
        delim : str, default=","
            delimiter used in the csv file
        name : None or str, optional
           The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
           will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
        LineageTree
            lineage tree
    """
    with open(file_path) as f:
        lines = f.readlines()
        f.close()
    successor = {}
    pos = {}
    time = {}
    lines_to_int = []
    corres = {}
    for line in lines:
        lines_to_int += [[eval(v.strip()) for v in line.split(delim)]]
    lines_to_int = np.array(lines_to_int)
    if link == 2:
        lines_to_int = lines_to_int[np.argsort(lines_to_int[:, 0])]
    else:
        lines_to_int = lines_to_int[np.argsort(lines_to_int[:, 1])]
    for unique_id, line in enumerate(lines_to_int):
        if link == 1:
            id_, t, z, y, x, pred, lin_id = line
        elif link == 2:
            t, z, y, x, id_, pred, lin_id = line
        else:
            id_, t, z, y, x, *_ = line
            pred = None
        t = int(t)
        positions = np.array([x, y, z])
        C = unique_id
        corres[id_] = C
        positions[-1] = positions[-1] * z_mult
        if pred in corres:
            M = corres[pred]
            successor.setdefault(M, []).append(C)
        pos[C] = positions
        time[C] = t
    if not name:
        tmp_name = Path(file_path).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name
    return LineageTree(successor=successor, time=time, pos=pos, name=name)

read_from_mamut_xml

read_from_mamut_xml(
    path: str,
    xml_attributes: list[str] | None = None,
    name: None | str = None,
) -> LineageTree

Read a lineage tree from a MaMuT xml.

Parameters:

Name Type Description Default

path

str

path to the MaMut xml

required

name

None or str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_mamut_xml(
    path: str, xml_attributes: list[str] | None = None, name: None | str = None
) -> LineageTree:
    """Read a lineage tree from a MaMuT xml.

    Parameters
    ----------
    path : str
        path to the MaMut xml
    name : None or str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """
    tree = ET.parse(path)
    for elem in tree.getroot():
        if elem.tag == "Model":
            Model = elem
    FeatureDeclarations, AllSpots, AllTracks, FilteredTracks = list(Model)
    xml_attributes = xml_attributes or []

    properties = {}
    for attr in xml_attributes:
        properties[attr] = {}
    nodes = set()
    pos = {}
    time = {}
    properties["label"] = {}

    for frame in AllSpots:
        t = int(frame.attrib["frame"])
        for cell in frame:
            cell_id, n, x, y, z = (
                int(cell.attrib["ID"]),
                cell.attrib["name"],
                float(cell.attrib["POSITION_X"]),
                float(cell.attrib["POSITION_Y"]),
                float(cell.attrib["POSITION_Z"]),
            )
            nodes.add(cell_id)
            pos[cell_id] = np.array([x, y, z])
            time[cell_id] = t
            properties["label"][cell_id] = n
            if "TISSUE_NAME" in cell.attrib:
                if "fate" not in properties:
                    properties["fate"] = {}
                properties["fate"][cell_id] = cell.attrib["TISSUE_NAME"]
            if "TISSUE_TYPE" in cell.attrib:
                if "fate_nb" not in properties:
                    properties["fate_nb"] = {}
                properties["fate_nb"][cell_id] = eval(
                    cell.attrib["TISSUE_TYPE"]
                )
            for attr in cell.attrib:
                if attr in xml_attributes:
                    properties[attr][cell_id] = eval(cell.attrib[attr])

    properties["tracks"] = {}
    successor = {}
    properties["track_name"] = {}
    for track in AllTracks:
        if "TRACK_DURATION" in track.attrib:
            t_id, _ = (
                int(track.attrib["TRACK_ID"]),
                float(track.attrib["TRACK_DURATION"]),
            )
        else:
            t_id = int(track.attrib["TRACK_ID"])
        t_name = track.attrib["name"]
        properties["tracks"][t_id] = []
        for edge in track:
            s, t = (
                int(edge.attrib["SPOT_SOURCE_ID"]),
                int(edge.attrib["SPOT_TARGET_ID"]),
            )
            if s in nodes and t in nodes:
                if time[s] > time[t]:
                    s, t = t, s
                successor.setdefault(s, []).append(t)
                properties["track_name"][s] = t_name
                properties["track_name"][t] = t_name
                properties["tracks"][t_id].append((s, t))
    if not name:
        tmp_name = Path(path).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name

    return LineageTree(
        successor=successor,
        time=time,
        pos=pos,
        name=name,
        **properties,
    )

read_from_mastodon

read_from_mastodon(
    path: str,
    tag_set: str | None = None,
    name: str | None = None,
) -> LineageTree

Read a maston lineage tree.

Parameters:

Name Type Description Default

path

str

path to the mastodon file

required

tag_set

str

If specified, tag_set will be used for labeling the cells Otherwise a random tag set will be used

None

name

str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_mastodon(
    path: str, tag_set: str | None = None, name: str | None = None
) -> LineageTree:
    """Read a maston lineage tree.

    Parameters
    ----------
    path : str
        path to the mastodon file
    tag_set : str, optional
        If specified, `tag_set` will be used for labeling the cells
        Otherwise a random tag set will be used
    name : str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """

    mr = MicroMastodonReader(path)
    spots, links = mr.read_tables()

    nodes = list(range(len(spots)))
    pos = dict(zip(nodes, spots[:, :3], strict=True))
    time = dict(zip(nodes, spots[:, 3], strict=True))
    predecessor = {}
    labels, labels_name = {}, []

    for succ, pred in zip(links[:, 1], links[:, 0]):
        predecessor[int(succ)] = int(pred)

    _, properties, _ = mr.read_tags()

    if isinstance(tag_set, str) and tag_set in properties:
        labels = properties[tag_set]
    elif 0 < len(properties):
        labels_name, labels = next(iter(properties.items()))

    if not name:
        if isinstance(path, Path):
            tmp_name = path.stem
        else:
            tmp_name = Path(path).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name

    return LineageTree(
        predecessor=predecessor,
        time=time,
        pos=pos,
        labels=labels,
        labels_name=labels_name,
        name=name,
        **properties,
    )

read_from_mastodon_csv

read_from_mastodon_csv(
    paths: list[str], name: None | str = None
) -> LineageTree

Read a lineage tree from a mastodon csv.

Parameters:

Name Type Description Default

paths

list of strings

list of paths to the csv files

required

name

None or str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_mastodon_csv(
    paths: list[str], name: None | str = None
) -> LineageTree:
    """Read a lineage tree from a mastodon csv.

    Parameters
    ----------
    paths : list of strings
        list of paths to the csv files
    name : None or str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """
    spots = []
    links = []
    label = {}
    time = {}
    pos = {}
    successor = {}

    with open(paths[0], encoding="utf-8", errors="ignore") as file:
        csvreader = csv.reader(file)
        for row in csvreader:
            spots.append(row)
    spots = spots[3:]

    with open(paths[1], encoding="utf-8", errors="ignore") as file:
        csvreader = csv.reader(file)
        for row in csvreader:
            links.append(row)
    links = links[3:]

    for spot in spots:
        unique_id = int(spot[1])
        x, y, z = spot[5:8]
        t = int(spot[4])
        time[unique_id] = t
        label[unique_id] = spot[1]
        pos[unique_id] = np.array([x, y, z], dtype=float)

    for link in links:
        source = int(float(link[4]))
        target = int(float(link[5]))
        successor.setdefault(source, []).append(target)
    if not name:
        tmp_name = Path(paths[0]).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name

    return LineageTree(
        successor=successor, time=time, pos=pos, label=label, name=name
    )

read_from_tgmm_xml

read_from_tgmm_xml(
    file_format: str,
    tb: int,
    te: int,
    z_mult: float = 1.0,
    name: None | str = None,
) -> LineageTree

Reads a lineage tree from TGMM xml output.

Parameters:

Name Type Description Default

file_format

str

path to the xmls location. it should be written as follow: path/to/xml/standard_name_t{t:06d}.xml where (as an example) {t:06d} means a series of 6 digits representing the time and if the time values is smaller that 6 digits, the missing digits are filed with 0s

required

tb

int

first time point to read

required

te

int

last time point to read

required

z_mult

float

aspect ratio

1.0

name

str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_tgmm_xml(
    file_format: str,
    tb: int,
    te: int,
    z_mult: float = 1.0,
    name: None | str = None,
) -> LineageTree:
    """Reads a lineage tree from TGMM xml output.

    Parameters
    ----------
    file_format : str
        path to the xmls location.
        it should be written as follow:
        path/to/xml/standard_name_t{t:06d}.xml where (as an example)
        {t:06d} means a series of 6 digits representing the time and
        if the time values is smaller that 6 digits, the missing
        digits are filed with 0s
    tb : int
        first time point to read
    te : int
        last time point to read
    z_mult : float, default=1.0
        aspect ratio
    name : str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """
    unique_id = 0
    successor = {}
    pos = {}
    time_id = {}
    time = {}
    properties = {}
    properties["svIdx"] = {}
    properties["lin"] = {}
    properties["C_lin"] = {}
    properties["coeffs"] = {}
    properties["intensity"] = {}
    W = {}
    for t in range(tb, te + 1):
        tree = ET.parse(file_format.format(t=t))
        root = tree.getroot()
        for unique_id, it in enumerate(root):
            if "-1.#IND" not in it.attrib["m"] and "nan" not in it.attrib["m"]:
                M_id, positions, cell_id, svIdx, lin_id = (
                    int(it.attrib["parent"]),
                    [float(v) for v in it.attrib["m"].split(" ") if v != ""],
                    int(it.attrib["id"]),
                    [int(v) for v in it.attrib["svIdx"].split(" ") if v != ""],
                    int(it.attrib["lineage"]),
                )
                if (
                    "alpha" in it.attrib
                    and "W" in it.attrib
                    and "nu" in it.attrib
                    and "alphaPrior" in it.attrib
                ):
                    alpha, W, nu, alphaPrior = (
                        float(it.attrib["alpha"]),
                        [
                            float(v)
                            for v in it.attrib["W"].split(" ")
                            if v != ""
                        ],
                        float(it.attrib["nu"]),
                        float(it.attrib["alphaPrior"]),
                    )
                    positions = np.array(positions)
                    C = unique_id
                    positions[-1] = positions[-1] * z_mult
                    if (t - 1, M_id) in time_id:
                        M = time_id[(t - 1, M_id)]
                        successor.setdefault(M, []).append(C)
                    pos[C] = positions
                    time_id[(t, cell_id)] = C
                    time[C] = t
                    properties["svIdx"][C] = svIdx
                    properties["lin"].setdefault(lin_id, []).append(C)
                    properties["C_lin"][C] = lin_id
                    properties["intensity"][C] = max(alpha - alphaPrior, 0)
                    tmp = list(np.array(W) * nu)
                    W[C] = np.array(W).reshape(3, 3)
                    properties["coeffs"][C] = (
                        tmp[:3] + tmp[4:6] + tmp[8:9] + list(positions)
                    )
    if not name:
        tmp_name = Path(file_format).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name
    return LineageTree(
        successor=successor, time=time, pos=pos, name=name, **properties
    )

read_from_txt_for_celegans

read_from_txt_for_celegans(
    file: str, name: None | str = None
) -> LineageTree

Read a C. elegans lineage tree

Parameters:

Name Type Description Default

file

str

Path to the file to read

required

name

None or str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_txt_for_celegans(
    file: str, name: None | str = None
) -> LineageTree:
    """
    Read a C. elegans lineage tree

    Parameters
    ----------
    file : str
        Path to the file to read
    name : None or str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """
    with open(file) as f:
        raw = f.readlines()[1:]
        f.close()
    _labels = {}
    time_nodes = {}
    pos = {}
    time = {}
    successor = {}

    for unique_id, line in enumerate(raw):
        t = int(line.split("\t")[0])
        _labels[unique_id] = line.split("\t")[1]
        position = np.array(line.split("\t")[2:5], dtype=float)
        time_nodes.setdefault(t, set()).add(unique_id)
        pos[unique_id] = position
        time[unique_id] = t

    t_b = min(time_nodes)

    for t, cells in time_nodes.items():
        if t != t_b:
            prev_cells = time_nodes[t - 1]
            name_to_id = {_labels[c]: c for c in prev_cells}
            for c in cells:
                if _labels[c] in name_to_id:
                    p = name_to_id[_labels[c]]
                elif _labels[c][:-1] in name_to_id:
                    p = name_to_id[_labels[c][:-1]]
                elif IMPLICIT_L_T.get(_labels[c]) in name_to_id:
                    p = name_to_id[IMPLICIT_L_T.get(_labels[c])]
                else:
                    p = None
                successor.setdefault(p, []).append(c)
    if not name:
        tmp_name = Path(file).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name
    properties = {"_labels": _labels}
    return LineageTree(
        successor=successor, time=time, pos=pos, name=name, **properties
    )

read_from_txt_for_celegans_BAO

read_from_txt_for_celegans_BAO(
    path: str, name: None | str = None
) -> LineageTree

Read a C. elegans Bao file from http://digital-development.org

Parameters:

Name Type Description Default

file

str

Path to the file to read

required

name

str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_txt_for_celegans_BAO(
    path: str, name: None | str = None
) -> LineageTree:
    """Read a C. elegans Bao file from http://digital-development.org

    Parameters
    ----------
    file : str
        Path to the file to read
    name : str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """
    cell_times = {}
    properties = {}
    properties["expression"] = {}
    properties["_labels"] = {}
    with open(path) as f:
        for line in f:
            if "cell_name" not in line:
                cell_times[line.split("\t")[0]] = [
                    eval(val) for val in line.split("\t")[-1].split(",")
                ]
    unique_id = 0
    to_link = {}
    successor = {}
    for c, lc in cell_times.items():
        ids = list(range(unique_id, unique_id + len(lc)))
        successor.update({ids[i]: [ids[i + 1]] for i in range(len(ids) - 1)})
        properties["expression"].update(dict(zip(ids, lc, strict=True)))
        properties["_labels"].update(dict.fromkeys(ids, c))
        to_link[c] = (unique_id, unique_id + len(lc) - 1)
        unique_id += len(lc)

    for c_name, c_id in to_link.items():
        if c_name[:-1] in to_link:
            successor.setdefault(to_link[c_name[:-1]][1], []).append(c_id[0])
        elif c_name in IMPLICIT_L_T and IMPLICIT_L_T[c_name] in to_link:
            successor.setdefault(to_link[IMPLICIT_L_T[c_name]][1], []).append(
                c_id[0]
            )
    if not name:
        tmp_name = Path(path).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name
    return LineageTree(
        successor=successor, starting_time=0, name=name, **properties
    )

read_from_txt_for_celegans_CAO

read_from_txt_for_celegans_CAO(
    file: str,
    reorder: bool = False,
    raw_size: ndarray | None = None,
    shape: float | None = None,
    name: str | None = None,
) -> LineageTree

Read a C. elegans lineage tree from Cao et al.

Parameters:

Name Type Description Default

file

str

Path to the file to read

required

name

None or str

The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute will be the name attribute, otherwise the name will be the stem of the file path.

None

Returns:

Type Description
LineageTree

lineage tree

Source code in src/lineagetree/_io/_loaders.py
def read_from_txt_for_celegans_CAO(
    file: str,
    reorder: bool = False,
    raw_size: np.ndarray | None = None,
    shape: float | None = None,
    name: str | None = None,
) -> LineageTree:
    """
    Read a C. elegans lineage tree from Cao et al.

    Parameters
    ----------
    file : str
        Path to the file to read
    name : None or str, optional
        The name attribute of the LineageTree file. If given a non-empty string, the value of the attribute
        will be the name attribute, otherwise the name will be the stem of the file path.

    Returns
    -------
    LineageTree
        lineage tree
    """

    def split_line(line):
        return (
            line.split()[0],
            eval(line.split()[1]),
            eval(line.split()[2]),
            eval(line.split()[3]),
            eval(line.split()[4]),
        )

    with open(file) as f:
        raw = f.readlines()[1:]
        f.close()
    label = {}
    time_nodes = {}
    pos = {}
    successor = {}
    time = {}

    unique_id = 0
    for unique_id, (label, t, z, x, y) in enumerate(map(split_line, raw)):
        label[unique_id] = label
        position = np.array([x, y, z], dtype=np.float)
        time_nodes.setdefault(t, set()).add(unique_id)
        if reorder:

            def flip(x):
                return np.array([x[0], x[1], raw_size[2] - x[2]])

            def adjust(x):
                return (shape / raw_size * flip(x))[[1, 0, 2]]

            pos[unique_id] = adjust(position)
        else:
            pos[unique_id] = position
        time[unique_id] = t

    t_b = min(time_nodes)

    for t, cells in time_nodes.items():
        if t != t_b:
            prev_cells = time_nodes[t - 1]
            name_to_id = {label[c]: c for c in prev_cells}
            for c in cells:
                if label[c] in name_to_id:
                    p = name_to_id[label[c]]
                elif label[c][:-1] in name_to_id:
                    p = name_to_id[label[c][:-1]]
                elif IMPLICIT_L_T.get(label[c]) in name_to_id:
                    p = name_to_id[IMPLICIT_L_T.get(label[c])]
                else:
                    warn(
                        f"error, cell {label[c]} has no predecessors",
                        stacklevel=2,
                    )
                    p = None
                successor.setdefault(p, []).append(c)
    if not name:
        tmp_name = Path(file).stem
        if name == "":
            warn(f"Name set to default {tmp_name}", stacklevel=2)
        name = tmp_name
    return LineageTree(
        successor=successor, time=time, pos=pos, label=label, name=name
    )