LineageTree 1.7.0__tar.gz → 1.8.0__tar.gz
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-1.7.0/src/LineageTree.egg-info → lineagetree-1.8.0}/PKG-INFO +2 -1
- {lineagetree-1.7.0 → lineagetree-1.8.0}/README.md +1 -0
- {lineagetree-1.7.0 → lineagetree-1.8.0}/pyproject.toml +5 -3
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree/__init__.py +1 -1
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree/lineageTree.py +82 -207
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree/lineageTreeManager.py +37 -12
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree/loaders.py +216 -42
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree/tree_styles.py +23 -11
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree/utils.py +7 -9
- {lineagetree-1.7.0 → lineagetree-1.8.0/src/LineageTree.egg-info}/PKG-INFO +2 -1
- lineagetree-1.8.0/test/test_lineageTree.py +298 -0
- lineagetree-1.7.0/test/test_lineageTree.py +0 -115
- {lineagetree-1.7.0 → lineagetree-1.8.0}/LICENSE +0 -0
- {lineagetree-1.7.0 → lineagetree-1.8.0}/setup.cfg +0 -0
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree.egg-info/SOURCES.txt +0 -0
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree.egg-info/dependency_links.txt +0 -0
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree.egg-info/requires.txt +0 -0
- {lineagetree-1.7.0 → lineagetree-1.8.0}/src/LineageTree.egg-info/top_level.txt +0 -0
- {lineagetree-1.7.0 → lineagetree-1.8.0}/test/test_uted.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: LineageTree
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.8.0
|
4
4
|
Summary: Lineage tree structure
|
5
5
|
Home-page: https://github.com/leoguignard/LineageTree
|
6
6
|
Author: Léo Guignard
|
@@ -59,6 +59,7 @@ With LineageTree you can read from:
|
|
59
59
|
- MaMuT files described in [Wolff et al. 2018](https://doi.org/10.7554/eLife.34410)
|
60
60
|
- SVF algorithm outputs described in [McDole, Guignard et al. 2018](https://doi.org/10.1016/j.cell.2018.09.031)
|
61
61
|
- ASTEC algorithm outputs described in [Guignard, Fiuza et al. 2020](https://doi.org/10.1126/science.aar5663)
|
62
|
+
- Data from the [Digital development Database](http://digital-development.org/index.html) described in [Du et al. 2014](https://www.cell.com/fulltext/S0092-8674(13)01542-0) and [Du et al. 2015](https://www.sciencedirect.com/science/article/pii/S1534580715004876?via%3Dihub)
|
62
63
|
- and few others
|
63
64
|
|
64
65
|
## Basic usage
|
@@ -8,6 +8,7 @@ With LineageTree you can read from:
|
|
8
8
|
- MaMuT files described in [Wolff et al. 2018](https://doi.org/10.7554/eLife.34410)
|
9
9
|
- SVF algorithm outputs described in [McDole, Guignard et al. 2018](https://doi.org/10.1016/j.cell.2018.09.031)
|
10
10
|
- ASTEC algorithm outputs described in [Guignard, Fiuza et al. 2020](https://doi.org/10.1126/science.aar5663)
|
11
|
+
- Data from the [Digital development Database](http://digital-development.org/index.html) described in [Du et al. 2014](https://www.cell.com/fulltext/S0092-8674(13)01542-0) and [Du et al. 2015](https://www.sciencedirect.com/science/article/pii/S1534580715004876?via%3Dihub)
|
11
12
|
- and few others
|
12
13
|
|
13
14
|
## Basic usage
|
@@ -10,7 +10,7 @@ profile = "black"
|
|
10
10
|
line_length = 79
|
11
11
|
|
12
12
|
[tool.bumpver]
|
13
|
-
current_version = "1.
|
13
|
+
current_version = "1.8.0"
|
14
14
|
version_pattern = "MAJOR.MINOR.PATCH[-TAG]"
|
15
15
|
commit_message = "bump version {old_version} -> {new_version}"
|
16
16
|
commit = true
|
@@ -40,13 +40,15 @@ select = [
|
|
40
40
|
"G", # flake8-logging-format
|
41
41
|
"PIE", # flake8-pie
|
42
42
|
"SIM", # flake8-simplify
|
43
|
-
"I"
|
43
|
+
"I", # imports
|
44
|
+
"T201", # print statements
|
45
|
+
"ERA001", #commented out code
|
44
46
|
]
|
45
47
|
ignore = [
|
46
48
|
"E501", # line too long. let black handle this
|
47
49
|
"UP006", "UP007", # type annotation. As using magicgui require runtime type annotation then we disable this.
|
48
50
|
"SIM117", # flake8-simplify - some of merged with statements are not looking great with black, reanble after drop python 3.9
|
49
|
-
"SIM300"
|
51
|
+
"SIM300", # yoda conditions
|
50
52
|
]
|
51
53
|
|
52
54
|
exclude = [
|
@@ -70,7 +70,12 @@ class lineageTree(lineageTreeLoaders):
|
|
70
70
|
sub = set(self.get_sub_tree(node))
|
71
71
|
specific_leaves = sub.intersection(self.leaves)
|
72
72
|
for leaf in specific_leaves:
|
73
|
-
self.add_branch(
|
73
|
+
self.add_branch(
|
74
|
+
leaf,
|
75
|
+
(self.t_e - self.time[leaf]),
|
76
|
+
reverse=True,
|
77
|
+
move_timepoints=True,
|
78
|
+
)
|
74
79
|
|
75
80
|
###TODO pos can be callable and stay motionless (copy the position of the succ node, use something like optical flow)
|
76
81
|
def add_branch(
|
@@ -88,7 +93,7 @@ class lineageTree(lineageTreeLoaders):
|
|
88
93
|
pred (int): Id of the successor (predecessor if reverse is False)
|
89
94
|
length (int): The length of the new branch.
|
90
95
|
pos (np.ndarray, optional): The new position of the branch. Defaults to None.
|
91
|
-
move_timepoints (bool): Moves the
|
96
|
+
move_timepoints (bool): Moves the time, important only if reverse= True
|
92
97
|
reverese (bool): If True will create a branch that goes forwards in time otherwise backwards.
|
93
98
|
Returns:
|
94
99
|
(int): Id of the first node of the sublineage.
|
@@ -138,14 +143,12 @@ class lineageTree(lineageTreeLoaders):
|
|
138
143
|
reverse=False,
|
139
144
|
)
|
140
145
|
pred = _next
|
146
|
+
self.successor[self.get_cycle(pred)[-1]] = []
|
141
147
|
self.labels[pred] = "New branch"
|
142
148
|
if self.time[pred] == self.t_b:
|
143
|
-
self.roots.add(pred)
|
144
149
|
self.labels[pred] = "New branch"
|
145
150
|
if original in self.roots and reverse is True:
|
146
|
-
self.roots.add(pred)
|
147
151
|
self.labels[pred] = "New branch"
|
148
|
-
self.roots.remove(original)
|
149
152
|
self.labels.pop(original, -1)
|
150
153
|
self.t_e = max(self.time_nodes)
|
151
154
|
return pred
|
@@ -167,9 +170,7 @@ class lineageTree(lineageTreeLoaders):
|
|
167
170
|
self.predecessor.pop(new_lT)
|
168
171
|
label_of_root = self.labels.get(cycle[0], cycle[0])
|
169
172
|
self.labels[cycle[0]] = f"L-Split {label_of_root}"
|
170
|
-
new_tr = self.add_branch(
|
171
|
-
new_lT, len(cycle) + 1, move_timepoints=False
|
172
|
-
)
|
173
|
+
new_tr = self.add_branch(new_lT, len(cycle), move_timepoints=False)
|
173
174
|
self.roots.add(new_tr)
|
174
175
|
self.labels[new_tr] = f"R-Split {label_of_root}"
|
175
176
|
return new_tr
|
@@ -209,7 +210,10 @@ class lineageTree(lineageTreeLoaders):
|
|
209
210
|
self.remove_nodes(new_root1)
|
210
211
|
self.successor[new_root2].append(next_root1)
|
211
212
|
self.predecessor[next_root1] = [new_root2]
|
212
|
-
new_branch = self.add_branch(
|
213
|
+
new_branch = self.add_branch(
|
214
|
+
new_root2,
|
215
|
+
length - 1,
|
216
|
+
)
|
213
217
|
self.labels[new_branch] = f"Fusion of {new_root1} and {new_root2}"
|
214
218
|
return new_branch
|
215
219
|
|
@@ -321,7 +325,7 @@ class lineageTree(lineageTreeLoaders):
|
|
321
325
|
new_length (int): The new length of the tree.
|
322
326
|
"""
|
323
327
|
if new_length <= 1:
|
324
|
-
warnings.warn("New length should be more than 1")
|
328
|
+
warnings.warn("New length should be more than 1", stacklevel=2)
|
325
329
|
return None
|
326
330
|
cycle = self.get_cycle(node)
|
327
331
|
length = len(cycle)
|
@@ -383,14 +387,29 @@ class lineageTree(lineageTreeLoaders):
|
|
383
387
|
if time_resolution is not None:
|
384
388
|
self._time_resolution = int(time_resolution * 10)
|
385
389
|
else:
|
386
|
-
warnings.warn("Time resolution set to default
|
390
|
+
warnings.warn("Time resolution set to default 0", stacklevel=2)
|
387
391
|
self._time_resolution = 10
|
388
392
|
|
393
|
+
@property
|
394
|
+
def depth(self):
|
395
|
+
if not hasattr(self, "_depth"):
|
396
|
+
self._depth = {}
|
397
|
+
for leaf in self.leaves:
|
398
|
+
self._depth[leaf] = 1
|
399
|
+
while leaf in self.predecessor:
|
400
|
+
parent = self.predecessor[leaf][0]
|
401
|
+
current_depth = self._depth.get(parent, 0)
|
402
|
+
self._depth[parent] = max(
|
403
|
+
self._depth[leaf] + 1, current_depth
|
404
|
+
)
|
405
|
+
leaf = parent
|
406
|
+
for root in self.roots - set(self._depth):
|
407
|
+
self._depth[root] = 1
|
408
|
+
return self._depth
|
409
|
+
|
389
410
|
@property
|
390
411
|
def roots(self):
|
391
|
-
|
392
|
-
self._roots = set(self.nodes).difference(self.predecessor)
|
393
|
-
return self._roots
|
412
|
+
return set(self.nodes).difference(self.predecessor)
|
394
413
|
|
395
414
|
@property
|
396
415
|
def edges(self):
|
@@ -398,7 +417,7 @@ class lineageTree(lineageTreeLoaders):
|
|
398
417
|
|
399
418
|
@property
|
400
419
|
def leaves(self):
|
401
|
-
return
|
420
|
+
return {p for p, s in self.successor.items() if s == []}
|
402
421
|
|
403
422
|
@property
|
404
423
|
def labels(self):
|
@@ -409,10 +428,10 @@ class lineageTree(lineageTreeLoaders):
|
|
409
428
|
}
|
410
429
|
else:
|
411
430
|
self._labels = {
|
412
|
-
|
413
|
-
for
|
414
|
-
for
|
415
|
-
if abs(self.time[
|
431
|
+
root: "Unlabeled"
|
432
|
+
for root in self.roots
|
433
|
+
for leaf in self.find_leaves(root)
|
434
|
+
if abs(self.time[leaf] - self.time[root])
|
416
435
|
>= abs(self.t_e - self.t_b) / 4
|
417
436
|
}
|
418
437
|
return self._labels
|
@@ -504,7 +523,7 @@ class lineageTree(lineageTreeLoaders):
|
|
504
523
|
|
505
524
|
f.write("@5\n")
|
506
525
|
for C in self.to_take_time[t]:
|
507
|
-
f.write(
|
526
|
+
f.write(f"{manual_labels.get(C, default_label):f}\n")
|
508
527
|
f.write(f"{0:f}\n")
|
509
528
|
|
510
529
|
f.write("@6\n")
|
@@ -523,8 +542,7 @@ class lineageTree(lineageTreeLoaders):
|
|
523
542
|
f.write("@7\n")
|
524
543
|
for C in self.to_take_time[t]:
|
525
544
|
f.write(
|
526
|
-
"
|
527
|
-
% (np.linalg.norm(points_v[C][0] - points_v[C][-1]))
|
545
|
+
f"{np.linalg.norm(points_v[C][0] - points_v[C][-1]):f}\n"
|
528
546
|
)
|
529
547
|
|
530
548
|
f.write("@8\n")
|
@@ -953,10 +971,10 @@ class lineageTree(lineageTreeLoaders):
|
|
953
971
|
if node_properties:
|
954
972
|
for p_name, (p_dict, default) in node_properties.items():
|
955
973
|
if isinstance(list(p_dict.values())[0], str):
|
956
|
-
f.write('(property 0 string "
|
974
|
+
f.write(f'(property 0 string "{p_name}"\n')
|
957
975
|
f.write(f"\t(default {default} {default})\n")
|
958
976
|
elif isinstance(list(p_dict.values())[0], Number):
|
959
|
-
f.write('(property 0 double "
|
977
|
+
f.write(f'(property 0 double "{p_name}"\n')
|
960
978
|
f.write('\t(default "0" "0")\n')
|
961
979
|
for n in nodes_to_use:
|
962
980
|
f.write(
|
@@ -1030,148 +1048,6 @@ class lineageTree(lineageTreeLoaders):
|
|
1030
1048
|
|
1031
1049
|
f.close()
|
1032
1050
|
|
1033
|
-
def read_from_binary(self, fname: str):
|
1034
|
-
"""
|
1035
|
-
Reads a binary lineageTree file name.
|
1036
|
-
Format description: see self.to_binary
|
1037
|
-
|
1038
|
-
Args:
|
1039
|
-
fname: string, path to the binary file
|
1040
|
-
reverse_time: bool, not used
|
1041
|
-
"""
|
1042
|
-
q_size = struct.calcsize("q")
|
1043
|
-
H_size = struct.calcsize("H")
|
1044
|
-
d_size = struct.calcsize("d")
|
1045
|
-
|
1046
|
-
with open(fname, "rb") as f:
|
1047
|
-
len_tree = struct.unpack("q", f.read(q_size))[0]
|
1048
|
-
len_time = struct.unpack("q", f.read(q_size))[0]
|
1049
|
-
len_pos = struct.unpack("q", f.read(q_size))[0]
|
1050
|
-
number_sequence = list(
|
1051
|
-
struct.unpack("q" * len_tree, f.read(q_size * len_tree))
|
1052
|
-
)
|
1053
|
-
time_sequence = list(
|
1054
|
-
struct.unpack("H" * len_time, f.read(H_size * len_time))
|
1055
|
-
)
|
1056
|
-
pos_sequence = np.array(
|
1057
|
-
struct.unpack("d" * len_pos, f.read(d_size * len_pos))
|
1058
|
-
)
|
1059
|
-
|
1060
|
-
f.close()
|
1061
|
-
|
1062
|
-
successor = {}
|
1063
|
-
predecessor = {}
|
1064
|
-
time = {}
|
1065
|
-
time_nodes = {}
|
1066
|
-
time_edges = {}
|
1067
|
-
pos = {}
|
1068
|
-
is_root = {}
|
1069
|
-
nodes = []
|
1070
|
-
edges = []
|
1071
|
-
waiting_list = []
|
1072
|
-
print(number_sequence[0])
|
1073
|
-
i = 0
|
1074
|
-
done = False
|
1075
|
-
if max(number_sequence[::2]) == -1:
|
1076
|
-
tmp = number_sequence[1::2]
|
1077
|
-
if len(tmp) * 3 == len(pos_sequence) == len(time_sequence) * 3:
|
1078
|
-
time = dict(list(zip(tmp, time_sequence)))
|
1079
|
-
for c, t in time.items():
|
1080
|
-
time_nodes.setdefault(t, set()).add(c)
|
1081
|
-
pos = dict(
|
1082
|
-
list(zip(tmp, np.reshape(pos_sequence, (len_time, 3))))
|
1083
|
-
)
|
1084
|
-
is_root = {c: True for c in tmp}
|
1085
|
-
nodes = tmp
|
1086
|
-
done = True
|
1087
|
-
while (
|
1088
|
-
i < len(number_sequence) and not done
|
1089
|
-
): # , c in enumerate(number_sequence[:-1]):
|
1090
|
-
c = number_sequence[i]
|
1091
|
-
if c == -1:
|
1092
|
-
if waiting_list != []:
|
1093
|
-
prev_mother = waiting_list.pop()
|
1094
|
-
successor[prev_mother].insert(0, number_sequence[i + 1])
|
1095
|
-
edges.append((prev_mother, number_sequence[i + 1]))
|
1096
|
-
time_edges.setdefault(t, set()).add(
|
1097
|
-
(prev_mother, number_sequence[i + 1])
|
1098
|
-
)
|
1099
|
-
is_root[number_sequence[i + 1]] = False
|
1100
|
-
t = time[prev_mother] + 1
|
1101
|
-
else:
|
1102
|
-
t = time_sequence.pop(0)
|
1103
|
-
is_root[number_sequence[i + 1]] = True
|
1104
|
-
|
1105
|
-
elif c == -2:
|
1106
|
-
successor[waiting_list[-1]] = [number_sequence[i + 1]]
|
1107
|
-
edges.append((waiting_list[-1], number_sequence[i + 1]))
|
1108
|
-
time_edges.setdefault(t, set()).add(
|
1109
|
-
(waiting_list[-1], number_sequence[i + 1])
|
1110
|
-
)
|
1111
|
-
is_root[number_sequence[i + 1]] = False
|
1112
|
-
pos[waiting_list[-1]] = pos_sequence[:3]
|
1113
|
-
pos_sequence = pos_sequence[3:]
|
1114
|
-
nodes.append(waiting_list[-1])
|
1115
|
-
time[waiting_list[-1]] = t
|
1116
|
-
time_nodes.setdefault(t, set()).add(waiting_list[-1])
|
1117
|
-
t += 1
|
1118
|
-
|
1119
|
-
elif number_sequence[i + 1] >= 0:
|
1120
|
-
successor[c] = [number_sequence[i + 1]]
|
1121
|
-
edges.append((c, number_sequence[i + 1]))
|
1122
|
-
time_edges.setdefault(t, set()).add(
|
1123
|
-
(c, number_sequence[i + 1])
|
1124
|
-
)
|
1125
|
-
is_root[number_sequence[i + 1]] = False
|
1126
|
-
pos[c] = pos_sequence[:3]
|
1127
|
-
pos_sequence = pos_sequence[3:]
|
1128
|
-
nodes.append(c)
|
1129
|
-
time[c] = t
|
1130
|
-
time_nodes.setdefault(t, set()).add(c)
|
1131
|
-
t += 1
|
1132
|
-
|
1133
|
-
elif number_sequence[i + 1] == -2:
|
1134
|
-
waiting_list += [c]
|
1135
|
-
|
1136
|
-
elif number_sequence[i + 1] == -1:
|
1137
|
-
pos[c] = pos_sequence[:3]
|
1138
|
-
pos_sequence = pos_sequence[3:]
|
1139
|
-
nodes.append(c)
|
1140
|
-
time[c] = t
|
1141
|
-
time_nodes.setdefault(t, set()).add(c)
|
1142
|
-
t += 1
|
1143
|
-
i += 1
|
1144
|
-
if waiting_list != []:
|
1145
|
-
prev_mother = waiting_list.pop()
|
1146
|
-
successor[prev_mother].insert(0, number_sequence[i + 1])
|
1147
|
-
edges.append((prev_mother, number_sequence[i + 1]))
|
1148
|
-
time_edges.setdefault(t, set()).add(
|
1149
|
-
(prev_mother, number_sequence[i + 1])
|
1150
|
-
)
|
1151
|
-
if i + 1 < len(number_sequence):
|
1152
|
-
is_root[number_sequence[i + 1]] = False
|
1153
|
-
t = time[prev_mother] + 1
|
1154
|
-
else:
|
1155
|
-
if len(time_sequence) > 0:
|
1156
|
-
t = time_sequence.pop(0)
|
1157
|
-
if i + 1 < len(number_sequence):
|
1158
|
-
is_root[number_sequence[i + 1]] = True
|
1159
|
-
i += 1
|
1160
|
-
|
1161
|
-
predecessor = {vi: [k] for k, v in successor.items() for vi in v}
|
1162
|
-
|
1163
|
-
self.successor = successor
|
1164
|
-
self.predecessor = predecessor
|
1165
|
-
self.time = time
|
1166
|
-
self.time_nodes = time_nodes
|
1167
|
-
self.time_edges = time_edges
|
1168
|
-
self.pos = pos
|
1169
|
-
self.nodes = set(nodes)
|
1170
|
-
self.t_b = min(time_nodes.keys())
|
1171
|
-
self.t_e = max(time_nodes.keys())
|
1172
|
-
self.is_root = is_root
|
1173
|
-
self.max_id = max(self.nodes)
|
1174
|
-
|
1175
1051
|
def write(self, fname: str):
|
1176
1052
|
"""
|
1177
1053
|
Write a lineage tree on disk as an .lT file.
|
@@ -1186,7 +1062,7 @@ class lineageTree(lineageTreeLoaders):
|
|
1186
1062
|
f.close()
|
1187
1063
|
|
1188
1064
|
@classmethod
|
1189
|
-
def load(clf, fname: str, rm_empty_lists=
|
1065
|
+
def load(clf, fname: str, rm_empty_lists=False):
|
1190
1066
|
"""
|
1191
1067
|
Loading a lineage tree from a ".lT" file.
|
1192
1068
|
|
@@ -1201,18 +1077,6 @@ class lineageTree(lineageTreeLoaders):
|
|
1201
1077
|
f.close()
|
1202
1078
|
if not hasattr(lT, "time_resolution"):
|
1203
1079
|
lT.time_resolution = None
|
1204
|
-
if rm_empty_lists:
|
1205
|
-
if [] in lT.successor.values():
|
1206
|
-
for node, succ in lT.successor.items():
|
1207
|
-
if succ == []:
|
1208
|
-
lT.successor.pop(node)
|
1209
|
-
if [] in lT.predecessor.values():
|
1210
|
-
for node, succ in lT.predecessor.items():
|
1211
|
-
if succ == []:
|
1212
|
-
lT.predecessor.pop(node)
|
1213
|
-
lT.t_e = max(lT.time_nodes)
|
1214
|
-
lT.t_b = min(lT.time_nodes)
|
1215
|
-
warnings.warn("Empty lists have been removed")
|
1216
1080
|
return lT
|
1217
1081
|
|
1218
1082
|
def get_idx3d(self, t: int) -> tuple:
|
@@ -1474,11 +1338,10 @@ class lineageTree(lineageTreeLoaders):
|
|
1474
1338
|
leaves = set()
|
1475
1339
|
while to_do:
|
1476
1340
|
curr = to_do.pop()
|
1477
|
-
succ = self.successor.get(curr)
|
1478
|
-
if
|
1341
|
+
succ = self.successor.get(curr, [])
|
1342
|
+
if not succ:
|
1479
1343
|
leaves.add(curr)
|
1480
|
-
|
1481
|
-
to_do += succ
|
1344
|
+
to_do += succ
|
1482
1345
|
return leaves
|
1483
1346
|
|
1484
1347
|
def get_sub_tree(
|
@@ -1883,7 +1746,6 @@ class lineageTree(lineageTreeLoaders):
|
|
1883
1746
|
color_of_edges=None,
|
1884
1747
|
size=10,
|
1885
1748
|
ax=None,
|
1886
|
-
figure=None,
|
1887
1749
|
default_color="black",
|
1888
1750
|
**kwargs,
|
1889
1751
|
):
|
@@ -1940,7 +1802,7 @@ class lineageTree(lineageTreeLoaders):
|
|
1940
1802
|
)
|
1941
1803
|
ax.get_yaxis().set_visible(False)
|
1942
1804
|
ax.get_xaxis().set_visible(False)
|
1943
|
-
return
|
1805
|
+
return ax.get_figure(), ax
|
1944
1806
|
|
1945
1807
|
def to_simple_graph(self, node=None, start_time: int = None):
|
1946
1808
|
"""Generates a dictionary of graphs where the keys are the index of the graph and
|
@@ -1975,8 +1837,8 @@ class lineageTree(lineageTreeLoaders):
|
|
1975
1837
|
figsize=(10, 15),
|
1976
1838
|
dpi=100,
|
1977
1839
|
fontsize=15,
|
1978
|
-
figure=None,
|
1979
1840
|
axes=None,
|
1841
|
+
vert_gap=1,
|
1980
1842
|
**kwargs,
|
1981
1843
|
):
|
1982
1844
|
"""Plots all lineages.
|
@@ -2006,14 +1868,24 @@ class lineageTree(lineageTreeLoaders):
|
|
2006
1868
|
)
|
2007
1869
|
pos = {
|
2008
1870
|
i: hierarchical_pos(
|
2009
|
-
g,
|
1871
|
+
g,
|
1872
|
+
g["root"],
|
1873
|
+
ycenter=-int(self.time[g["root"]]),
|
1874
|
+
vert_gap=vert_gap,
|
2010
1875
|
)
|
2011
1876
|
for i, g in graphs.items()
|
2012
1877
|
}
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
1878
|
+
if axes is None:
|
1879
|
+
ncols = int(len(graphs) // nrows) + (+np.sign(len(graphs) % nrows))
|
1880
|
+
figure, axes = plt.subplots(
|
1881
|
+
figsize=figsize, nrows=nrows, ncols=ncols, dpi=dpi, sharey=True
|
1882
|
+
)
|
1883
|
+
else:
|
1884
|
+
figure, axes = axes.flatten()[0].get_figure(), axes
|
1885
|
+
if len(axes.flatten()) < len(graphs):
|
1886
|
+
raise Exception(
|
1887
|
+
f"Not enough axes, they should be at least {len(graphs)}."
|
1888
|
+
)
|
2017
1889
|
flat_axes = axes.flatten()
|
2018
1890
|
ax2root = {}
|
2019
1891
|
min_width, min_height = float("inf"), float("inf")
|
@@ -2051,9 +1923,17 @@ class lineageTree(lineageTreeLoaders):
|
|
2051
1923
|
},
|
2052
1924
|
)
|
2053
1925
|
[figure.delaxes(ax) for ax in axes.flatten() if not ax.has_data()]
|
2054
|
-
return
|
1926
|
+
return axes.flatten()[0].get_figure(), axes, ax2root
|
2055
1927
|
|
2056
|
-
def plot_node(
|
1928
|
+
def plot_node(
|
1929
|
+
self,
|
1930
|
+
node,
|
1931
|
+
figsize=(4, 7),
|
1932
|
+
dpi=150,
|
1933
|
+
vert_gap=2,
|
1934
|
+
ax=None,
|
1935
|
+
**kwargs,
|
1936
|
+
):
|
2057
1937
|
"""Plots the subtree spawn by a node.
|
2058
1938
|
|
2059
1939
|
Args:
|
@@ -2064,7 +1944,10 @@ class lineageTree(lineageTreeLoaders):
|
|
2064
1944
|
if len(graph) > 1:
|
2065
1945
|
raise Warning("Please enter only one node")
|
2066
1946
|
graph = graph[0]
|
2067
|
-
|
1947
|
+
if not ax:
|
1948
|
+
figure, ax = plt.subplots(
|
1949
|
+
nrows=1, ncols=1, figsize=figsize, dpi=dpi
|
1950
|
+
)
|
2068
1951
|
self.draw_tree_graph(
|
2069
1952
|
hier=hierarchical_pos(
|
2070
1953
|
graph,
|
@@ -2074,9 +1957,8 @@ class lineageTree(lineageTreeLoaders):
|
|
2074
1957
|
),
|
2075
1958
|
lnks_tms=graph,
|
2076
1959
|
ax=ax,
|
2077
|
-
**kwargs,
|
2078
1960
|
)
|
2079
|
-
return
|
1961
|
+
return ax.get_figure(), ax
|
2080
1962
|
|
2081
1963
|
# def DTW(self, t1, t2, max_w=None, start_delay=None, end_delay=None,
|
2082
1964
|
# metric='euclidian', **kwargs):
|
@@ -2729,6 +2611,7 @@ class lineageTree(lineageTreeLoaders):
|
|
2729
2611
|
warnings.warn(
|
2730
2612
|
"Error: not possible to show alignment in PCA projection !",
|
2731
2613
|
UserWarning,
|
2614
|
+
stacklevel=2,
|
2732
2615
|
)
|
2733
2616
|
|
2734
2617
|
return distance, fig
|
@@ -2824,6 +2707,8 @@ class lineageTree(lineageTreeLoaders):
|
|
2824
2707
|
self.read_from_ASTEC(file_format, eigen)
|
2825
2708
|
elif file_type == "csv":
|
2826
2709
|
self.read_from_csv(file_format, z_mult, link=1, delim=delim)
|
2710
|
+
elif file_type == "bao":
|
2711
|
+
self.read_C_elegans_bao(file_format)
|
2827
2712
|
elif file_format and file_format.endswith(".lT"):
|
2828
2713
|
with open(file_format, "br") as f:
|
2829
2714
|
tmp = pkl.load(f)
|
@@ -2834,15 +2719,5 @@ class lineageTree(lineageTreeLoaders):
|
|
2834
2719
|
if self.name is None:
|
2835
2720
|
try:
|
2836
2721
|
self.name = Path(file_format).stem
|
2837
|
-
except:
|
2722
|
+
except TypeError:
|
2838
2723
|
self.name = Path(file_format[0]).stem
|
2839
|
-
if [] in self.successor.values():
|
2840
|
-
successors = list(self.successor.keys())
|
2841
|
-
for succ in successors:
|
2842
|
-
if self[succ] == []:
|
2843
|
-
self.successor.pop(succ)
|
2844
|
-
if [] in self.predecessor.values():
|
2845
|
-
predecessors = list(self.predecessor.keys())
|
2846
|
-
for succ in predecessors:
|
2847
|
-
if self[succ] == []:
|
2848
|
-
self.predecessor.pop(succ)
|
@@ -2,6 +2,7 @@ import os
|
|
2
2
|
import pickle as pkl
|
3
3
|
import warnings
|
4
4
|
from functools import partial
|
5
|
+
|
5
6
|
import numpy as np
|
6
7
|
|
7
8
|
try:
|
@@ -21,21 +22,19 @@ class lineageTreeManager:
|
|
21
22
|
self.lineagetrees = {}
|
22
23
|
self.lineageTree_counter = 0
|
23
24
|
self.registered = {}
|
24
|
-
self.greatest_common_divisors = {}
|
25
25
|
|
26
26
|
def __next__(self):
|
27
27
|
self.lineageTree_counter += 1
|
28
28
|
return self.lineageTree_counter - 1
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
)
|
30
|
+
@property
|
31
|
+
def gcd(self):
|
32
|
+
if len(self.lineagetrees) >= 1:
|
33
|
+
all_time_res = [
|
34
|
+
embryo._time_resolution
|
35
|
+
for embryo in self.lineagetrees.values()
|
36
|
+
]
|
37
|
+
return np.gcd.reduce(all_time_res)
|
39
38
|
|
40
39
|
def add(
|
41
40
|
self, other_tree: lineageTree, name: str = "", classification: str = ""
|
@@ -64,7 +63,6 @@ class lineageTreeManager:
|
|
64
63
|
name = f"Lineagetree {next(self)}"
|
65
64
|
self.lineagetrees[name] = other_tree
|
66
65
|
self.lineagetrees[name].name = name
|
67
|
-
# self.greatest_common_divisors[name] = gcd
|
68
66
|
else:
|
69
67
|
raise Exception(
|
70
68
|
"Please add a LineageTree object or add time resolution to the LineageTree added."
|
@@ -120,7 +118,7 @@ class lineageTreeManager:
|
|
120
118
|
n2: int,
|
121
119
|
embryo_2,
|
122
120
|
end_time2: int,
|
123
|
-
style="
|
121
|
+
style="simple",
|
124
122
|
downsample: int = 2,
|
125
123
|
registration=None, # will be added as a later feature
|
126
124
|
):
|
@@ -141,21 +139,48 @@ class lineageTreeManager:
|
|
141
139
|
"""
|
142
140
|
|
143
141
|
tree = tree_style[style].value
|
142
|
+
lcm = (
|
143
|
+
self.lineagetrees[embryo_1]._time_resolution
|
144
|
+
* self.lineagetrees[embryo_2]._time_resolution
|
145
|
+
) / self.gcd
|
146
|
+
if style == "downsampled":
|
147
|
+
if downsample % (lcm / 10) != 0:
|
148
|
+
raise Exception(
|
149
|
+
f"Use a valid downsampling rate (multiple of {lcm/10})"
|
150
|
+
)
|
151
|
+
time_res = [
|
152
|
+
downsample / self.lineagetrees[embryo_2].time_resolution,
|
153
|
+
downsample / self.lineagetrees[embryo_1].time_resolution,
|
154
|
+
]
|
155
|
+
elif style == "full":
|
156
|
+
time_res = [
|
157
|
+
lcm / 10 / self.lineagetrees[embryo_2].time_resolution,
|
158
|
+
lcm / 10 / self.lineagetrees[embryo_1].time_resolution,
|
159
|
+
]
|
160
|
+
else:
|
161
|
+
time_res = [
|
162
|
+
self.lineagetrees[embryo_1]._time_resolution,
|
163
|
+
self.lineagetrees[embryo_2]._time_resolution,
|
164
|
+
]
|
165
|
+
time_res = [i / self.gcd for i in time_res]
|
144
166
|
tree1 = tree(
|
145
167
|
lT=self.lineagetrees[embryo_1],
|
146
168
|
downsample=downsample,
|
147
169
|
end_time=end_time1,
|
148
170
|
root=n1,
|
171
|
+
time_scale=time_res[0],
|
149
172
|
)
|
150
173
|
tree2 = tree(
|
151
174
|
lT=self.lineagetrees[embryo_2],
|
152
175
|
downsample=downsample,
|
153
176
|
end_time=end_time2,
|
154
177
|
root=n2,
|
178
|
+
time_scale=time_res[1],
|
155
179
|
)
|
156
180
|
delta = tree1.delta
|
157
181
|
_, times1 = tree1.tree
|
158
182
|
_, times2 = tree2.tree
|
183
|
+
|
159
184
|
nodes1, adj1, corres1 = tree1.edist
|
160
185
|
nodes2, adj2, corres2 = tree2.edist
|
161
186
|
if len(nodes1) == len(nodes2) == 0:
|