LineageTree 1.6.1__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- LineageTree/__init__.py +1 -1
- LineageTree/lineageTree.py +194 -68
- LineageTree/lineageTreeManager.py +24 -22
- LineageTree/loaders.py +69 -1
- LineageTree/tree_styles.py +87 -66
- LineageTree/utils.py +0 -2
- {LineageTree-1.6.1.dist-info → LineageTree-1.7.0.dist-info}/METADATA +10 -10
- LineageTree-1.7.0.dist-info/RECORD +11 -0
- {LineageTree-1.6.1.dist-info → LineageTree-1.7.0.dist-info}/WHEEL +1 -1
- LineageTree-1.6.1.dist-info/RECORD +0 -11
- {LineageTree-1.6.1.dist-info → LineageTree-1.7.0.dist-info}/LICENSE +0 -0
- {LineageTree-1.6.1.dist-info → LineageTree-1.7.0.dist-info}/top_level.txt +0 -0
LineageTree/__init__.py
CHANGED
LineageTree/lineageTree.py
CHANGED
@@ -20,7 +20,8 @@ try:
|
|
20
20
|
from edist import uted
|
21
21
|
except ImportError:
|
22
22
|
warnings.warn(
|
23
|
-
"No edist installed therefore you will not be able to compute the tree edit distance."
|
23
|
+
"No edist installed therefore you will not be able to compute the tree edit distance.",
|
24
|
+
stacklevel=2,
|
24
25
|
)
|
25
26
|
import matplotlib.pyplot as plt
|
26
27
|
import numpy as np
|
@@ -88,7 +89,7 @@ class lineageTree(lineageTreeLoaders):
|
|
88
89
|
length (int): The length of the new branch.
|
89
90
|
pos (np.ndarray, optional): The new position of the branch. Defaults to None.
|
90
91
|
move_timepoints (bool): Moves the ti Important only if reverse= True
|
91
|
-
reverese (bool): If
|
92
|
+
reverese (bool): If True will create a branch that goes forwards in time otherwise backwards.
|
92
93
|
Returns:
|
93
94
|
(int): Id of the first node of the sublineage.
|
94
95
|
"""
|
@@ -129,9 +130,12 @@ class lineageTree(lineageTreeLoaders):
|
|
129
130
|
)
|
130
131
|
pred = _next
|
131
132
|
else:
|
132
|
-
for
|
133
|
+
for _ in range(length):
|
133
134
|
_next = self.add_node(
|
134
|
-
time +
|
135
|
+
self.time[pred] + 1,
|
136
|
+
succ=pred,
|
137
|
+
pos=self.pos[original],
|
138
|
+
reverse=False,
|
135
139
|
)
|
136
140
|
pred = _next
|
137
141
|
self.labels[pred] = "New branch"
|
@@ -325,7 +329,10 @@ class lineageTree(lineageTreeLoaders):
|
|
325
329
|
if length == 1 and new_length != 1:
|
326
330
|
pred = self.predecessor.pop(node, None)
|
327
331
|
new_node = self.add_branch(
|
328
|
-
node,
|
332
|
+
node,
|
333
|
+
length=new_length - 1,
|
334
|
+
move_timepoints=True,
|
335
|
+
reverse=False,
|
329
336
|
)
|
330
337
|
if pred:
|
331
338
|
self.successor[pred[0]].remove(node)
|
@@ -365,6 +372,20 @@ class lineageTree(lineageTreeLoaders):
|
|
365
372
|
else:
|
366
373
|
return None
|
367
374
|
|
375
|
+
@property
|
376
|
+
def time_resolution(self):
|
377
|
+
if not hasattr(self, "_time_resolution"):
|
378
|
+
self.time_resolution = 1
|
379
|
+
return self._time_resolution / 10
|
380
|
+
|
381
|
+
@time_resolution.setter
|
382
|
+
def time_resolution(self, time_resolution):
|
383
|
+
if time_resolution is not None:
|
384
|
+
self._time_resolution = int(time_resolution * 10)
|
385
|
+
else:
|
386
|
+
warnings.warn("Time resolution set to default 1", stacklevel=2)
|
387
|
+
self._time_resolution = 10
|
388
|
+
|
368
389
|
@property
|
369
390
|
def roots(self):
|
370
391
|
if not hasattr(self, "_roots"):
|
@@ -1178,6 +1199,8 @@ class lineageTree(lineageTreeLoaders):
|
|
1178
1199
|
with open(fname, "br") as f:
|
1179
1200
|
lT = pkl.load(f)
|
1180
1201
|
f.close()
|
1202
|
+
if not hasattr(lT, "time_resolution"):
|
1203
|
+
lT.time_resolution = None
|
1181
1204
|
if rm_empty_lists:
|
1182
1205
|
if [] in lT.successor.values():
|
1183
1206
|
for node, succ in lT.successor.items():
|
@@ -1392,8 +1415,10 @@ class lineageTree(lineageTreeLoaders):
|
|
1392
1415
|
while to_do:
|
1393
1416
|
current = to_do.pop()
|
1394
1417
|
track = self.get_successors(current, end_time=end_time)
|
1395
|
-
|
1396
|
-
|
1418
|
+
# if len(track) != 1 or self.time[current] <= end_time:
|
1419
|
+
if self.time[track[-1]] <= end_time:
|
1420
|
+
branches += [track]
|
1421
|
+
to_do += self[track[-1]]
|
1397
1422
|
return branches
|
1398
1423
|
|
1399
1424
|
def get_all_tracks(self, force_recompute: bool = False) -> list:
|
@@ -1629,6 +1654,8 @@ class lineageTree(lineageTreeLoaders):
|
|
1629
1654
|
at a given time `time`.
|
1630
1655
|
|
1631
1656
|
If there is no ancestor, returns `-1`
|
1657
|
+
If time is None return the root of the sub tree that spawns
|
1658
|
+
the node n.
|
1632
1659
|
|
1633
1660
|
Args:
|
1634
1661
|
n (int): node for which to look the ancestor
|
@@ -1645,7 +1672,9 @@ class lineageTree(lineageTreeLoaders):
|
|
1645
1672
|
if time is None:
|
1646
1673
|
time = self.t_b
|
1647
1674
|
ancestor = n
|
1648
|
-
while
|
1675
|
+
while (
|
1676
|
+
time < self.time.get(ancestor, -1) and ancestor in self.predecessor
|
1677
|
+
):
|
1649
1678
|
ancestor = self.predecessor.get(ancestor, [-1])[0]
|
1650
1679
|
return ancestor
|
1651
1680
|
|
@@ -1674,10 +1703,11 @@ class lineageTree(lineageTreeLoaders):
|
|
1674
1703
|
def unordered_tree_edit_distances_at_time_t(
|
1675
1704
|
self,
|
1676
1705
|
t: int,
|
1677
|
-
delta: callable = None,
|
1678
|
-
norm: callable = None,
|
1679
|
-
recompute: bool = False,
|
1680
1706
|
end_time: int = None,
|
1707
|
+
style="simple",
|
1708
|
+
downsample: int = 2,
|
1709
|
+
normalize: bool = True,
|
1710
|
+
recompute: bool = False,
|
1681
1711
|
) -> dict:
|
1682
1712
|
"""
|
1683
1713
|
Compute all the pairwise unordered tree edit distances from Zhang 996 between the trees spawned at time `t`
|
@@ -1704,7 +1734,12 @@ class lineageTree(lineageTreeLoaders):
|
|
1704
1734
|
for n1, n2 in combinations(roots, 2):
|
1705
1735
|
key = tuple(sorted((n1, n2)))
|
1706
1736
|
self.uted[t][key] = self.unordered_tree_edit_distance(
|
1707
|
-
n1,
|
1737
|
+
n1,
|
1738
|
+
n2,
|
1739
|
+
end_time=end_time,
|
1740
|
+
style=style,
|
1741
|
+
downsample=downsample,
|
1742
|
+
normalize=normalize,
|
1708
1743
|
)
|
1709
1744
|
return self.uted[t]
|
1710
1745
|
|
@@ -1713,11 +1748,11 @@ class lineageTree(lineageTreeLoaders):
|
|
1713
1748
|
n1: int,
|
1714
1749
|
n2: int,
|
1715
1750
|
end_time: int = None,
|
1716
|
-
|
1717
|
-
|
1751
|
+
norm: Union["max", "sum", None] = "max",
|
1752
|
+
style="simple",
|
1753
|
+
downsample: int = 2,
|
1718
1754
|
) -> float:
|
1719
1755
|
"""
|
1720
|
-
TODO: Add option for choosing which tree aproximation should be used (Full, simple, comp)
|
1721
1756
|
Compute the unordered tree edit distance from Zhang 1996 between the trees spawned
|
1722
1757
|
by two nodes `n1` and `n2`. The topology of the trees are compared and the matching
|
1723
1758
|
cost is given by the function delta (see edist doc for more information).
|
@@ -1736,10 +1771,18 @@ class lineageTree(lineageTreeLoaders):
|
|
1736
1771
|
|
1737
1772
|
tree = tree_style[style].value
|
1738
1773
|
tree1 = tree(
|
1739
|
-
lT=self,
|
1774
|
+
lT=self,
|
1775
|
+
downsample=downsample,
|
1776
|
+
end_time=end_time,
|
1777
|
+
root=n1,
|
1778
|
+
time_scale=1,
|
1740
1779
|
)
|
1741
1780
|
tree2 = tree(
|
1742
|
-
lT=self,
|
1781
|
+
lT=self,
|
1782
|
+
downsample=downsample,
|
1783
|
+
end_time=end_time,
|
1784
|
+
root=n2,
|
1785
|
+
time_scale=1,
|
1743
1786
|
)
|
1744
1787
|
delta = tree1.delta
|
1745
1788
|
_, times1 = tree1.tree
|
@@ -1763,68 +1806,138 @@ class lineageTree(lineageTreeLoaders):
|
|
1763
1806
|
times1=times1,
|
1764
1807
|
times2=times2,
|
1765
1808
|
)
|
1809
|
+
norm1 = tree1.get_norm()
|
1810
|
+
norm2 = tree2.get_norm()
|
1811
|
+
norm_dict = {"max": max, "sum": sum, "None": lambda x: 1}
|
1812
|
+
if norm is None:
|
1813
|
+
norm = "None"
|
1814
|
+
if norm not in norm_dict:
|
1815
|
+
raise Warning(
|
1816
|
+
"Select a viable normalization method (max, sum, None)"
|
1817
|
+
)
|
1818
|
+
return uted.uted(
|
1819
|
+
nodes1, adj1, nodes2, adj2, delta=delta_tmp
|
1820
|
+
) / norm_dict[norm]([norm1, norm2])
|
1766
1821
|
|
1767
|
-
|
1768
|
-
|
1822
|
+
@staticmethod
|
1823
|
+
def __plot_nodes(
|
1824
|
+
hier, selected_nodes, color, size, ax, default_color="black", **kwargs
|
1825
|
+
):
|
1826
|
+
"""
|
1827
|
+
Private method that plots the nodes of the tree.
|
1828
|
+
"""
|
1829
|
+
hier_unselected = np.array(
|
1830
|
+
[v for k, v in hier.items() if k not in selected_nodes]
|
1769
1831
|
)
|
1832
|
+
if hier_unselected.any():
|
1833
|
+
ax.scatter(
|
1834
|
+
*hier_unselected.T,
|
1835
|
+
s=size,
|
1836
|
+
zorder=10,
|
1837
|
+
color=default_color,
|
1838
|
+
**kwargs,
|
1839
|
+
)
|
1840
|
+
if selected_nodes.intersection(hier.keys()):
|
1841
|
+
hier_selected = np.array(
|
1842
|
+
[v for k, v in hier.items() if k in selected_nodes]
|
1843
|
+
)
|
1844
|
+
ax.scatter(
|
1845
|
+
*hier_selected.T, s=size, zorder=10, color=color, **kwargs
|
1846
|
+
)
|
1847
|
+
|
1848
|
+
@staticmethod
|
1849
|
+
def __plot_edges(
|
1850
|
+
hier,
|
1851
|
+
lnks_tms,
|
1852
|
+
selected_edges,
|
1853
|
+
color,
|
1854
|
+
ax,
|
1855
|
+
default_color="black",
|
1856
|
+
**kwargs,
|
1857
|
+
):
|
1858
|
+
"""
|
1859
|
+
Private method that plots the edges of the tree.
|
1860
|
+
"""
|
1861
|
+
x, y = [], []
|
1862
|
+
for pred, succs in lnks_tms["links"].items():
|
1863
|
+
for succ in succs:
|
1864
|
+
if pred not in selected_edges or succ not in selected_edges:
|
1865
|
+
x.extend((hier[succ][0], hier[pred][0], None))
|
1866
|
+
y.extend((hier[succ][1], hier[pred][1], None))
|
1867
|
+
ax.plot(x, y, linewidth=0.3, zorder=0.1, c=default_color, **kwargs)
|
1868
|
+
x, y = [], []
|
1869
|
+
for pred, succs in lnks_tms["links"].items():
|
1870
|
+
for succ in succs:
|
1871
|
+
if pred in selected_edges and succ in selected_edges:
|
1872
|
+
x.extend((hier[succ][0], hier[pred][0], None))
|
1873
|
+
y.extend((hier[succ][1], hier[pred][1], None))
|
1874
|
+
ax.plot(x, y, linewidth=0.3, zorder=0.2, c=color, **kwargs)
|
1770
1875
|
|
1771
1876
|
def draw_tree_graph(
|
1772
1877
|
self,
|
1773
1878
|
hier,
|
1774
1879
|
lnks_tms,
|
1775
|
-
|
1776
|
-
|
1880
|
+
selected_nodes=None,
|
1881
|
+
selected_edges=None,
|
1882
|
+
color_of_nodes="magenta",
|
1883
|
+
color_of_edges=None,
|
1884
|
+
size=10,
|
1777
1885
|
ax=None,
|
1778
1886
|
figure=None,
|
1887
|
+
default_color="black",
|
1779
1888
|
**kwargs,
|
1780
1889
|
):
|
1781
|
-
|
1782
|
-
|
1890
|
+
"""Function to plot the tree graph.
|
1891
|
+
|
1892
|
+
Args:
|
1893
|
+
hier (dict): Dictinary that contains the positions of all nodes.
|
1894
|
+
lnks_tms (dict): 2 dictionaries: 1 contains all links from start of life cycle to end of life cycle and
|
1895
|
+
the succesors of each cell.
|
1896
|
+
1 contains the length of each life cycle.
|
1897
|
+
selected_nodes (list|set, optional): Which cells are to be selected (Painted with a different color). Defaults to None.
|
1898
|
+
selected_edges (list|set, optional): Which edges are to be selected (Painted with a different color). Defaults to None.
|
1899
|
+
color_of_nodes (str, optional): Color of selected nodes. Defaults to "magenta".
|
1900
|
+
color_of_edges (_type_, optional): Color of selected edges. Defaults to None.
|
1901
|
+
size (int, optional): Size of the nodes. Defaults to 10.
|
1902
|
+
ax (_type_, optional): Plot the graph on existing ax. Defaults to None.
|
1903
|
+
figure (_type_, optional): _description_. Defaults to None.
|
1904
|
+
default_color (str, optional): Default color of nodes. Defaults to "black".
|
1905
|
+
|
1906
|
+
Returns:
|
1907
|
+
figure, ax: The matplotlib figure and ax object.
|
1908
|
+
"""
|
1909
|
+
if selected_nodes is None:
|
1910
|
+
selected_nodes = []
|
1911
|
+
if selected_edges is None:
|
1912
|
+
selected_edges = []
|
1783
1913
|
if ax is None:
|
1784
1914
|
figure, ax = plt.subplots()
|
1785
1915
|
else:
|
1786
1916
|
ax.clear()
|
1787
|
-
if not isinstance(
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
selected = np.array(tuple(hier_selected.values()))
|
1812
|
-
x = []
|
1813
|
-
y = []
|
1814
|
-
for pred, succs in lnks_tms["links"].items():
|
1815
|
-
if pred in selected_cells:
|
1816
|
-
for succ in succs:
|
1817
|
-
x.extend((hier[succ][0], hier[pred][0], None))
|
1818
|
-
y.extend((hier[succ][1], hier[pred][1], None))
|
1819
|
-
ax.plot(x, y, c=color, linewidth=0.3, zorder=0.4, **kwargs)
|
1820
|
-
ax.scatter(
|
1821
|
-
selected.T[0],
|
1822
|
-
selected.T[1],
|
1823
|
-
s=0.1,
|
1824
|
-
c=color,
|
1825
|
-
zorder=0.9,
|
1826
|
-
**kwargs,
|
1827
|
-
)
|
1917
|
+
if not isinstance(selected_nodes, set):
|
1918
|
+
selected_nodes = set(selected_nodes)
|
1919
|
+
if not isinstance(selected_edges, set):
|
1920
|
+
selected_edges = set(selected_edges)
|
1921
|
+
self.__plot_nodes(
|
1922
|
+
hier,
|
1923
|
+
selected_nodes,
|
1924
|
+
color_of_nodes,
|
1925
|
+
size=size,
|
1926
|
+
ax=ax,
|
1927
|
+
default_color=default_color,
|
1928
|
+
**kwargs,
|
1929
|
+
)
|
1930
|
+
if not color_of_edges:
|
1931
|
+
color_of_edges = color_of_nodes
|
1932
|
+
self.__plot_edges(
|
1933
|
+
hier,
|
1934
|
+
lnks_tms,
|
1935
|
+
selected_edges,
|
1936
|
+
color_of_edges,
|
1937
|
+
ax,
|
1938
|
+
default_color=default_color,
|
1939
|
+
**kwargs,
|
1940
|
+
)
|
1828
1941
|
ax.get_yaxis().set_visible(False)
|
1829
1942
|
ax.get_xaxis().set_visible(False)
|
1830
1943
|
return figure, ax
|
@@ -1961,6 +2074,7 @@ class lineageTree(lineageTreeLoaders):
|
|
1961
2074
|
),
|
1962
2075
|
lnks_tms=graph,
|
1963
2076
|
ax=ax,
|
2077
|
+
**kwargs,
|
1964
2078
|
)
|
1965
2079
|
return figure, ax
|
1966
2080
|
|
@@ -2636,6 +2750,7 @@ class lineageTree(lineageTreeLoaders):
|
|
2636
2750
|
reorder: bool = False,
|
2637
2751
|
xml_attributes: tuple = None,
|
2638
2752
|
name: str = None,
|
2753
|
+
time_resolution: Union[int, None] = None,
|
2639
2754
|
):
|
2640
2755
|
"""
|
2641
2756
|
TODO: complete the doc
|
@@ -2646,13 +2761,22 @@ class lineageTree(lineageTreeLoaders):
|
|
2646
2761
|
file_format (str): either - path format to TGMM xmls
|
2647
2762
|
- path to the MaMuT xml
|
2648
2763
|
- path to the binary file
|
2649
|
-
tb (int):
|
2650
|
-
te (int): last time point (necessary for TGMM xmls only)
|
2651
|
-
z_mult (float):
|
2652
|
-
file_type (str):
|
2764
|
+
tb (int, optional):first time point (necessary for TGMM xmls only)
|
2765
|
+
te (int, optional): last time point (necessary for TGMM xmls only)
|
2766
|
+
z_mult (float, optional):z aspect ratio if necessary (usually only for TGMM xmls)
|
2767
|
+
file_type (str, optional):type of input file. Accepts:
|
2653
2768
|
'TGMM, 'ASTEC', MaMuT', 'TrackMate', 'csv', 'celegans', 'binary'
|
2654
2769
|
default is 'binary'
|
2770
|
+
delim (str, optional): _description_. Defaults to ",".
|
2771
|
+
eigen (bool, optional): _description_. Defaults to False.
|
2772
|
+
shape (tuple, optional): _description_. Defaults to None.
|
2773
|
+
raw_size (tuple, optional): _description_. Defaults to None.
|
2774
|
+
reorder (bool, optional): _description_. Defaults to False.
|
2775
|
+
xml_attributes (tuple, optional): _description_. Defaults to None.
|
2776
|
+
name (str, optional): The name of the dataset. Defaults to None.
|
2777
|
+
time_resolution (Union[int, None], optional): Time resolution in mins (If time resolution is smaller than one minute input the time in ms). Defaults to None.
|
2655
2778
|
"""
|
2779
|
+
|
2656
2780
|
self.name = name
|
2657
2781
|
self.time_nodes = {}
|
2658
2782
|
self.time_edges = {}
|
@@ -2664,6 +2788,8 @@ class lineageTree(lineageTreeLoaders):
|
|
2664
2788
|
self.pos = {}
|
2665
2789
|
self.time_id = {}
|
2666
2790
|
self.time = {}
|
2791
|
+
if time_resolution is not None:
|
2792
|
+
self._time_resolution = time_resolution
|
2667
2793
|
self.kdtrees = {}
|
2668
2794
|
self.spatial_density = {}
|
2669
2795
|
if file_type and file_format:
|
@@ -2,12 +2,14 @@ import os
|
|
2
2
|
import pickle as pkl
|
3
3
|
import warnings
|
4
4
|
from functools import partial
|
5
|
+
import numpy as np
|
5
6
|
|
6
7
|
try:
|
7
8
|
from edist import uted
|
8
9
|
except ImportError:
|
9
10
|
warnings.warn(
|
10
|
-
"No edist installed therefore you will not be able to compute the tree edit distance."
|
11
|
+
"No edist installed therefore you will not be able to compute the tree edit distance.",
|
12
|
+
stacklevel=2,
|
11
13
|
)
|
12
14
|
from LineageTree import lineageTree
|
13
15
|
|
@@ -17,14 +19,24 @@ from .tree_styles import tree_style
|
|
17
19
|
class lineageTreeManager:
|
18
20
|
def __init__(self):
|
19
21
|
self.lineagetrees = {}
|
20
|
-
# self.classification = {"Wt": {}, "Ptb": {}}
|
21
22
|
self.lineageTree_counter = 0
|
22
23
|
self.registered = {}
|
24
|
+
self.greatest_common_divisors = {}
|
23
25
|
|
24
26
|
def __next__(self):
|
25
27
|
self.lineageTree_counter += 1
|
26
28
|
return self.lineageTree_counter - 1
|
27
29
|
|
30
|
+
def gcd_update(self, new_embryo):
|
31
|
+
if len(self.lineagetrees) > 1:
|
32
|
+
for lineagetree in self.lineagetrees:
|
33
|
+
self.greatest_common_divisors[lineagetree, new_embryo.name] = (
|
34
|
+
np.gcd(
|
35
|
+
self.lineagetrees[lineagetree].time_resolution,
|
36
|
+
new_embryo.time_resolution,
|
37
|
+
)
|
38
|
+
)
|
39
|
+
|
28
40
|
def add(
|
29
41
|
self, other_tree: lineageTree, name: str = "", classification: str = ""
|
30
42
|
):
|
@@ -38,7 +50,7 @@ class lineageTreeManager:
|
|
38
50
|
name (str, optional): Then name of. Defaults to "".
|
39
51
|
|
40
52
|
"""
|
41
|
-
if isinstance(other_tree, lineageTree):
|
53
|
+
if isinstance(other_tree, lineageTree) and other_tree.time_resolution:
|
42
54
|
for tree in self.lineagetrees.values():
|
43
55
|
if tree == other_tree:
|
44
56
|
return False
|
@@ -52,25 +64,15 @@ class lineageTreeManager:
|
|
52
64
|
name = f"Lineagetree {next(self)}"
|
53
65
|
self.lineagetrees[name] = other_tree
|
54
66
|
self.lineagetrees[name].name = name
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
# f"Lineagetree {next(self)}"
|
61
|
-
# ] = other_tree
|
62
|
-
# if classification in ("Wt", "Ptb"):
|
63
|
-
# self.classification[type] = {name: other_tree}
|
67
|
+
# self.greatest_common_divisors[name] = gcd
|
68
|
+
else:
|
69
|
+
raise Exception(
|
70
|
+
"Please add a LineageTree object or add time resolution to the LineageTree added."
|
71
|
+
)
|
64
72
|
|
65
73
|
def __add__(self, other):
|
66
74
|
self.add(other)
|
67
75
|
|
68
|
-
# def classify_existing(self, key, classification: str):
|
69
|
-
# if classification in ("Wt", "Ptb"):
|
70
|
-
# self.classification[classification] = {key: self.lineagetrees[key]}
|
71
|
-
# else:
|
72
|
-
# return False
|
73
|
-
|
74
76
|
def write(self, fname: str):
|
75
77
|
"""Saves the manager
|
76
78
|
|
@@ -119,8 +121,8 @@ class lineageTreeManager:
|
|
119
121
|
embryo_2,
|
120
122
|
end_time2: int,
|
121
123
|
style="fragmented",
|
122
|
-
|
123
|
-
registration=None,
|
124
|
+
downsample: int = 2,
|
125
|
+
registration=None, # will be added as a later feature
|
124
126
|
):
|
125
127
|
"""Compute the unordered tree edit distance from Zhang 1996 between the trees spawned
|
126
128
|
by two nodes `n1` from lineagetree1 and `n2` lineagetree2. The topology of the trees
|
@@ -141,13 +143,13 @@ class lineageTreeManager:
|
|
141
143
|
tree = tree_style[style].value
|
142
144
|
tree1 = tree(
|
143
145
|
lT=self.lineagetrees[embryo_1],
|
144
|
-
|
146
|
+
downsample=downsample,
|
145
147
|
end_time=end_time1,
|
146
148
|
root=n1,
|
147
149
|
)
|
148
150
|
tree2 = tree(
|
149
151
|
lT=self.lineagetrees[embryo_2],
|
150
|
-
|
152
|
+
downsample=downsample,
|
151
153
|
end_time=end_time2,
|
152
154
|
root=n2,
|
153
155
|
)
|
LineageTree/loaders.py
CHANGED
@@ -1,11 +1,79 @@
|
|
1
1
|
import csv
|
2
|
+
import os
|
2
3
|
import pickle as pkl
|
3
4
|
import xml.etree.ElementTree as ET
|
4
|
-
|
5
|
+
|
5
6
|
import numpy as np
|
6
7
|
|
7
8
|
|
8
9
|
class lineageTreeLoaders:
|
10
|
+
|
11
|
+
def read_from_csv(
|
12
|
+
self, file_path: str, z_mult: float, link: int = 1, delim: str = ","
|
13
|
+
):
|
14
|
+
"""
|
15
|
+
TODO: write doc
|
16
|
+
"""
|
17
|
+
with open(file_path) as f:
|
18
|
+
lines = f.readlines()
|
19
|
+
f.close()
|
20
|
+
self.time_nodes = {}
|
21
|
+
self.time_edges = {}
|
22
|
+
unique_id = 0
|
23
|
+
self.nodes = set()
|
24
|
+
self.edges = set()
|
25
|
+
self.successor = {}
|
26
|
+
self.predecessor = {}
|
27
|
+
self.pos = {}
|
28
|
+
self.time_id = {}
|
29
|
+
self.time = {}
|
30
|
+
self.lin = {}
|
31
|
+
self.C_lin = {}
|
32
|
+
if not link:
|
33
|
+
self.displacement = {}
|
34
|
+
lines_to_int = []
|
35
|
+
corres = {}
|
36
|
+
for line in lines:
|
37
|
+
lines_to_int += [[eval(v.strip()) for v in line.split(delim)]]
|
38
|
+
lines_to_int = np.array(lines_to_int)
|
39
|
+
if link == 2:
|
40
|
+
lines_to_int = lines_to_int[np.argsort(lines_to_int[:, 0])]
|
41
|
+
else:
|
42
|
+
lines_to_int = lines_to_int[np.argsort(lines_to_int[:, 1])]
|
43
|
+
for line in lines_to_int:
|
44
|
+
if link == 1:
|
45
|
+
id_, t, z, y, x, pred, lin_id = line
|
46
|
+
elif link == 2:
|
47
|
+
t, z, y, x, id_, pred, lin_id = line
|
48
|
+
else:
|
49
|
+
id_, t, z, y, x, dz, dy, dx = line
|
50
|
+
pred = None
|
51
|
+
lin_id = None
|
52
|
+
t = int(t)
|
53
|
+
pos = np.array([x, y, z])
|
54
|
+
C = unique_id
|
55
|
+
corres[id_] = C
|
56
|
+
pos[-1] = pos[-1] * z_mult
|
57
|
+
if pred in corres:
|
58
|
+
M = corres[pred]
|
59
|
+
self.predecessor[C] = [M]
|
60
|
+
self.successor.setdefault(M, []).append(C)
|
61
|
+
self.edges.add((M, C))
|
62
|
+
self.time_edges.setdefault(t, set()).add((M, C))
|
63
|
+
self.lin.setdefault(lin_id, []).append(C)
|
64
|
+
self.C_lin[C] = lin_id
|
65
|
+
self.pos[C] = pos
|
66
|
+
self.nodes.add(C)
|
67
|
+
self.time_nodes.setdefault(t, set()).add(C)
|
68
|
+
# self.time_id[(t, cell_id)] = C
|
69
|
+
self.time[C] = t
|
70
|
+
if not link:
|
71
|
+
self.displacement[C] = np.array([dx, dy, dz * z_mult])
|
72
|
+
unique_id += 1
|
73
|
+
self.max_id = unique_id - 1
|
74
|
+
self.t_b = min(self.time_nodes)
|
75
|
+
self.t_e = max(self.time_nodes)
|
76
|
+
|
9
77
|
def read_from_ASTEC(self, file_path: str, eigen: bool = False):
|
10
78
|
"""
|
11
79
|
Read an `xml` or `pkl` file produced by the ASTEC algorithm.
|
LineageTree/tree_styles.py
CHANGED
@@ -7,14 +7,30 @@ from LineageTree import lineageTree
|
|
7
7
|
|
8
8
|
|
9
9
|
class abstract_trees(ABC):
|
10
|
+
"""Template class to produce different techniques to comapare lineageTrees.
|
11
|
+
To add a new technique you need to iherit this class or one of its children
|
12
|
+
and add them to the tree_style enum.
|
13
|
+
For a class to be valid you need a
|
14
|
+
- tree constructor (get_tree) that produces one dictionary that contains
|
15
|
+
arbitary unique labels and one dictionary that contains the duration of each node.
|
16
|
+
- delta function: A function that handles the cost of comparing nodes to each other.
|
17
|
+
- normalization function, a function that returns the length of the tree or any interger.
|
18
|
+
"""
|
19
|
+
|
10
20
|
def __init__(
|
11
|
-
self,
|
21
|
+
self,
|
22
|
+
lT: lineageTree,
|
23
|
+
root: int,
|
24
|
+
downsample: int,
|
25
|
+
end_time: int = None,
|
26
|
+
time_scale: int = None,
|
12
27
|
):
|
13
|
-
self.lT = lT
|
14
|
-
self.root = root
|
15
|
-
self.
|
16
|
-
self.end_time = end_time
|
17
|
-
self.
|
28
|
+
self.lT: lineageTree = lT
|
29
|
+
self.root: int = root
|
30
|
+
self.downsample: int = downsample
|
31
|
+
self.end_time: int = end_time if end_time else self.lT.t_e
|
32
|
+
self.time_scale: int = time_scale if time_scale is not None else 1
|
33
|
+
self.tree: tuple = self.get_tree()
|
18
34
|
self.edist = self._edist_format(self.tree[0])
|
19
35
|
|
20
36
|
@abstractmethod
|
@@ -124,7 +140,7 @@ class mini_tree(abstract_trees):
|
|
124
140
|
cycle = cycle[cycle_times <= self.end_time]
|
125
141
|
if cycle.size:
|
126
142
|
_next = self.lT[cycle[-1]]
|
127
|
-
if len(_next)
|
143
|
+
if 1 < len(_next):
|
128
144
|
out_dict[current] = _next
|
129
145
|
to_do.extend(_next)
|
130
146
|
else:
|
@@ -177,86 +193,76 @@ class simple_tree(abstract_trees):
|
|
177
193
|
to_do.extend(_next)
|
178
194
|
else:
|
179
195
|
out_dict[current] = []
|
180
|
-
self.times[current] = len(
|
181
|
-
cycle
|
182
|
-
) # * time_resolution will be fixed when working on registered trees.
|
196
|
+
self.times[current] = len(cycle) * self.time_scale
|
183
197
|
return out_dict, self.times
|
184
198
|
|
185
199
|
def delta(self, x, y, corres1, corres2, times1, times2):
|
186
200
|
return super().delta(x, y, corres1, corres2, times1, times2)
|
187
201
|
|
188
202
|
def get_norm(self):
|
189
|
-
return
|
203
|
+
return (
|
204
|
+
len(self.lT.get_sub_tree(self.root, end_time=self.end_time))
|
205
|
+
* self.time_scale
|
206
|
+
)
|
190
207
|
|
191
208
|
|
192
|
-
class
|
193
|
-
"""
|
194
|
-
Instead of having branches with length == life cycle of cell,nodes of specific length are added on the
|
195
|
-
edges of the branch, providing both accuratr results and speed.
|
196
|
-
It's the recommended method for calculating edit distances on developing embryos.
|
197
|
-
"""
|
209
|
+
class downsample_tree(abstract_trees):
|
210
|
+
"""Downsamples a tree so every n nodes are being used as one."""
|
198
211
|
|
199
212
|
def __init__(self, **kwargs):
|
200
213
|
super().__init__(**kwargs)
|
201
214
|
|
202
215
|
def get_tree(self):
|
203
|
-
if self.end_time is None:
|
204
|
-
self.end_time = self.lT.t_e
|
205
216
|
self.out_dict = {}
|
206
217
|
self.times = {}
|
207
218
|
to_do = [self.root]
|
208
|
-
if not isinstance(self.node_length, list):
|
209
|
-
self.node_length = list(self.node_length)
|
210
219
|
while to_do:
|
211
220
|
current = to_do.pop()
|
212
|
-
|
213
|
-
self.lT.
|
221
|
+
_next = self.lT.get_cells_at_t_from_root(
|
222
|
+
current, self.lT.time[current] + self.downsample
|
214
223
|
)
|
215
|
-
if
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
)
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
].copy()
|
224
|
-
length_middle_node = (
|
225
|
-
len(cycle) - sum(current_node_lengths) * 2
|
226
|
-
)
|
227
|
-
times_tmp = (
|
228
|
-
current_node_lengths
|
229
|
-
+ [length_middle_node]
|
230
|
-
+ current_node_lengths[::-1]
|
231
|
-
)
|
232
|
-
pos_all_nodes = np.concatenate(
|
233
|
-
[[0], np.cumsum(times_tmp[:-1])]
|
234
|
-
)
|
235
|
-
track = cycle[pos_all_nodes]
|
236
|
-
self.out_dict.update(
|
237
|
-
{k: [v] for k, v in zip(track[:-1], track[1:])}
|
238
|
-
)
|
239
|
-
self.times.update(zip(track, times_tmp))
|
240
|
-
else:
|
241
|
-
for i, cell in enumerate(cycle[:-1]):
|
242
|
-
self.out_dict[cell] = [cycle[i + 1]]
|
243
|
-
self.times[cell] = 1
|
244
|
-
current = cycle[-1]
|
245
|
-
_next = self.lT[current]
|
246
|
-
self.times[current] = 1
|
247
|
-
if _next and self.lT.time[_next[0]] <= self.end_time:
|
248
|
-
to_do.extend(_next)
|
249
|
-
self.out_dict[current] = _next
|
250
|
-
else:
|
251
|
-
self.out_dict[current] = []
|
252
|
-
|
224
|
+
if _next == [current]:
|
225
|
+
_next = None
|
226
|
+
if _next and self.lT.time[_next[0]] <= self.end_time:
|
227
|
+
self.out_dict[current] = _next
|
228
|
+
to_do.extend(_next)
|
229
|
+
else:
|
230
|
+
self.out_dict[current] = []
|
231
|
+
self.times[current] = self.downsample
|
253
232
|
return self.out_dict, self.times
|
254
233
|
|
255
234
|
def get_norm(self):
|
256
|
-
return
|
235
|
+
return sum(self.times.values())
|
257
236
|
|
258
237
|
def delta(self, x, y, corres1, corres2, times1, times2):
|
259
|
-
|
238
|
+
if x is None and y is None:
|
239
|
+
return 0
|
240
|
+
if x is None:
|
241
|
+
return self.downsample
|
242
|
+
if y is None:
|
243
|
+
return self.downsample
|
244
|
+
return 0
|
245
|
+
|
246
|
+
|
247
|
+
class normalized_simple_tree(simple_tree):
|
248
|
+
def __init__(self, **kwargs):
|
249
|
+
super().__init__(**kwargs)
|
250
|
+
|
251
|
+
def delta(self, x, y, corres1, corres2, times1, times2):
|
252
|
+
if x is None and y is None:
|
253
|
+
return 0
|
254
|
+
if x is None:
|
255
|
+
return 1
|
256
|
+
if y is None:
|
257
|
+
return 1
|
258
|
+
return abs(times1[corres1[x]] - times2[corres2[y]]) / (
|
259
|
+
times1[corres1[x]] + times2[corres2[y]]
|
260
|
+
)
|
261
|
+
|
262
|
+
def get_norm(self):
|
263
|
+
return len(
|
264
|
+
self.lT.get_all_branches_of_node(self.root, end_time=self.end_time)
|
265
|
+
)
|
260
266
|
|
261
267
|
|
262
268
|
class full_tree(abstract_trees):
|
@@ -276,6 +282,11 @@ class full_tree(abstract_trees):
|
|
276
282
|
current = to_do.pop()
|
277
283
|
_next = self.lT.successor.get(current, [])
|
278
284
|
if _next and self.lT.time[_next[0]] <= self.end_time:
|
285
|
+
if self.time_scale > 1:
|
286
|
+
for _ in range(self.time_scale):
|
287
|
+
next_id = self.lT.get_next_id()
|
288
|
+
self.out_dict[current] = next_id
|
289
|
+
current = next_id
|
279
290
|
self.out_dict[current] = _next
|
280
291
|
to_do.extend(_next)
|
281
292
|
else:
|
@@ -284,16 +295,26 @@ class full_tree(abstract_trees):
|
|
284
295
|
return self.out_dict, self.times
|
285
296
|
|
286
297
|
def get_norm(self):
|
287
|
-
return
|
298
|
+
return (
|
299
|
+
len(self.lT.get_sub_tree(self.root, end_time=self.end_time))
|
300
|
+
* self.time_scale
|
301
|
+
)
|
288
302
|
|
289
303
|
def delta(self, x, y, corres1, corres2, times1, times2):
|
290
|
-
|
304
|
+
if x is None and y is None:
|
305
|
+
return 0
|
306
|
+
if x is None:
|
307
|
+
return 1
|
308
|
+
if y is None:
|
309
|
+
return 1
|
310
|
+
return 0
|
291
311
|
|
292
312
|
|
293
313
|
class tree_style(Enum):
|
294
314
|
mini = mini_tree
|
295
315
|
simple = simple_tree
|
296
|
-
|
316
|
+
normalized_simple = normalized_simple_tree
|
317
|
+
downsampled = downsample_tree
|
297
318
|
full = full_tree
|
298
319
|
|
299
320
|
@classmethod
|
LineageTree/utils.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: LineageTree
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.7.0
|
4
4
|
Summary: Lineage tree structure
|
5
5
|
Home-page: https://github.com/leoguignard/LineageTree
|
6
6
|
Author: Léo Guignard
|
@@ -28,14 +28,6 @@ Requires-Dist: scipy>=1.9
|
|
28
28
|
Requires-Dist: numpy>=1.23
|
29
29
|
Requires-Dist: mastodon-reader
|
30
30
|
Requires-Dist: matplotlib
|
31
|
-
Provides-Extra: all
|
32
|
-
Requires-Dist: svgwrite; extra == "all"
|
33
|
-
Requires-Dist: matplotlib; extra == "all"
|
34
|
-
Requires-Dist: tox; extra == "all"
|
35
|
-
Requires-Dist: pytest; extra == "all"
|
36
|
-
Requires-Dist: pytest-cov; extra == "all"
|
37
|
-
Requires-Dist: edist; extra == "all"
|
38
|
-
Requires-Dist: mastodon-reader; extra == "all"
|
39
31
|
Provides-Extra: svg
|
40
32
|
Requires-Dist: svgwrite; extra == "svg"
|
41
33
|
Requires-Dist: matplotlib; extra == "svg"
|
@@ -48,6 +40,14 @@ Requires-Dist: pytest-cov; extra == "test"
|
|
48
40
|
Requires-Dist: mastodon-reader; extra == "test"
|
49
41
|
Provides-Extra: treeedit
|
50
42
|
Requires-Dist: edist; extra == "treeedit"
|
43
|
+
Provides-Extra: all
|
44
|
+
Requires-Dist: svgwrite; extra == "all"
|
45
|
+
Requires-Dist: matplotlib; extra == "all"
|
46
|
+
Requires-Dist: tox; extra == "all"
|
47
|
+
Requires-Dist: pytest; extra == "all"
|
48
|
+
Requires-Dist: pytest-cov; extra == "all"
|
49
|
+
Requires-Dist: edist; extra == "all"
|
50
|
+
Requires-Dist: mastodon-reader; extra == "all"
|
51
51
|
|
52
52
|
# LineageTree
|
53
53
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
LineageTree/__init__.py,sha256=gRnB16cDWVXa8GC6errEK0JxnllNypanG8HOJeO9mgo,155
|
2
|
+
LineageTree/lineageTree.py,sha256=WLpKDzlt0vDL4zMOuyEsWYiLgNJcX8qMORgD0wPwQJg,107536
|
3
|
+
LineageTree/lineageTreeManager.py,sha256=OFcW4DmUtsPQb6dw1jKy3EPezs4UCQ666-Ki3IYuzbc,5641
|
4
|
+
LineageTree/loaders.py,sha256=D5hE928OD2RUGwG8CBW-69V8tp-k-VyNCm4I9AmtK2A,25976
|
5
|
+
LineageTree/tree_styles.py,sha256=U4mkcAYCK1WjWT3rdP1Hc-KxNXqEnwgZy0HAxTsUNpE,10746
|
6
|
+
LineageTree/utils.py,sha256=nhOThtLVA29cvt9SwrC9LE7kf6DZMaBr0OC7_hZf3Rk,4947
|
7
|
+
LineageTree-1.7.0.dist-info/LICENSE,sha256=IKncNCNpq93Kq7Ywg1V4I9Bu_99VMK-mX1EJyjTKLyc,1068
|
8
|
+
LineageTree-1.7.0.dist-info/METADATA,sha256=rdZOPPbmNa5YRbjOhLpLJPEeDPuFR4eHcPfOiXyud18,4195
|
9
|
+
LineageTree-1.7.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
10
|
+
LineageTree-1.7.0.dist-info/top_level.txt,sha256=CCqPCTX76zPTEUagUgTWbLaZun8752n59iOOwfUlvxs,12
|
11
|
+
LineageTree-1.7.0.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
LineageTree/__init__.py,sha256=fIOwpoeHI19rwQShQ50iRXyRg4OXzVo_A4q_BUDBmsk,155
|
2
|
-
LineageTree/lineageTree.py,sha256=lamlTaHtu3gPUCMKIvlCHQqGMbcQAHCvgS8MB6EHWTQ,102742
|
3
|
-
LineageTree/lineageTreeManager.py,sha256=tQMNmxAvUEd4fNLJViB-9DGItpgCRnSF-bS2_IdebMo,5566
|
4
|
-
LineageTree/loaders.py,sha256=hKd-69d_csjcsV0zabSTEOyy0NhprFqvnRAI18tYa9w,23718
|
5
|
-
LineageTree/tree_styles.py,sha256=IUEq8RHfwXTv3WXylpLVa5XoThaWt1i1cRsgZGXdd50,10634
|
6
|
-
LineageTree/utils.py,sha256=kC90fi8TFm8Pz7oom_bDsq7HPPKnFdJ6wI-x36fLvHE,4962
|
7
|
-
LineageTree-1.6.1.dist-info/LICENSE,sha256=IKncNCNpq93Kq7Ywg1V4I9Bu_99VMK-mX1EJyjTKLyc,1068
|
8
|
-
LineageTree-1.6.1.dist-info/METADATA,sha256=eDkg_GjSQtFDXXjmyagn-gXRosFsDq2n8706srcixA8,4195
|
9
|
-
LineageTree-1.6.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
10
|
-
LineageTree-1.6.1.dist-info/top_level.txt,sha256=CCqPCTX76zPTEUagUgTWbLaZun8752n59iOOwfUlvxs,12
|
11
|
-
LineageTree-1.6.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|