LineageTree 1.6.1__py3-none-any.whl → 1.8.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.
@@ -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
@@ -69,7 +70,12 @@ class lineageTree(lineageTreeLoaders):
69
70
  sub = set(self.get_sub_tree(node))
70
71
  specific_leaves = sub.intersection(self.leaves)
71
72
  for leaf in specific_leaves:
72
- self.add_branch(leaf, self.t_e - self.time[leaf], reverse=True)
73
+ self.add_branch(
74
+ leaf,
75
+ (self.t_e - self.time[leaf]),
76
+ reverse=True,
77
+ move_timepoints=True,
78
+ )
73
79
 
74
80
  ###TODO pos can be callable and stay motionless (copy the position of the succ node, use something like optical flow)
75
81
  def add_branch(
@@ -87,8 +93,8 @@ class lineageTree(lineageTreeLoaders):
87
93
  pred (int): Id of the successor (predecessor if reverse is False)
88
94
  length (int): The length of the new branch.
89
95
  pos (np.ndarray, optional): The new position of the branch. Defaults to None.
90
- move_timepoints (bool): Moves the ti Important only if reverse= True
91
- reverese (bool): If reverse will add a successor branch instead of a predecessor branch
96
+ move_timepoints (bool): Moves the time, important only if reverse= True
97
+ reverese (bool): If True will create a branch that goes forwards in time otherwise backwards.
92
98
  Returns:
93
99
  (int): Id of the first node of the sublineage.
94
100
  """
@@ -129,19 +135,20 @@ class lineageTree(lineageTreeLoaders):
129
135
  )
130
136
  pred = _next
131
137
  else:
132
- for t in range(length):
138
+ for _ in range(length):
133
139
  _next = self.add_node(
134
- time + t, succ=pred, pos=self.pos[original], reverse=False
140
+ self.time[pred] + 1,
141
+ succ=pred,
142
+ pos=self.pos[original],
143
+ reverse=False,
135
144
  )
136
145
  pred = _next
146
+ self.successor[self.get_cycle(pred)[-1]] = []
137
147
  self.labels[pred] = "New branch"
138
148
  if self.time[pred] == self.t_b:
139
- self.roots.add(pred)
140
149
  self.labels[pred] = "New branch"
141
150
  if original in self.roots and reverse is True:
142
- self.roots.add(pred)
143
151
  self.labels[pred] = "New branch"
144
- self.roots.remove(original)
145
152
  self.labels.pop(original, -1)
146
153
  self.t_e = max(self.time_nodes)
147
154
  return pred
@@ -163,9 +170,7 @@ class lineageTree(lineageTreeLoaders):
163
170
  self.predecessor.pop(new_lT)
164
171
  label_of_root = self.labels.get(cycle[0], cycle[0])
165
172
  self.labels[cycle[0]] = f"L-Split {label_of_root}"
166
- new_tr = self.add_branch(
167
- new_lT, len(cycle) + 1, move_timepoints=False
168
- )
173
+ new_tr = self.add_branch(new_lT, len(cycle), move_timepoints=False)
169
174
  self.roots.add(new_tr)
170
175
  self.labels[new_tr] = f"R-Split {label_of_root}"
171
176
  return new_tr
@@ -205,7 +210,10 @@ class lineageTree(lineageTreeLoaders):
205
210
  self.remove_nodes(new_root1)
206
211
  self.successor[new_root2].append(next_root1)
207
212
  self.predecessor[next_root1] = [new_root2]
208
- new_branch = self.add_branch(new_root2, length)
213
+ new_branch = self.add_branch(
214
+ new_root2,
215
+ length - 1,
216
+ )
209
217
  self.labels[new_branch] = f"Fusion of {new_root1} and {new_root2}"
210
218
  return new_branch
211
219
 
@@ -317,7 +325,7 @@ class lineageTree(lineageTreeLoaders):
317
325
  new_length (int): The new length of the tree.
318
326
  """
319
327
  if new_length <= 1:
320
- warnings.warn("New length should be more than 1")
328
+ warnings.warn("New length should be more than 1", stacklevel=2)
321
329
  return None
322
330
  cycle = self.get_cycle(node)
323
331
  length = len(cycle)
@@ -325,7 +333,10 @@ class lineageTree(lineageTreeLoaders):
325
333
  if length == 1 and new_length != 1:
326
334
  pred = self.predecessor.pop(node, None)
327
335
  new_node = self.add_branch(
328
- node, length=new_length, move_timepoints=True, reverse=False
336
+ node,
337
+ length=new_length - 1,
338
+ move_timepoints=True,
339
+ reverse=False,
329
340
  )
330
341
  if pred:
331
342
  self.successor[pred[0]].remove(node)
@@ -365,11 +376,40 @@ class lineageTree(lineageTreeLoaders):
365
376
  else:
366
377
  return None
367
378
 
379
+ @property
380
+ def time_resolution(self):
381
+ if not hasattr(self, "_time_resolution"):
382
+ self.time_resolution = 1
383
+ return self._time_resolution / 10
384
+
385
+ @time_resolution.setter
386
+ def time_resolution(self, time_resolution):
387
+ if time_resolution is not None:
388
+ self._time_resolution = int(time_resolution * 10)
389
+ else:
390
+ warnings.warn("Time resolution set to default 0", stacklevel=2)
391
+ self._time_resolution = 10
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
+
368
410
  @property
369
411
  def roots(self):
370
- if not hasattr(self, "_roots"):
371
- self._roots = set(self.nodes).difference(self.predecessor)
372
- return self._roots
412
+ return set(self.nodes).difference(self.predecessor)
373
413
 
374
414
  @property
375
415
  def edges(self):
@@ -377,7 +417,7 @@ class lineageTree(lineageTreeLoaders):
377
417
 
378
418
  @property
379
419
  def leaves(self):
380
- return set(self.predecessor).difference(self.successor)
420
+ return {p for p, s in self.successor.items() if s == []}
381
421
 
382
422
  @property
383
423
  def labels(self):
@@ -388,10 +428,10 @@ class lineageTree(lineageTreeLoaders):
388
428
  }
389
429
  else:
390
430
  self._labels = {
391
- i: "Unlabeled"
392
- for i in self.roots
393
- for l in self.find_leaves(i)
394
- if abs(self.time[l] - self.time[i])
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])
395
435
  >= abs(self.t_e - self.t_b) / 4
396
436
  }
397
437
  return self._labels
@@ -483,7 +523,7 @@ class lineageTree(lineageTreeLoaders):
483
523
 
484
524
  f.write("@5\n")
485
525
  for C in self.to_take_time[t]:
486
- f.write("%f\n" % (manual_labels.get(C, default_label)))
526
+ f.write(f"{manual_labels.get(C, default_label):f}\n")
487
527
  f.write(f"{0:f}\n")
488
528
 
489
529
  f.write("@6\n")
@@ -502,8 +542,7 @@ class lineageTree(lineageTreeLoaders):
502
542
  f.write("@7\n")
503
543
  for C in self.to_take_time[t]:
504
544
  f.write(
505
- "%f\n"
506
- % (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"
507
546
  )
508
547
 
509
548
  f.write("@8\n")
@@ -932,10 +971,10 @@ class lineageTree(lineageTreeLoaders):
932
971
  if node_properties:
933
972
  for p_name, (p_dict, default) in node_properties.items():
934
973
  if isinstance(list(p_dict.values())[0], str):
935
- f.write('(property 0 string "%s"\n' % p_name)
974
+ f.write(f'(property 0 string "{p_name}"\n')
936
975
  f.write(f"\t(default {default} {default})\n")
937
976
  elif isinstance(list(p_dict.values())[0], Number):
938
- f.write('(property 0 double "%s"\n' % p_name)
977
+ f.write(f'(property 0 double "{p_name}"\n')
939
978
  f.write('\t(default "0" "0")\n')
940
979
  for n in nodes_to_use:
941
980
  f.write(
@@ -1009,148 +1048,6 @@ class lineageTree(lineageTreeLoaders):
1009
1048
 
1010
1049
  f.close()
1011
1050
 
1012
- def read_from_binary(self, fname: str):
1013
- """
1014
- Reads a binary lineageTree file name.
1015
- Format description: see self.to_binary
1016
-
1017
- Args:
1018
- fname: string, path to the binary file
1019
- reverse_time: bool, not used
1020
- """
1021
- q_size = struct.calcsize("q")
1022
- H_size = struct.calcsize("H")
1023
- d_size = struct.calcsize("d")
1024
-
1025
- with open(fname, "rb") as f:
1026
- len_tree = struct.unpack("q", f.read(q_size))[0]
1027
- len_time = struct.unpack("q", f.read(q_size))[0]
1028
- len_pos = struct.unpack("q", f.read(q_size))[0]
1029
- number_sequence = list(
1030
- struct.unpack("q" * len_tree, f.read(q_size * len_tree))
1031
- )
1032
- time_sequence = list(
1033
- struct.unpack("H" * len_time, f.read(H_size * len_time))
1034
- )
1035
- pos_sequence = np.array(
1036
- struct.unpack("d" * len_pos, f.read(d_size * len_pos))
1037
- )
1038
-
1039
- f.close()
1040
-
1041
- successor = {}
1042
- predecessor = {}
1043
- time = {}
1044
- time_nodes = {}
1045
- time_edges = {}
1046
- pos = {}
1047
- is_root = {}
1048
- nodes = []
1049
- edges = []
1050
- waiting_list = []
1051
- print(number_sequence[0])
1052
- i = 0
1053
- done = False
1054
- if max(number_sequence[::2]) == -1:
1055
- tmp = number_sequence[1::2]
1056
- if len(tmp) * 3 == len(pos_sequence) == len(time_sequence) * 3:
1057
- time = dict(list(zip(tmp, time_sequence)))
1058
- for c, t in time.items():
1059
- time_nodes.setdefault(t, set()).add(c)
1060
- pos = dict(
1061
- list(zip(tmp, np.reshape(pos_sequence, (len_time, 3))))
1062
- )
1063
- is_root = {c: True for c in tmp}
1064
- nodes = tmp
1065
- done = True
1066
- while (
1067
- i < len(number_sequence) and not done
1068
- ): # , c in enumerate(number_sequence[:-1]):
1069
- c = number_sequence[i]
1070
- if c == -1:
1071
- if waiting_list != []:
1072
- prev_mother = waiting_list.pop()
1073
- successor[prev_mother].insert(0, number_sequence[i + 1])
1074
- edges.append((prev_mother, number_sequence[i + 1]))
1075
- time_edges.setdefault(t, set()).add(
1076
- (prev_mother, number_sequence[i + 1])
1077
- )
1078
- is_root[number_sequence[i + 1]] = False
1079
- t = time[prev_mother] + 1
1080
- else:
1081
- t = time_sequence.pop(0)
1082
- is_root[number_sequence[i + 1]] = True
1083
-
1084
- elif c == -2:
1085
- successor[waiting_list[-1]] = [number_sequence[i + 1]]
1086
- edges.append((waiting_list[-1], number_sequence[i + 1]))
1087
- time_edges.setdefault(t, set()).add(
1088
- (waiting_list[-1], number_sequence[i + 1])
1089
- )
1090
- is_root[number_sequence[i + 1]] = False
1091
- pos[waiting_list[-1]] = pos_sequence[:3]
1092
- pos_sequence = pos_sequence[3:]
1093
- nodes.append(waiting_list[-1])
1094
- time[waiting_list[-1]] = t
1095
- time_nodes.setdefault(t, set()).add(waiting_list[-1])
1096
- t += 1
1097
-
1098
- elif number_sequence[i + 1] >= 0:
1099
- successor[c] = [number_sequence[i + 1]]
1100
- edges.append((c, number_sequence[i + 1]))
1101
- time_edges.setdefault(t, set()).add(
1102
- (c, number_sequence[i + 1])
1103
- )
1104
- is_root[number_sequence[i + 1]] = False
1105
- pos[c] = pos_sequence[:3]
1106
- pos_sequence = pos_sequence[3:]
1107
- nodes.append(c)
1108
- time[c] = t
1109
- time_nodes.setdefault(t, set()).add(c)
1110
- t += 1
1111
-
1112
- elif number_sequence[i + 1] == -2:
1113
- waiting_list += [c]
1114
-
1115
- elif number_sequence[i + 1] == -1:
1116
- pos[c] = pos_sequence[:3]
1117
- pos_sequence = pos_sequence[3:]
1118
- nodes.append(c)
1119
- time[c] = t
1120
- time_nodes.setdefault(t, set()).add(c)
1121
- t += 1
1122
- i += 1
1123
- if waiting_list != []:
1124
- prev_mother = waiting_list.pop()
1125
- successor[prev_mother].insert(0, number_sequence[i + 1])
1126
- edges.append((prev_mother, number_sequence[i + 1]))
1127
- time_edges.setdefault(t, set()).add(
1128
- (prev_mother, number_sequence[i + 1])
1129
- )
1130
- if i + 1 < len(number_sequence):
1131
- is_root[number_sequence[i + 1]] = False
1132
- t = time[prev_mother] + 1
1133
- else:
1134
- if len(time_sequence) > 0:
1135
- t = time_sequence.pop(0)
1136
- if i + 1 < len(number_sequence):
1137
- is_root[number_sequence[i + 1]] = True
1138
- i += 1
1139
-
1140
- predecessor = {vi: [k] for k, v in successor.items() for vi in v}
1141
-
1142
- self.successor = successor
1143
- self.predecessor = predecessor
1144
- self.time = time
1145
- self.time_nodes = time_nodes
1146
- self.time_edges = time_edges
1147
- self.pos = pos
1148
- self.nodes = set(nodes)
1149
- self.t_b = min(time_nodes.keys())
1150
- self.t_e = max(time_nodes.keys())
1151
- self.is_root = is_root
1152
- self.max_id = max(self.nodes)
1153
-
1154
1051
  def write(self, fname: str):
1155
1052
  """
1156
1053
  Write a lineage tree on disk as an .lT file.
@@ -1165,7 +1062,7 @@ class lineageTree(lineageTreeLoaders):
1165
1062
  f.close()
1166
1063
 
1167
1064
  @classmethod
1168
- def load(clf, fname: str, rm_empty_lists=True):
1065
+ def load(clf, fname: str, rm_empty_lists=False):
1169
1066
  """
1170
1067
  Loading a lineage tree from a ".lT" file.
1171
1068
 
@@ -1178,18 +1075,8 @@ class lineageTree(lineageTreeLoaders):
1178
1075
  with open(fname, "br") as f:
1179
1076
  lT = pkl.load(f)
1180
1077
  f.close()
1181
- if rm_empty_lists:
1182
- if [] in lT.successor.values():
1183
- for node, succ in lT.successor.items():
1184
- if succ == []:
1185
- lT.successor.pop(node)
1186
- if [] in lT.predecessor.values():
1187
- for node, succ in lT.predecessor.items():
1188
- if succ == []:
1189
- lT.predecessor.pop(node)
1190
- lT.t_e = max(lT.time_nodes)
1191
- lT.t_b = min(lT.time_nodes)
1192
- warnings.warn("Empty lists have been removed")
1078
+ if not hasattr(lT, "time_resolution"):
1079
+ lT.time_resolution = None
1193
1080
  return lT
1194
1081
 
1195
1082
  def get_idx3d(self, t: int) -> tuple:
@@ -1392,8 +1279,10 @@ class lineageTree(lineageTreeLoaders):
1392
1279
  while to_do:
1393
1280
  current = to_do.pop()
1394
1281
  track = self.get_successors(current, end_time=end_time)
1395
- branches += [track]
1396
- to_do += self[track[-1]]
1282
+ # if len(track) != 1 or self.time[current] <= end_time:
1283
+ if self.time[track[-1]] <= end_time:
1284
+ branches += [track]
1285
+ to_do += self[track[-1]]
1397
1286
  return branches
1398
1287
 
1399
1288
  def get_all_tracks(self, force_recompute: bool = False) -> list:
@@ -1449,11 +1338,10 @@ class lineageTree(lineageTreeLoaders):
1449
1338
  leaves = set()
1450
1339
  while to_do:
1451
1340
  curr = to_do.pop()
1452
- succ = self.successor.get(curr)
1453
- if succ is not None:
1341
+ succ = self.successor.get(curr, [])
1342
+ if not succ:
1454
1343
  leaves.add(curr)
1455
- else:
1456
- to_do += succ
1344
+ to_do += succ
1457
1345
  return leaves
1458
1346
 
1459
1347
  def get_sub_tree(
@@ -1629,6 +1517,8 @@ class lineageTree(lineageTreeLoaders):
1629
1517
  at a given time `time`.
1630
1518
 
1631
1519
  If there is no ancestor, returns `-1`
1520
+ If time is None return the root of the sub tree that spawns
1521
+ the node n.
1632
1522
 
1633
1523
  Args:
1634
1524
  n (int): node for which to look the ancestor
@@ -1645,7 +1535,9 @@ class lineageTree(lineageTreeLoaders):
1645
1535
  if time is None:
1646
1536
  time = self.t_b
1647
1537
  ancestor = n
1648
- while time < self.time.get(ancestor, -1):
1538
+ while (
1539
+ time < self.time.get(ancestor, -1) and ancestor in self.predecessor
1540
+ ):
1649
1541
  ancestor = self.predecessor.get(ancestor, [-1])[0]
1650
1542
  return ancestor
1651
1543
 
@@ -1674,10 +1566,11 @@ class lineageTree(lineageTreeLoaders):
1674
1566
  def unordered_tree_edit_distances_at_time_t(
1675
1567
  self,
1676
1568
  t: int,
1677
- delta: callable = None,
1678
- norm: callable = None,
1679
- recompute: bool = False,
1680
1569
  end_time: int = None,
1570
+ style="simple",
1571
+ downsample: int = 2,
1572
+ normalize: bool = True,
1573
+ recompute: bool = False,
1681
1574
  ) -> dict:
1682
1575
  """
1683
1576
  Compute all the pairwise unordered tree edit distances from Zhang 996 between the trees spawned at time `t`
@@ -1704,7 +1597,12 @@ class lineageTree(lineageTreeLoaders):
1704
1597
  for n1, n2 in combinations(roots, 2):
1705
1598
  key = tuple(sorted((n1, n2)))
1706
1599
  self.uted[t][key] = self.unordered_tree_edit_distance(
1707
- n1, n2, end_time=end_time
1600
+ n1,
1601
+ n2,
1602
+ end_time=end_time,
1603
+ style=style,
1604
+ downsample=downsample,
1605
+ normalize=normalize,
1708
1606
  )
1709
1607
  return self.uted[t]
1710
1608
 
@@ -1713,11 +1611,11 @@ class lineageTree(lineageTreeLoaders):
1713
1611
  n1: int,
1714
1612
  n2: int,
1715
1613
  end_time: int = None,
1716
- style="fragmented",
1717
- node_lengths: tuple = (1, 5, 7),
1614
+ norm: Union["max", "sum", None] = "max",
1615
+ style="simple",
1616
+ downsample: int = 2,
1718
1617
  ) -> float:
1719
1618
  """
1720
- TODO: Add option for choosing which tree aproximation should be used (Full, simple, comp)
1721
1619
  Compute the unordered tree edit distance from Zhang 1996 between the trees spawned
1722
1620
  by two nodes `n1` and `n2`. The topology of the trees are compared and the matching
1723
1621
  cost is given by the function delta (see edist doc for more information).
@@ -1736,10 +1634,18 @@ class lineageTree(lineageTreeLoaders):
1736
1634
 
1737
1635
  tree = tree_style[style].value
1738
1636
  tree1 = tree(
1739
- lT=self, node_length=node_lengths, end_time=end_time, root=n1
1637
+ lT=self,
1638
+ downsample=downsample,
1639
+ end_time=end_time,
1640
+ root=n1,
1641
+ time_scale=1,
1740
1642
  )
1741
1643
  tree2 = tree(
1742
- lT=self, node_length=node_lengths, end_time=end_time, root=n2
1644
+ lT=self,
1645
+ downsample=downsample,
1646
+ end_time=end_time,
1647
+ root=n2,
1648
+ time_scale=1,
1743
1649
  )
1744
1650
  delta = tree1.delta
1745
1651
  _, times1 = tree1.tree
@@ -1763,71 +1669,140 @@ class lineageTree(lineageTreeLoaders):
1763
1669
  times1=times1,
1764
1670
  times2=times2,
1765
1671
  )
1672
+ norm1 = tree1.get_norm()
1673
+ norm2 = tree2.get_norm()
1674
+ norm_dict = {"max": max, "sum": sum, "None": lambda x: 1}
1675
+ if norm is None:
1676
+ norm = "None"
1677
+ if norm not in norm_dict:
1678
+ raise Warning(
1679
+ "Select a viable normalization method (max, sum, None)"
1680
+ )
1681
+ return uted.uted(
1682
+ nodes1, adj1, nodes2, adj2, delta=delta_tmp
1683
+ ) / norm_dict[norm]([norm1, norm2])
1766
1684
 
1767
- return uted.uted(nodes1, adj1, nodes2, adj2, delta=delta_tmp) / max(
1768
- tree1.get_norm(), tree2.get_norm()
1685
+ @staticmethod
1686
+ def __plot_nodes(
1687
+ hier, selected_nodes, color, size, ax, default_color="black", **kwargs
1688
+ ):
1689
+ """
1690
+ Private method that plots the nodes of the tree.
1691
+ """
1692
+ hier_unselected = np.array(
1693
+ [v for k, v in hier.items() if k not in selected_nodes]
1769
1694
  )
1695
+ if hier_unselected.any():
1696
+ ax.scatter(
1697
+ *hier_unselected.T,
1698
+ s=size,
1699
+ zorder=10,
1700
+ color=default_color,
1701
+ **kwargs,
1702
+ )
1703
+ if selected_nodes.intersection(hier.keys()):
1704
+ hier_selected = np.array(
1705
+ [v for k, v in hier.items() if k in selected_nodes]
1706
+ )
1707
+ ax.scatter(
1708
+ *hier_selected.T, s=size, zorder=10, color=color, **kwargs
1709
+ )
1710
+
1711
+ @staticmethod
1712
+ def __plot_edges(
1713
+ hier,
1714
+ lnks_tms,
1715
+ selected_edges,
1716
+ color,
1717
+ ax,
1718
+ default_color="black",
1719
+ **kwargs,
1720
+ ):
1721
+ """
1722
+ Private method that plots the edges of the tree.
1723
+ """
1724
+ x, y = [], []
1725
+ for pred, succs in lnks_tms["links"].items():
1726
+ for succ in succs:
1727
+ if pred not in selected_edges or succ not in selected_edges:
1728
+ x.extend((hier[succ][0], hier[pred][0], None))
1729
+ y.extend((hier[succ][1], hier[pred][1], None))
1730
+ ax.plot(x, y, linewidth=0.3, zorder=0.1, c=default_color, **kwargs)
1731
+ x, y = [], []
1732
+ for pred, succs in lnks_tms["links"].items():
1733
+ for succ in succs:
1734
+ if pred in selected_edges and succ in selected_edges:
1735
+ x.extend((hier[succ][0], hier[pred][0], None))
1736
+ y.extend((hier[succ][1], hier[pred][1], None))
1737
+ ax.plot(x, y, linewidth=0.3, zorder=0.2, c=color, **kwargs)
1770
1738
 
1771
1739
  def draw_tree_graph(
1772
1740
  self,
1773
1741
  hier,
1774
1742
  lnks_tms,
1775
- selected_cells=None,
1776
- color="magenta",
1743
+ selected_nodes=None,
1744
+ selected_edges=None,
1745
+ color_of_nodes="magenta",
1746
+ color_of_edges=None,
1747
+ size=10,
1777
1748
  ax=None,
1778
- figure=None,
1749
+ default_color="black",
1779
1750
  **kwargs,
1780
1751
  ):
1781
- if selected_cells is None:
1782
- selected_cells = []
1752
+ """Function to plot the tree graph.
1753
+
1754
+ Args:
1755
+ hier (dict): Dictinary that contains the positions of all nodes.
1756
+ lnks_tms (dict): 2 dictionaries: 1 contains all links from start of life cycle to end of life cycle and
1757
+ the succesors of each cell.
1758
+ 1 contains the length of each life cycle.
1759
+ selected_nodes (list|set, optional): Which cells are to be selected (Painted with a different color). Defaults to None.
1760
+ selected_edges (list|set, optional): Which edges are to be selected (Painted with a different color). Defaults to None.
1761
+ color_of_nodes (str, optional): Color of selected nodes. Defaults to "magenta".
1762
+ color_of_edges (_type_, optional): Color of selected edges. Defaults to None.
1763
+ size (int, optional): Size of the nodes. Defaults to 10.
1764
+ ax (_type_, optional): Plot the graph on existing ax. Defaults to None.
1765
+ figure (_type_, optional): _description_. Defaults to None.
1766
+ default_color (str, optional): Default color of nodes. Defaults to "black".
1767
+
1768
+ Returns:
1769
+ figure, ax: The matplotlib figure and ax object.
1770
+ """
1771
+ if selected_nodes is None:
1772
+ selected_nodes = []
1773
+ if selected_edges is None:
1774
+ selected_edges = []
1783
1775
  if ax is None:
1784
1776
  figure, ax = plt.subplots()
1785
1777
  else:
1786
1778
  ax.clear()
1787
- if not isinstance(selected_cells, set):
1788
- selected_cells = set(selected_cells)
1789
- hier_unselected = {
1790
- k: v for k, v in hier.items() if k not in selected_cells
1791
- }
1792
- hier_selected = {k: v for k, v in hier.items() if k in selected_cells}
1793
- unselected = np.array(tuple(hier_unselected.values()))
1794
- x = []
1795
- y = []
1796
- if hier_unselected:
1797
- for pred, succs in lnks_tms["links"].items():
1798
- if pred not in selected_cells:
1799
- for succ in succs:
1800
- x.extend((hier[succ][0], hier[pred][0], None))
1801
- y.extend((hier[succ][1], hier[pred][1], None))
1802
- ax.plot(x, y, c="black", linewidth=0.3, zorder=0.5, **kwargs)
1803
- ax.scatter(
1804
- *unselected.T,
1805
- s=0.1,
1806
- c="black",
1807
- zorder=1,
1808
- **kwargs,
1809
- )
1810
- if selected_cells:
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
- )
1779
+ if not isinstance(selected_nodes, set):
1780
+ selected_nodes = set(selected_nodes)
1781
+ if not isinstance(selected_edges, set):
1782
+ selected_edges = set(selected_edges)
1783
+ self.__plot_nodes(
1784
+ hier,
1785
+ selected_nodes,
1786
+ color_of_nodes,
1787
+ size=size,
1788
+ ax=ax,
1789
+ default_color=default_color,
1790
+ **kwargs,
1791
+ )
1792
+ if not color_of_edges:
1793
+ color_of_edges = color_of_nodes
1794
+ self.__plot_edges(
1795
+ hier,
1796
+ lnks_tms,
1797
+ selected_edges,
1798
+ color_of_edges,
1799
+ ax,
1800
+ default_color=default_color,
1801
+ **kwargs,
1802
+ )
1828
1803
  ax.get_yaxis().set_visible(False)
1829
1804
  ax.get_xaxis().set_visible(False)
1830
- return figure, ax
1805
+ return ax.get_figure(), ax
1831
1806
 
1832
1807
  def to_simple_graph(self, node=None, start_time: int = None):
1833
1808
  """Generates a dictionary of graphs where the keys are the index of the graph and
@@ -1862,8 +1837,8 @@ class lineageTree(lineageTreeLoaders):
1862
1837
  figsize=(10, 15),
1863
1838
  dpi=100,
1864
1839
  fontsize=15,
1865
- figure=None,
1866
1840
  axes=None,
1841
+ vert_gap=1,
1867
1842
  **kwargs,
1868
1843
  ):
1869
1844
  """Plots all lineages.
@@ -1893,14 +1868,24 @@ class lineageTree(lineageTreeLoaders):
1893
1868
  )
1894
1869
  pos = {
1895
1870
  i: hierarchical_pos(
1896
- g, g["root"], ycenter=-int(self.time[g["root"]])
1871
+ g,
1872
+ g["root"],
1873
+ ycenter=-int(self.time[g["root"]]),
1874
+ vert_gap=vert_gap,
1897
1875
  )
1898
1876
  for i, g in graphs.items()
1899
1877
  }
1900
- ncols = int(len(graphs) // nrows) + (+np.sign(len(graphs) % nrows))
1901
- figure, axes = plt.subplots(
1902
- figsize=figsize, nrows=nrows, ncols=ncols, dpi=dpi, sharey=True
1903
- )
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
+ )
1904
1889
  flat_axes = axes.flatten()
1905
1890
  ax2root = {}
1906
1891
  min_width, min_height = float("inf"), float("inf")
@@ -1938,9 +1923,17 @@ class lineageTree(lineageTreeLoaders):
1938
1923
  },
1939
1924
  )
1940
1925
  [figure.delaxes(ax) for ax in axes.flatten() if not ax.has_data()]
1941
- return figure, axes, ax2root
1926
+ return axes.flatten()[0].get_figure(), axes, ax2root
1942
1927
 
1943
- def plot_node(self, node, figsize=(4, 7), dpi=150, vert_gap=2, **kwargs):
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
+ ):
1944
1937
  """Plots the subtree spawn by a node.
1945
1938
 
1946
1939
  Args:
@@ -1951,7 +1944,10 @@ class lineageTree(lineageTreeLoaders):
1951
1944
  if len(graph) > 1:
1952
1945
  raise Warning("Please enter only one node")
1953
1946
  graph = graph[0]
1954
- figure, ax = plt.subplots(nrows=1, ncols=1, figsize=figsize, dpi=dpi)
1947
+ if not ax:
1948
+ figure, ax = plt.subplots(
1949
+ nrows=1, ncols=1, figsize=figsize, dpi=dpi
1950
+ )
1955
1951
  self.draw_tree_graph(
1956
1952
  hier=hierarchical_pos(
1957
1953
  graph,
@@ -1962,7 +1958,7 @@ class lineageTree(lineageTreeLoaders):
1962
1958
  lnks_tms=graph,
1963
1959
  ax=ax,
1964
1960
  )
1965
- return figure, ax
1961
+ return ax.get_figure(), ax
1966
1962
 
1967
1963
  # def DTW(self, t1, t2, max_w=None, start_delay=None, end_delay=None,
1968
1964
  # metric='euclidian', **kwargs):
@@ -2615,6 +2611,7 @@ class lineageTree(lineageTreeLoaders):
2615
2611
  warnings.warn(
2616
2612
  "Error: not possible to show alignment in PCA projection !",
2617
2613
  UserWarning,
2614
+ stacklevel=2,
2618
2615
  )
2619
2616
 
2620
2617
  return distance, fig
@@ -2636,6 +2633,7 @@ class lineageTree(lineageTreeLoaders):
2636
2633
  reorder: bool = False,
2637
2634
  xml_attributes: tuple = None,
2638
2635
  name: str = None,
2636
+ time_resolution: Union[int, None] = None,
2639
2637
  ):
2640
2638
  """
2641
2639
  TODO: complete the doc
@@ -2646,13 +2644,22 @@ class lineageTree(lineageTreeLoaders):
2646
2644
  file_format (str): either - path format to TGMM xmls
2647
2645
  - path to the MaMuT xml
2648
2646
  - path to the binary file
2649
- tb (int): first time point (necessary for TGMM xmls only)
2650
- te (int): last time point (necessary for TGMM xmls only)
2651
- z_mult (float): z aspect ratio if necessary (usually only for TGMM xmls)
2652
- file_type (str): type of input file. Accepts:
2647
+ tb (int, optional):first time point (necessary for TGMM xmls only)
2648
+ te (int, optional): last time point (necessary for TGMM xmls only)
2649
+ z_mult (float, optional):z aspect ratio if necessary (usually only for TGMM xmls)
2650
+ file_type (str, optional):type of input file. Accepts:
2653
2651
  'TGMM, 'ASTEC', MaMuT', 'TrackMate', 'csv', 'celegans', 'binary'
2654
2652
  default is 'binary'
2653
+ delim (str, optional): _description_. Defaults to ",".
2654
+ eigen (bool, optional): _description_. Defaults to False.
2655
+ shape (tuple, optional): _description_. Defaults to None.
2656
+ raw_size (tuple, optional): _description_. Defaults to None.
2657
+ reorder (bool, optional): _description_. Defaults to False.
2658
+ xml_attributes (tuple, optional): _description_. Defaults to None.
2659
+ name (str, optional): The name of the dataset. Defaults to None.
2660
+ 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
2661
  """
2662
+
2656
2663
  self.name = name
2657
2664
  self.time_nodes = {}
2658
2665
  self.time_edges = {}
@@ -2664,6 +2671,8 @@ class lineageTree(lineageTreeLoaders):
2664
2671
  self.pos = {}
2665
2672
  self.time_id = {}
2666
2673
  self.time = {}
2674
+ if time_resolution is not None:
2675
+ self._time_resolution = time_resolution
2667
2676
  self.kdtrees = {}
2668
2677
  self.spatial_density = {}
2669
2678
  if file_type and file_format:
@@ -2698,6 +2707,8 @@ class lineageTree(lineageTreeLoaders):
2698
2707
  self.read_from_ASTEC(file_format, eigen)
2699
2708
  elif file_type == "csv":
2700
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)
2701
2712
  elif file_format and file_format.endswith(".lT"):
2702
2713
  with open(file_format, "br") as f:
2703
2714
  tmp = pkl.load(f)
@@ -2708,15 +2719,5 @@ class lineageTree(lineageTreeLoaders):
2708
2719
  if self.name is None:
2709
2720
  try:
2710
2721
  self.name = Path(file_format).stem
2711
- except:
2722
+ except TypeError:
2712
2723
  self.name = Path(file_format[0]).stem
2713
- if [] in self.successor.values():
2714
- successors = list(self.successor.keys())
2715
- for succ in successors:
2716
- if self[succ] == []:
2717
- self.successor.pop(succ)
2718
- if [] in self.predecessor.values():
2719
- predecessors = list(self.predecessor.keys())
2720
- for succ in predecessors:
2721
- if self[succ] == []:
2722
- self.predecessor.pop(succ)