LineageTree 1.7.0__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.
LineageTree/__init__.py CHANGED
@@ -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:
LineageTree/loaders.py CHANGED
@@ -1,12 +1,28 @@
1
1
  import csv
2
2
  import os
3
3
  import pickle as pkl
4
+ import struct
4
5
  import xml.etree.ElementTree as ET
6
+ from warnings import warn
5
7
 
6
8
  import numpy as np
7
9
 
8
10
 
9
11
  class lineageTreeLoaders:
12
+ implicit_l_t = {
13
+ "AB": "P0",
14
+ "P1": "P0",
15
+ "EMS": "P1",
16
+ "P2": "P1",
17
+ "MS": "EMS",
18
+ "E": "EMS",
19
+ "C": "P2",
20
+ "P3": "P2",
21
+ "D": "P3",
22
+ "P4": "P3",
23
+ "Z2": "P4",
24
+ "Z3": "P4",
25
+ }
10
26
 
11
27
  def read_from_csv(
12
28
  self, file_path: str, z_mult: float, link: int = 1, delim: str = ","
@@ -65,7 +81,6 @@ class lineageTreeLoaders:
65
81
  self.pos[C] = pos
66
82
  self.nodes.add(C)
67
83
  self.time_nodes.setdefault(t, set()).add(C)
68
- # self.time_id[(t, cell_id)] = C
69
84
  self.time[C] = t
70
85
  if not link:
71
86
  self.displacement[C] = np.array([dx, dy, dz * z_mult])
@@ -314,6 +329,147 @@ class lineageTreeLoaders:
314
329
  new_dict[k] = v
315
330
  return new_dict
316
331
 
332
+ def read_from_binary(self, fname: str):
333
+ """
334
+ Reads a binary lineageTree file name.
335
+ Format description: see self.to_binary
336
+
337
+ Args:
338
+ fname: string, path to the binary file
339
+ reverse_time: bool, not used
340
+ """
341
+ q_size = struct.calcsize("q")
342
+ H_size = struct.calcsize("H")
343
+ d_size = struct.calcsize("d")
344
+
345
+ with open(fname, "rb") as f:
346
+ len_tree = struct.unpack("q", f.read(q_size))[0]
347
+ len_time = struct.unpack("q", f.read(q_size))[0]
348
+ len_pos = struct.unpack("q", f.read(q_size))[0]
349
+ number_sequence = list(
350
+ struct.unpack("q" * len_tree, f.read(q_size * len_tree))
351
+ )
352
+ time_sequence = list(
353
+ struct.unpack("H" * len_time, f.read(H_size * len_time))
354
+ )
355
+ pos_sequence = np.array(
356
+ struct.unpack("d" * len_pos, f.read(d_size * len_pos))
357
+ )
358
+
359
+ f.close()
360
+
361
+ successor = {}
362
+ predecessor = {}
363
+ time = {}
364
+ time_nodes = {}
365
+ time_edges = {}
366
+ pos = {}
367
+ is_root = {}
368
+ nodes = []
369
+ edges = []
370
+ waiting_list = []
371
+ i = 0
372
+ done = False
373
+ if max(number_sequence[::2]) == -1:
374
+ tmp = number_sequence[1::2]
375
+ if len(tmp) * 3 == len(pos_sequence) == len(time_sequence) * 3:
376
+ time = dict(list(zip(tmp, time_sequence)))
377
+ for c, t in time.items():
378
+ time_nodes.setdefault(t, set()).add(c)
379
+ pos = dict(
380
+ list(zip(tmp, np.reshape(pos_sequence, (len_time, 3))))
381
+ )
382
+ is_root = {c: True for c in tmp}
383
+ nodes = tmp
384
+ done = True
385
+ while (
386
+ i < len(number_sequence) and not done
387
+ ): # , c in enumerate(number_sequence[:-1]):
388
+ c = number_sequence[i]
389
+ if c == -1:
390
+ if waiting_list != []:
391
+ prev_mother = waiting_list.pop()
392
+ successor[prev_mother].insert(0, number_sequence[i + 1])
393
+ edges.append((prev_mother, number_sequence[i + 1]))
394
+ time_edges.setdefault(t, set()).add(
395
+ (prev_mother, number_sequence[i + 1])
396
+ )
397
+ is_root[number_sequence[i + 1]] = False
398
+ t = time[prev_mother] + 1
399
+ else:
400
+ t = time_sequence.pop(0)
401
+ is_root[number_sequence[i + 1]] = True
402
+
403
+ elif c == -2:
404
+ successor[waiting_list[-1]] = [number_sequence[i + 1]]
405
+ edges.append((waiting_list[-1], number_sequence[i + 1]))
406
+ time_edges.setdefault(t, set()).add(
407
+ (waiting_list[-1], number_sequence[i + 1])
408
+ )
409
+ is_root[number_sequence[i + 1]] = False
410
+ pos[waiting_list[-1]] = pos_sequence[:3]
411
+ pos_sequence = pos_sequence[3:]
412
+ nodes.append(waiting_list[-1])
413
+ time[waiting_list[-1]] = t
414
+ time_nodes.setdefault(t, set()).add(waiting_list[-1])
415
+ t += 1
416
+
417
+ elif number_sequence[i + 1] >= 0:
418
+ successor[c] = [number_sequence[i + 1]]
419
+ edges.append((c, number_sequence[i + 1]))
420
+ time_edges.setdefault(t, set()).add(
421
+ (c, number_sequence[i + 1])
422
+ )
423
+ is_root[number_sequence[i + 1]] = False
424
+ pos[c] = pos_sequence[:3]
425
+ pos_sequence = pos_sequence[3:]
426
+ nodes.append(c)
427
+ time[c] = t
428
+ time_nodes.setdefault(t, set()).add(c)
429
+ t += 1
430
+
431
+ elif number_sequence[i + 1] == -2:
432
+ waiting_list += [c]
433
+
434
+ elif number_sequence[i + 1] == -1:
435
+ pos[c] = pos_sequence[:3]
436
+ pos_sequence = pos_sequence[3:]
437
+ nodes.append(c)
438
+ time[c] = t
439
+ time_nodes.setdefault(t, set()).add(c)
440
+ t += 1
441
+ i += 1
442
+ if waiting_list != []:
443
+ prev_mother = waiting_list.pop()
444
+ successor[prev_mother].insert(0, number_sequence[i + 1])
445
+ edges.append((prev_mother, number_sequence[i + 1]))
446
+ time_edges.setdefault(t, set()).add(
447
+ (prev_mother, number_sequence[i + 1])
448
+ )
449
+ if i + 1 < len(number_sequence):
450
+ is_root[number_sequence[i + 1]] = False
451
+ t = time[prev_mother] + 1
452
+ else:
453
+ if len(time_sequence) > 0:
454
+ t = time_sequence.pop(0)
455
+ if i + 1 < len(number_sequence):
456
+ is_root[number_sequence[i + 1]] = True
457
+ i += 1
458
+
459
+ predecessor = {vi: [k] for k, v in successor.items() for vi in v}
460
+
461
+ self.successor = successor
462
+ self.predecessor = predecessor
463
+ self.time = time
464
+ self.time_nodes = time_nodes
465
+ self.time_edges = time_edges
466
+ self.pos = pos
467
+ self.nodes = set(nodes)
468
+ self.t_b = min(time_nodes)
469
+ self.t_e = max(time_nodes)
470
+ self.is_root = is_root
471
+ self.max_id = max(self.nodes)
472
+
317
473
  def read_from_txt_for_celegans(self, file: str):
318
474
  """
319
475
  Read a C. elegans lineage tree
@@ -321,20 +477,6 @@ class lineageTreeLoaders:
321
477
  Args:
322
478
  file (str): Path to the file to read
323
479
  """
324
- implicit_l_t = {
325
- "AB": "P0",
326
- "P1": "P0",
327
- "EMS": "P1",
328
- "P2": "P1",
329
- "MS": "EMS",
330
- "E": "EMS",
331
- "C": "P2",
332
- "P3": "P2",
333
- "D": "P3",
334
- "P4": "P3",
335
- "Z2": "P4",
336
- "Z3": "P4",
337
- }
338
480
  with open(file) as f:
339
481
  raw = f.readlines()[1:]
340
482
  f.close()
@@ -363,12 +505,9 @@ class lineageTreeLoaders:
363
505
  p = name_to_id[self.name[c]]
364
506
  elif self.name[c][:-1] in name_to_id:
365
507
  p = name_to_id[self.name[c][:-1]]
366
- elif implicit_l_t.get(self.name[c]) in name_to_id:
367
- p = name_to_id[implicit_l_t.get(self.name[c])]
508
+ elif self.implicit_l_t.get(self.name[c]) in name_to_id:
509
+ p = name_to_id[self.implicit_l_t.get(self.name[c])]
368
510
  else:
369
- print(
370
- "error, cell %s has no predecessors" % self.name[c]
371
- )
372
511
  p = None
373
512
  self.predecessor.setdefault(c, []).append(p)
374
513
  self.successor.setdefault(p, []).append(c)
@@ -389,21 +528,6 @@ class lineageTreeLoaders:
389
528
  file (str): Path to the file to read
390
529
  """
391
530
 
392
- implicit_l_t = {
393
- "AB": "P0",
394
- "P1": "P0",
395
- "EMS": "P1",
396
- "P2": "P1",
397
- "MS": "EMS",
398
- "E": "EMS",
399
- "C": "P2",
400
- "P3": "P2",
401
- "D": "P3",
402
- "P4": "P3",
403
- "Z2": "P4",
404
- "Z3": "P4",
405
- }
406
-
407
531
  def split_line(line):
408
532
  return (
409
533
  line.split()[0],
@@ -450,11 +574,12 @@ class lineageTreeLoaders:
450
574
  p = name_to_id[self.name[c]]
451
575
  elif self.name[c][:-1] in name_to_id:
452
576
  p = name_to_id[self.name[c][:-1]]
453
- elif implicit_l_t.get(self.name[c]) in name_to_id:
454
- p = name_to_id[implicit_l_t.get(self.name[c])]
577
+ elif self.implicit_l_t.get(self.name[c]) in name_to_id:
578
+ p = name_to_id[self.implicit_l_t.get(self.name[c])]
455
579
  else:
456
- print(
457
- "error, cell %s has no predecessors" % self.name[c]
580
+ warn(
581
+ f"error, cell {self.name[c]} has no predecessors",
582
+ stacklevel=2,
458
583
  )
459
584
  p = None
460
585
  self.predecessor.setdefault(c, []).append(p)
@@ -496,9 +621,6 @@ class lineageTreeLoaders:
496
621
  self.intensity = {}
497
622
  self.W = {}
498
623
  for t in range(tb, te + 1):
499
- print(t, end=" ")
500
- if t % 10 == 0:
501
- print()
502
624
  tree = ET.parse(file_format.format(t=t))
503
625
  root = tree.getroot()
504
626
  self.time_nodes[t] = set()
@@ -720,3 +842,55 @@ class lineageTreeLoaders:
720
842
  tracks[t_id].append((s, t))
721
843
  self.t_b = min(self.time_nodes.keys())
722
844
  self.t_e = max(self.time_nodes.keys())
845
+
846
+ def read_C_elegans_bao(self, path):
847
+ cell_times = {}
848
+ self.expression = {}
849
+ with open(path) as f:
850
+ for line in f:
851
+ if "cell_name" not in line:
852
+ cell_times[line.split("\t")[0]] = list(
853
+ line.split("\t")[-1].split(",")
854
+ )
855
+ new_dict = {}
856
+ end_dict = {}
857
+ self.t_e = 0
858
+ self.t_b = 0
859
+ for c, lc in cell_times.items():
860
+ new_dict[c] = self.add_node(0)
861
+ tmp = self.add_branch(
862
+ new_dict[c],
863
+ length=len(lc) - 1,
864
+ reverse=True,
865
+ move_timepoints=True,
866
+ )
867
+ for i, node in enumerate(self.get_cycle(tmp)):
868
+ self.expression[node] = int(lc[i])
869
+ self._labels[self.get_cycle(tmp)[0]] = c
870
+ self._labels.pop(tmp)
871
+ end_dict[c] = self.get_cycle(new_dict[c])[-1]
872
+ cell_names = list(cell_times.keys())
873
+ c_to_p = {}
874
+ while cell_names:
875
+ cur = cell_names.pop()
876
+ if cur[:-1] in cell_names:
877
+ c_to_p[cur] = cur[:-1]
878
+ c_to_p.update(self.implicit_l_t)
879
+ for c, p in c_to_p.items():
880
+ if p in cell_times:
881
+ cyc = end_dict[p]
882
+ self.predecessor[new_dict[c]] = [cyc]
883
+ if cyc not in self.successor:
884
+ self.successor[cyc] = []
885
+ self.successor[cyc].append(new_dict[c])
886
+ self.time_nodes.clear()
887
+ for root in self.roots:
888
+ to_do = [root]
889
+ while to_do:
890
+ cur = to_do.pop()
891
+ self.time_nodes.setdefault(self.time[cur], set()).add(cur)
892
+ _next = self.successor.get(cur, [])
893
+ to_do += _next
894
+ for n in _next:
895
+ self.time[n] = self.time[cur] + 1
896
+ self.t_e = max(self.time.values())
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from abc import ABC, abstractmethod
2
3
  from enum import Enum
3
4
 
@@ -23,13 +24,15 @@ class abstract_trees(ABC):
23
24
  root: int,
24
25
  downsample: int,
25
26
  end_time: int = None,
26
- time_scale: int = None,
27
+ time_scale: int = 1,
27
28
  ):
28
29
  self.lT: lineageTree = lT
29
30
  self.root: int = root
30
31
  self.downsample: int = downsample
31
32
  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.time_scale: int = int(time_scale) if time_scale else 1
34
+ if time_scale <= 0:
35
+ raise Exception("Please used a valid time_scale (Larger than 0)")
33
36
  self.tree: tuple = self.get_tree()
34
37
  self.edist = self._edist_format(self.tree[0])
35
38
 
@@ -211,6 +214,13 @@ class downsample_tree(abstract_trees):
211
214
 
212
215
  def __init__(self, **kwargs):
213
216
  super().__init__(**kwargs)
217
+ if self.downsample == 0:
218
+ raise Exception("Please use a valid downsampling rate")
219
+ if self.downsample == 1:
220
+ warnings.warn(
221
+ "Downsampling rate of 1 is identical to the full tree.",
222
+ stacklevel=1,
223
+ )
214
224
 
215
225
  def get_tree(self):
216
226
  self.out_dict = {}
@@ -219,7 +229,8 @@ class downsample_tree(abstract_trees):
219
229
  while to_do:
220
230
  current = to_do.pop()
221
231
  _next = self.lT.get_cells_at_t_from_root(
222
- current, self.lT.time[current] + self.downsample
232
+ current,
233
+ self.lT.time[current] + (self.downsample / self.time_scale),
223
234
  )
224
235
  if _next == [current]:
225
236
  _next = None
@@ -228,11 +239,11 @@ class downsample_tree(abstract_trees):
228
239
  to_do.extend(_next)
229
240
  else:
230
241
  self.out_dict[current] = []
231
- self.times[current] = self.downsample
242
+ self.times[current] = 1 # self.downsample
232
243
  return self.out_dict, self.times
233
244
 
234
245
  def get_norm(self):
235
- return sum(self.times.values())
246
+ return len(self.times.values()) * self.downsample / self.time_scale
236
247
 
237
248
  def delta(self, x, y, corres1, corres2, times1, times2):
238
249
  if x is None and y is None:
@@ -283,22 +294,23 @@ class full_tree(abstract_trees):
283
294
  _next = self.lT.successor.get(current, [])
284
295
  if _next and self.lT.time[_next[0]] <= self.end_time:
285
296
  if self.time_scale > 1:
286
- for _ in range(self.time_scale):
297
+ for _ in range(self.time_scale - 1):
287
298
  next_id = self.lT.get_next_id()
288
- self.out_dict[current] = next_id
299
+ self.out_dict[current] = [next_id]
289
300
  current = next_id
290
301
  self.out_dict[current] = _next
291
302
  to_do.extend(_next)
292
303
  else:
304
+ for _ in range(self.time_scale - 1):
305
+ next_id = self.lT.get_next_id()
306
+ self.out_dict[current] = [next_id]
307
+ current = next_id
293
308
  self.out_dict[current] = []
294
309
  self.times[current] = 1
295
310
  return self.out_dict, self.times
296
311
 
297
312
  def get_norm(self):
298
- return (
299
- len(self.lT.get_sub_tree(self.root, end_time=self.end_time))
300
- * self.time_scale
301
- )
313
+ return len(self.times) * self.time_scale
302
314
 
303
315
  def delta(self, x, y, corres1, corres2, times1, times2):
304
316
  if x is None and y is None:
LineageTree/utils.py CHANGED
@@ -7,7 +7,8 @@ try:
7
7
  import motile
8
8
  except ImportError:
9
9
  warnings.warn(
10
- "No motile installed therefore you will not be able to produce links with motile."
10
+ "No motile installed therefore you will not be able to produce links with motile.",
11
+ stacklevel=2,
11
12
  )
12
13
 
13
14
 
@@ -16,16 +17,13 @@ def to_motile(
16
17
  ):
17
18
  try:
18
19
  import networkx as nx
19
- except:
20
- raise Warning("Please install networkx")
20
+ except ImportError:
21
+ raise Warning("Please install networkx") # noqa: B904
21
22
 
22
23
  fmt = nx.DiGraph()
23
24
  if not crop:
24
25
  crop = lT.t_e
25
- # time_nodes = [
26
26
  for time in range(crop):
27
- # time_nodes += lT.time_nodes[time]
28
- # print(time_nodes)
29
27
  for time_node in lT.time_nodes[time]:
30
28
  fmt.add_node(
31
29
  time_node,
@@ -33,8 +31,6 @@ def to_motile(
33
31
  pos=lT.pos[time_node],
34
32
  score=1,
35
33
  )
36
- # for suc in lT.successor:
37
- # fmt.add_edge(time_node, suc, **{"score":0})
38
34
 
39
35
  motile.add_cand_edges(fmt, max_dist, max_skip_frames=max_skip_frames)
40
36
 
@@ -135,7 +131,9 @@ def hierarchical_pos(
135
131
  elif len(succ) == 1:
136
132
  pos_node[succ[0]] = [
137
133
  pos_node[curr][0],
138
- pos_node[curr][1] - lnks_tms["times"].get(curr, 0),
134
+ pos_node[curr][1]
135
+ - lnks_tms["times"].get(curr, 0)
136
+ + min(vert_gap, lnks_tms["times"].get(curr, 0)),
139
137
  ]
140
138
  to_do.extend(succ)
141
139
  prev_width[succ[0]] = prev_width[curr]
@@ -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
@@ -0,0 +1,11 @@
1
+ LineageTree/__init__.py,sha256=888vRBgs18oExOHp7Q87HSH1GL60m-YLLjkMWYnZcBI,155
2
+ LineageTree/lineageTree.py,sha256=SoxLcYXhr5wzuX9ZXMDsK5fePd_6_Boq-y4HVJ6mBBI,102326
3
+ LineageTree/lineageTreeManager.py,sha256=bBhJrCUmLa6D6cZ95jT4hTnNfG7q-l_FiqOf2oaBc2o,6458
4
+ LineageTree/loaders.py,sha256=eStgVrqYeUlYQ-r7EByCZo2t5cgn-mXARTWBzLSN12Q,32778
5
+ LineageTree/tree_styles.py,sha256=ZEj0HcoEaWk8zyObboXW-qoM3uzKAQgiNzX2-UUa0cg,11337
6
+ LineageTree/utils.py,sha256=sFIj1eDid1EKP09g_jrSgqk4NLdNVf7KjC5UwN0Fihc,4878
7
+ LineageTree-1.8.0.dist-info/LICENSE,sha256=IKncNCNpq93Kq7Ywg1V4I9Bu_99VMK-mX1EJyjTKLyc,1068
8
+ LineageTree-1.8.0.dist-info/METADATA,sha256=X9UUm6EP44N_O3Uv0JN4Bu9wXU9kXAB9Nh-lHkjHoEs,4469
9
+ LineageTree-1.8.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
10
+ LineageTree-1.8.0.dist-info/top_level.txt,sha256=CCqPCTX76zPTEUagUgTWbLaZun8752n59iOOwfUlvxs,12
11
+ LineageTree-1.8.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
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,,