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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: LineageTree
3
- Version: 1.7.0
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.7.0"
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 = [
@@ -1,4 +1,4 @@
1
- __version__ = "1.7.0"
1
+ __version__ = "1.8.0"
2
2
  from .lineageTree import lineageTree
3
3
  from .lineageTreeManager import lineageTreeManager
4
4
 
@@ -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(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
+ )
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 ti Important only if reverse= True
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(new_root2, length)
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 1", stacklevel=2)
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
- if not hasattr(self, "_roots"):
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 set(self.predecessor).difference(self.successor)
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
- i: "Unlabeled"
413
- for i in self.roots
414
- for l in self.find_leaves(i)
415
- 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])
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("%f\n" % (manual_labels.get(C, default_label)))
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
- "%f\n"
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 "%s"\n' % p_name)
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 "%s"\n' % p_name)
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=True):
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 succ is not None:
1341
+ succ = self.successor.get(curr, [])
1342
+ if not succ:
1479
1343
  leaves.add(curr)
1480
- else:
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 figure, ax
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, 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,
2010
1875
  )
2011
1876
  for i, g in graphs.items()
2012
1877
  }
2013
- ncols = int(len(graphs) // nrows) + (+np.sign(len(graphs) % nrows))
2014
- figure, axes = plt.subplots(
2015
- figsize=figsize, nrows=nrows, ncols=ncols, dpi=dpi, sharey=True
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 figure, axes, ax2root
1926
+ return axes.flatten()[0].get_figure(), axes, ax2root
2055
1927
 
2056
- 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
+ ):
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
- 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
+ )
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 figure, ax
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
- 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
- )
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="fragmented",
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: