swcgeom 0.19.4__cp312-cp312-win_amd64.whl → 0.20.0__cp312-cp312-win_amd64.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.

Potentially problematic release.


This version of swcgeom might be problematic. Click here for more details.

@@ -15,10 +15,14 @@ from typing import Any, Protocol, TypeVar, cast, overload
15
15
  import numpy as np
16
16
  import numpy.typing as npt
17
17
  from tqdm.contrib.concurrent import process_map
18
+ from typing import Literal
18
19
  from typing_extensions import Self
19
20
 
20
21
  from swcgeom.core.swc import eswc_cols
22
+ from swcgeom.core.swc_utils.base import SWCNames
23
+ from swcgeom.core.swc_utils.io import read_swc_components
21
24
  from swcgeom.core.tree import Tree
25
+ from swcgeom.utils import PathOrIO
22
26
 
23
27
  __all__ = ["LazyLoadingTrees", "ChainTrees", "Population", "Populations"]
24
28
 
@@ -226,6 +230,55 @@ class Population:
226
230
 
227
231
  return swcs
228
232
 
233
+ @classmethod
234
+ def from_multi_roots_swc(
235
+ cls,
236
+ swc_file: PathOrIO,
237
+ *,
238
+ extra_cols: Iterable[str] | None = None,
239
+ encoding: Literal["detect"] | str = "utf-8",
240
+ names: SWCNames | None = None,
241
+ reset_index_per_subtree: bool = True,
242
+ **kwargs,
243
+ ) -> Self:
244
+ """Create a population from an SWC file containing multiple roots.
245
+
246
+ Each root in the SWC file will be treated as the start of a
247
+ separate tree in the resulting population.
248
+
249
+ Args:
250
+ swc_file: Path to the SWC file.
251
+ extra_cols: Read more cols in swc file. Passed to
252
+ `read_swc_components`.
253
+ encoding: The name of the encoding used to decode the file.
254
+ Passed to `read_swc_components`.
255
+ names: SWCNames configuration. Passed to `read_swc_components`.
256
+ reset_index_per_subtree: Reset node index for each subtree
257
+ to start with zero. Passed to `read_swc_components`.
258
+ **kwargs: Additional keyword arguments passed to `Tree.from_data_frame`
259
+ for each component tree.
260
+
261
+ Returns:
262
+ A Population object where each tree corresponds to a connected
263
+ component from the input SWC file.
264
+ """
265
+ dfs, comments = read_swc_components(
266
+ swc_file,
267
+ extra_cols=extra_cols,
268
+ encoding=encoding,
269
+ names=names,
270
+ reset_index_per_subtree=reset_index_per_subtree,
271
+ )
272
+
273
+ trees = [
274
+ Tree.from_data_frame(df, source=f"{swc_file}#component_{i}", comments=comments, **kwargs)
275
+ for i, df in enumerate(dfs)
276
+ ]
277
+
278
+ # Use the file path as the 'root' for the population representation
279
+ root_repr = str(swc_file) if not hasattr(swc_file, "name") else swc_file.name
280
+ return cls(trees, root=root_repr)
281
+
229
282
 
230
283
  class Populations:
231
284
  """A set of population."""
@@ -1,4 +1,3 @@
1
-
2
1
  # SPDX-FileCopyrightText: 2022 - 2025 Zexin Yuan <pypi@yzx9.xyz>
3
2
  #
4
3
  # SPDX-License-Identifier: Apache-2.0
@@ -15,6 +14,7 @@ from swcgeom.core.swc_utils.base import SWCNames, Topology, get_dsu, get_names,
15
14
  from swcgeom.utils import DisjointSetUnion
16
15
 
17
16
  __all__ = [
17
+ "get_num_of_roots",
18
18
  "is_single_root",
19
19
  "is_bifurcate",
20
20
  "is_sorted",
@@ -25,9 +25,14 @@ __all__ = [
25
25
  ]
26
26
 
27
27
 
28
+ def get_num_of_roots(df: pd.DataFrame, *, names: SWCNames | None = None) -> int:
29
+ """Get number of roots."""
30
+ return len(np.unique(get_dsu(df, names=names)))
31
+
32
+
28
33
  def is_single_root(df: pd.DataFrame, *, names: SWCNames | None = None) -> bool:
29
34
  """Check is it only one root."""
30
- return len(np.unique(get_dsu(df, names=names))) == 1
35
+ return get_num_of_roots(df, names=names) == 1
31
36
 
32
37
 
33
38
  def is_bifurcate(topology: Topology, *, exclude_root: bool = True) -> bool:
@@ -1,4 +1,3 @@
1
-
2
1
  # SPDX-FileCopyrightText: 2022 - 2025 Zexin Yuan <pypi@yzx9.xyz>
3
2
  #
4
3
  # SPDX-License-Identifier: Apache-2.0
@@ -15,23 +14,23 @@ import numpy.typing as npt
15
14
  import pandas as pd
16
15
 
17
16
  from swcgeom.core.swc_utils.base import SWCNames, get_names
18
- from swcgeom.core.swc_utils.checker import is_single_root
17
+ from swcgeom.core.swc_utils.checker import get_num_of_roots, is_single_root
19
18
  from swcgeom.core.swc_utils.normalizer import (
20
19
  link_roots_to_nearest_,
21
20
  mark_roots_as_somas_,
22
21
  reset_index_,
23
22
  sort_nodes_,
24
23
  )
25
- from swcgeom.utils import FileReader, PathOrIO
24
+ from swcgeom.utils import DisjointSetUnion, FileReader, PathOrIO
26
25
 
27
- __all__ = ["read_swc", "to_swc"]
26
+ __all__ = ["read_swc", "read_swc_components", "to_swc"]
28
27
 
29
28
 
30
29
  def read_swc(
31
30
  swc_file: PathOrIO,
32
31
  extra_cols: Iterable[str] | None = None,
33
32
  fix_roots: Literal["somas", "nearest", False] = False,
34
- sort_nodes: bool = False,
33
+ sort_nodes: bool = True,
35
34
  reset_index: bool = True,
36
35
  *,
37
36
  encoding: Literal["detect"] | str = "utf-8",
@@ -45,12 +44,12 @@ def read_swc(
45
44
  extra_cols: Read more cols in swc file.
46
45
  fix_roots: Fix multiple roots.
47
46
  sort_nodes: Sort the indices of neuron tree.
48
- After sorting the nodes, the index for each parent are always less than
49
- that of its children.
47
+ After sorting the nodes, the index for each parent are always less than that of its children, default to
48
+ True.
50
49
  reset_index: Reset node index to start with zero.
51
- DO NOT set to false if you are not sure what will happened.
50
+ DO NOT set to false if you are not sure what will happened, default to True.
52
51
  encoding: The name of the encoding used to decode the file.
53
- If is `detect`, we will try to detect the character encoding.
52
+ If is `detect`, we will try to detect the character encoding, default to "utf-8".
54
53
 
55
54
  Returns:
56
55
  df: ~pandas.DataFrame
@@ -60,35 +59,148 @@ def read_swc(
60
59
  df, comments = parse_swc(
61
60
  swc_file, names=names, extra_cols=extra_cols, encoding=encoding
62
61
  )
62
+ if df.empty:
63
+ raise ValueError(f"SWC file '{swc_file}' is empty or contains no valid nodes.")
63
64
 
64
65
  # fix swc
65
- if fix_roots is not False and np.count_nonzero(df[names.pid] == -1) > 1:
66
+ if not is_single_root(df, names=names):
66
67
  match fix_roots:
67
68
  case "somas":
68
69
  mark_roots_as_somas_(df)
69
70
  case "nearest":
70
71
  link_roots_to_nearest_(df)
72
+ case False:
73
+ warnings.warn(f"not a simple tree in `{swc_file}`")
71
74
  case _:
72
75
  raise ValueError(f"unknown fix type `{fix_roots}`")
73
76
 
74
- if sort_nodes:
75
- sort_nodes_(df)
76
- elif reset_index:
77
- reset_index_(df)
78
-
79
77
  # check swc
80
- if not is_single_root(df, names=names):
81
- warnings.warn(f"not a simple tree in `{swc_file}`")
82
-
83
78
  if (df[names.pid] == -1).argmax() != 0:
84
79
  warnings.warn(f"root is not the first node in `{swc_file}`")
85
80
 
86
81
  if (df[names.r] <= 0).any():
87
82
  warnings.warn(f"non-positive radius in `{swc_file}`")
88
83
 
84
+ # post processing
85
+ if sort_nodes:
86
+ sort_nodes_(df)
87
+ elif reset_index:
88
+ reset_index_(df)
89
+
89
90
  return df, comments
90
91
 
91
92
 
93
+ def read_swc_components(
94
+ swc_file: PathOrIO,
95
+ /,
96
+ *,
97
+ extra_cols: Iterable[str] | None = None,
98
+ encoding: Literal["detect"] | str = "utf-8",
99
+ names: SWCNames | None = None,
100
+ reset_index_per_subtree: bool = True,
101
+ ) -> tuple[list[pd.DataFrame], list[str]]:
102
+ """Read swc file, splitting multi-root files into separate DataFrames.
103
+
104
+ If the SWC file contains multiple roots (disconnected components),
105
+ each component is extracted into its own pandas DataFrame.
106
+
107
+ Args:
108
+ swc_file: Path to the SWC file.
109
+ extra_cols: Read more cols in swc file.
110
+ reset_index_per_subtree: Reset node index for each subtree
111
+ to start with zero. Defaults to True.
112
+ encoding: The name of the encoding used to decode the file.
113
+ If 'detect', attempts to detect the character encoding.
114
+ names: SWCNames configuration.
115
+
116
+ Returns:
117
+ dfs: A list of pandas DataFrames, each representing a
118
+ connected component (potential tree) from the SWC file.
119
+ comments: List of comment lines from the SWC file.
120
+ """
121
+ names = get_names(names)
122
+ df, comments = parse_swc(
123
+ swc_file, names=names, extra_cols=extra_cols, encoding=encoding
124
+ )
125
+ if df.empty:
126
+ warnings.warn(f"SWC file '{swc_file}' is empty or contains no valid nodes.")
127
+ return [], comments
128
+
129
+ num_roots = get_num_of_roots(df, names=names)
130
+ if num_roots == 0:
131
+ warnings.warn(f"SWC file '{swc_file}' contains no root nodes (pid = -1).")
132
+ return [], comments
133
+
134
+ elif num_roots == 1:
135
+ warnings.warn(
136
+ f"SWC file '{swc_file}' has only one root. Consider using `read_swc` for single trees."
137
+ )
138
+ # Return the original DataFrame wrapped in a list
139
+ return [df], comments
140
+
141
+ # Multiple roots: Split into components
142
+ sub_dfs = []
143
+ num_nodes = len(df)
144
+ dsu = DisjointSetUnion(num_nodes)
145
+
146
+ # Map original node IDs to 0..N-1 indices for DSU
147
+ id_to_idx = {node_id: i for i, node_id in enumerate(df[names.id])}
148
+ for i, row in df.iterrows():
149
+ parent_id = row[names.pid]
150
+ if parent_id == -1:
151
+ continue
152
+
153
+ child_idx = i # Use DataFrame index which is 0..N-1
154
+ parent_idx = id_to_idx.get(parent_id)
155
+ if parent_idx is None:
156
+ warnings.warn(
157
+ f"Parent ID {parent_id} for node ID {row[names.id]} not found "
158
+ f"in '{swc_file}'. Treating node as root of a component."
159
+ )
160
+ continue
161
+
162
+ # Ensure indices are valid before union (should always be if df is consistent)
163
+ if not dsu.validate_node(child_idx) or not dsu.validate_node(parent_idx):
164
+ # This case should ideally not happen with well-formed input
165
+ warnings.warn(
166
+ f"Internal error: Invalid node index for node id "
167
+ f"{row[names.id]} or parent id {parent_id} in '{swc_file}'.",
168
+ stacklevel=2,
169
+ )
170
+ continue
171
+
172
+ dsu.union_sets(child_idx, parent_idx)
173
+
174
+ # Group nodes by component representative index
175
+ components: dict[int, list[int]] = {}
176
+ for i in range(num_nodes):
177
+ parent_repr = dsu.find_parent(i)
178
+ if parent_repr not in components:
179
+ components[parent_repr] = []
180
+ components[parent_repr].append(i) # Store original DataFrame indices (0..N-1)
181
+
182
+ # Create a DataFrame for each component
183
+ for component_indices in components.values():
184
+ sub_df = df.iloc[component_indices].copy()
185
+
186
+ if reset_index_per_subtree:
187
+ # Remap IDs and PIDs for the subtree to be 0..M-1
188
+ old_id_to_new_id = {
189
+ old_id: new_id for new_id, old_id in enumerate(sub_df[names.id])
190
+ }
191
+
192
+ # Apply mapping, ensuring the root's PID becomes -1
193
+ sub_df[names.id] = sub_df[names.id].map(old_id_to_new_id)
194
+ sub_df[names.pid] = sub_df[names.pid].map(
195
+ lambda old_pid: old_id_to_new_id.get(old_pid, -1)
196
+ )
197
+ # else: IDs remain as they were in the original file subset.
198
+
199
+ sub_dfs.append(sub_df)
200
+
201
+ return sub_dfs, comments
202
+
203
+
92
204
  def to_swc(
93
205
  get_ndata: Callable[[str], npt.NDArray],
94
206
  *,
@@ -180,21 +292,40 @@ def parse_swc(
180
292
  with FileReader(fname, encoding=encoding) as f:
181
293
  try:
182
294
  for i, line in enumerate(f):
183
- if (match := re_swc.search(line)) is not None:
184
- if flag and match.group(last_group):
295
+ original_line = line # Keep for error messages/comments
296
+ line_content = original_line.split("#", 1)[0].strip() # Process content part
297
+
298
+ if not line_content: # Skip empty lines or lines that become empty
299
+ # Handle full comment lines using original_line
300
+ if match := RE_COMMENT.match(original_line):
301
+ comment = original_line[len(match.group(0)) :].removesuffix("\n").strip()
302
+ if comment and not comment.startswith(ignored_comment):
303
+ comments.append(comment)
304
+ continue # Move to next line
305
+
306
+ if (match := re_swc.search(line_content)) is not None:
307
+ # Check for extra numerical fields captured by the last group
308
+ # Warn if the captured group exists and contains non-whitespace
309
+ if flag and match.group(last_group) and match.group(last_group).strip():
185
310
  warnings.warn(
186
- f"some fields are ignored in row {i + 1} of `{fname}`"
311
+ f"Extra fields detected and ignored in row {i + 1} of `{fname}`"
187
312
  )
188
- flag = False
189
-
190
- for i, trans in enumerate(transforms):
191
- vals[i].append(trans(match.group(i + 1)))
192
- elif match := RE_COMMENT.match(line):
193
- comment = line[len(match.group(0)) :].removesuffix("\n")
194
- if not comment.startswith(ignored_comment):
195
- comments.append(comment)
196
- elif not line.isspace():
197
- raise ValueError(f"invalid row {i + 1} in `{fname}`")
313
+ flag = False # Only warn once
314
+
315
+ for j, trans in enumerate(transforms):
316
+ try:
317
+ vals[j].append(trans(match.group(j + 1)))
318
+ except ValueError as e:
319
+ raise ValueError(
320
+ f"Invalid data format in row {i + 1}, column {j+1} ('{keys[j]}') in `{fname}`: {match.group(j + 1)}"
321
+ ) from e
322
+
323
+ else: # If re_swc didn't match the line_content
324
+ # It's not a valid SWC data line. We already handled empty/comment lines.
325
+ raise ValueError(
326
+ f"Invalid SWC data format in row {i + 1} in `{fname}`: {original_line.strip()}"
327
+ )
328
+
198
329
  except UnicodeDecodeError as e:
199
330
  raise ValueError(
200
331
  "decode failed, try to enable auto detect `encoding='detect'`"
swcgeom/core/tree.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  # SPDX-FileCopyrightText: 2022 - 2025 Zexin Yuan <pypi@yzx9.xyz>
3
2
  #
4
3
  # SPDX-License-Identifier: Apache-2.0
@@ -233,22 +232,26 @@ class Tree(DictSWC):
233
232
  node: "Tree.Node", pre: list[tuple[list[Tree.Branch], list[int]]]
234
233
  ) -> tuple[list[Tree.Branch], list[int]]:
235
234
  if len(pre) == 1:
236
- branches, child = pre[0]
237
- child.append(node.id)
238
- return branches, child
235
+ branches, children = pre[0]
236
+ children.append(node.id)
237
+ return branches, children
239
238
 
240
239
  branches: list[Tree.Branch] = []
241
-
242
- for sub_branches, child in pre:
243
- child.append(node.id)
244
- child.reverse()
245
- sub_branches.append(Tree.Branch(self, np.array(child, dtype=np.int32)))
240
+ for sub_branches, children in pre:
241
+ children.append(node.id)
242
+ children.reverse()
243
+ sub_branches.append(
244
+ Tree.Branch(self, np.array(children, dtype=np.int32))
245
+ )
246
246
  sub_branches.reverse()
247
247
  branches.extend(sub_branches)
248
248
 
249
249
  return branches, [node.id]
250
250
 
251
- branches, _ = self.traverse(leave=collect_branches)
251
+ branches, children = self.traverse(leave=collect_branches)
252
+ if len(children) > 0:
253
+ branches.append(Tree.Branch(self, np.array(children, dtype=np.int32)))
254
+
252
255
  return branches
253
256
 
254
257
  def get_paths(self) -> list[Path]:
@@ -11,7 +11,6 @@ import numpy as np
11
11
  import pandas as pd
12
12
  from typing_extensions import override
13
13
 
14
- from swcgeom.core import Tree
15
14
  from swcgeom.core.swc_utils import (
16
15
  SWCNames,
17
16
  get_names,
@@ -23,7 +22,7 @@ from swcgeom.transforms.base import Transform
23
22
  EPS = 1e-5
24
23
 
25
24
 
26
- class LinesToTree(Transform[list[pd.DataFrame], Tree]):
25
+ class LinesToTree(Transform[Iterable[pd.DataFrame], pd.DataFrame]):
27
26
  """Assemble lines to swc."""
28
27
 
29
28
  def __init__(self, *, thre: float = 0.2, undirected: bool = True):
@@ -39,9 +38,9 @@ class LinesToTree(Transform[list[pd.DataFrame], Tree]):
39
38
 
40
39
  @override
41
40
  def __call__(
42
- self, lines: Iterable[pd.DataFrame], *, names: SWCNames | None = None
43
- ): # TODO check this
44
- return self.assemble(lines, names=names)
41
+ self, x: Iterable[pd.DataFrame], *, names: SWCNames | None = None
42
+ ) -> pd.DataFrame:
43
+ return self.assemble(x, names=names)
45
44
 
46
45
  def assemble(
47
46
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swcgeom
3
- Version: 0.19.4
3
+ Version: 0.20.0
4
4
  Summary: Neuron geometry library for swc format
5
5
  Author-email: yzx9 <pypi@yzx9.xyz>
6
6
  License-Expression: Apache-2.0
@@ -14,16 +14,16 @@ swcgeom/core/branch_tree.py,sha256=6doQaarCB-nEcrMuNElwmPqgvE15yn5fxy3JePBcaTs,2
14
14
  swcgeom/core/compartment.py,sha256=gWzklvRH9nXBPezr8QnIEKxta3uZHk3Um9hLC7DT_2I,3484
15
15
  swcgeom/core/node.py,sha256=XkMjlQ-1E_WaCCK6ghArQ9lXKuxPvzylLJyf6hnSzAE,3958
16
16
  swcgeom/core/path.py,sha256=5ZARke8KAnM2bO99OgDTvYDdsqxdJ6Oa2lZlfSI4Mco,4704
17
- swcgeom/core/population.py,sha256=rRkWh0Aik24EGBCo3jAOrvF-x2chpdJJCSA82caU4no,10563
17
+ swcgeom/core/population.py,sha256=EFsVNWD85W-fo-P5_bhWXzahYz4EdktRV22Ko37ZJso,12679
18
18
  swcgeom/core/swc.py,sha256=Wlz4kV44dJpsYgiOEGTi8mL56KjvLAVuz_F_ip_JtaU,7189
19
- swcgeom/core/tree.py,sha256=TdApE-ZANdyh6U8YoYmI-LsoaMzsyDQMvcKrHR6yuSE,13068
19
+ swcgeom/core/tree.py,sha256=BNLA5tDMWI87IvoInbtJqBJlE8YXWvWt8TegpKrnlJk,13249
20
20
  swcgeom/core/tree_utils.py,sha256=4eFHQ8EajJG4fGO3_in6LKLYW1e-dw1cP8ZhqBJA0b8,7662
21
21
  swcgeom/core/tree_utils_impl.py,sha256=mhgQqrtnGwei8B2jY2JvVgPLM-KU6PsKy8t-thdxxh8,1740
22
22
  swcgeom/core/swc_utils/__init__.py,sha256=afVBWtkRFqnZUZeHf194JiycdbvGi1TLP_jf6FN7XLw,778
23
23
  swcgeom/core/swc_utils/assembler.py,sha256=_VlOPnJvmveB8jSnnOhqY0MchnYq7F1dXCWJdvbC44c,1001
24
24
  swcgeom/core/swc_utils/base.py,sha256=TVT4e6XoKuyBWbu2sFyzslnTobIXSXLqAiHEbOILi_I,4979
25
- swcgeom/core/swc_utils/checker.py,sha256=RBFFgzw_LtwK_o2oXzrg54DClf0m3POn27lFwM5Wans,2774
26
- swcgeom/core/swc_utils/io.py,sha256=pI0NbD4b9KCv9btYiP4OCtMJchcp_GVZobVOlEMSmqY,6421
25
+ swcgeom/core/swc_utils/checker.py,sha256=ZcSSiprEnbgJiSCRm7cqc8eM0rQgNsdzFucR4bVTjdQ,2961
26
+ swcgeom/core/swc_utils/io.py,sha256=JRuNIq70xWFPSIMlBTZb7BUhvlnklswOTwZ_OKClINU,12062
27
27
  swcgeom/core/swc_utils/normalizer.py,sha256=b0RxQroVyPmfKvV4x8FneEGgce8G4MI2xTsv34FADZA,5265
28
28
  swcgeom/core/swc_utils/subtree.py,sha256=ri3Sh_uTi1mrfcA-rqTD88rW_L0cxZPRuON4zzADwNA,2126
29
29
  swcgeom/images/__init__.py,sha256=3BLzhVKRbVtdsjaPTXX0SMO8JVxGwGvwVc4D0uyZ2nA,240
@@ -32,9 +32,9 @@ swcgeom/images/contrast.py,sha256=2Z38wV2laTCFAjZ3mT4vQlDtwHIZ-GmQQi81JDEzgO0,20
32
32
  swcgeom/images/folder.py,sha256=veQnUP5W9eXtOsBB0YtwEs3GlsylREJt9yth1FMTyAA,6590
33
33
  swcgeom/images/io.py,sha256=mY6rklHn3xuSNvd_g4YGwLHP8Y3u-hEb6bV2jFVraso,20424
34
34
  swcgeom/images/loaders/__init__.py,sha256=a4XSH2Bn6v5YUGb_lKwdjAGD1i4SXBTXGVh08mct7pg,254
35
- swcgeom/images/loaders/pbd.cp312-win_amd64.pyd,sha256=Zv__Sp81UfrY3FqK9d_fq72RipRfyyT6Xff2uPB-nNc,198144
35
+ swcgeom/images/loaders/pbd.cp312-win_amd64.pyd,sha256=4A0cVdlDvaKNIJJZGcah1goEDdIvaWl5IogHswY-kks,182272
36
36
  swcgeom/images/loaders/pbd.pyx,sha256=14uWjLpdxLhdPVUoAjthEnRVbhV0AdKGBipXY_BZeWU,23092
37
- swcgeom/images/loaders/raw.cp312-win_amd64.pyd,sha256=BovqsFbVf_7bHPdmh0-h5PDQfnjSfcimDOl9gCmdemo,93184
37
+ swcgeom/images/loaders/raw.cp312-win_amd64.pyd,sha256=hS0nDfemawDGJ9Mdd12F8c7iDNMkW0ZbHxD2TaHH59Q,83968
38
38
  swcgeom/images/loaders/raw.pyx,sha256=Zd6CIoy97aaGs6bxi9CzoraPp261dpuWMK_ZujElfYU,6947
39
39
  swcgeom/transforms/__init__.py,sha256=DSE21HbgD6WczilWtONVq_m3zUgZXP-nsJjOCE_vKKU,916
40
40
  swcgeom/transforms/base.py,sha256=oaOSZGrZKVbauypp9G-3G2f_WIx2gEJIft60jMg5rBc,4612
@@ -49,7 +49,7 @@ swcgeom/transforms/neurolucida_asc.py,sha256=QV9K6Q0lUandF0r2Fyd999VyiI8zdS4rYNZ
49
49
  swcgeom/transforms/path.py,sha256=JMjPKm3eYB_nr6j2-BBF-zUqPRTTIDMInM8TkCoSafE,1319
50
50
  swcgeom/transforms/population.py,sha256=305NHfplPu_VJvVNUm1_8-me0Cd6TsGXAQTi9AYfSTE,1019
51
51
  swcgeom/transforms/tree.py,sha256=ykmEP9626z4SBAwl0Vgw3Z7VZwRqiO2GxPsWtTha5wQ,8028
52
- swcgeom/transforms/tree_assembler.py,sha256=42bAOBzzSlMMYdqlFu8ZoNQrS2Qi-MJje5_kAkWqdmA,5060
52
+ swcgeom/transforms/tree_assembler.py,sha256=zYO_NwO8ES3IGnzhA9JhvK5PV42vDy1r8t5YgSeBuyI,5030
53
53
  swcgeom/utils/__init__.py,sha256=k74fxbYGZfizmqHbWTqavtONOiqs5mndrBSC8SzDMQE,758
54
54
  swcgeom/utils/debug.py,sha256=vURuhDf1Mx67-p8EMSoOTQbzZRAQd1Q5OQzD4OR6PaA,594
55
55
  swcgeom/utils/download.py,sha256=bU2Be9fX_zNLaIe04fCaZFDHaLd_KoJMwnUvyUG_qUI,3789
@@ -65,8 +65,8 @@ swcgeom/utils/sdf.py,sha256=WxPwGd4DJRM8sIA2t8RP5_6fypom3PxMtg03sSNqKrM,10816
65
65
  swcgeom/utils/solid_geometry.py,sha256=ma30775813e8bFC6ycGLNdY1N2v96iPmECeHZc2YnPA,4257
66
66
  swcgeom/utils/transforms.py,sha256=hX6QgHKcaWmRwc5RlbKwVBGqCdXJbuQh-CPr02Lme0Q,9469
67
67
  swcgeom/utils/volumetric_object.py,sha256=C6k73avzKJUIsvaP6Bb43K2oENjv0mRub2caNHz4qGI,15316
68
- swcgeom-0.19.4.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
69
- swcgeom-0.19.4.dist-info/METADATA,sha256=fG08-VedmB_7VuWAAt_sxQXK9D9pBOinkr7nXWfSVvs,2951
70
- swcgeom-0.19.4.dist-info/WHEEL,sha256=t5QiC5vd2hOdU0bFY5fyqZIc3wMdCuUPTCilUZToJT4,101
71
- swcgeom-0.19.4.dist-info/top_level.txt,sha256=hmLyUXWS61Gxl07haswFEKKefYPBVJYlUlol8ghNkjY,8
72
- swcgeom-0.19.4.dist-info/RECORD,,
68
+ swcgeom-0.20.0.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
69
+ swcgeom-0.20.0.dist-info/METADATA,sha256=XW-JuO06KGOLekzXNpzm-sfWMe9XYRU2ldarUWCFHwk,2951
70
+ swcgeom-0.20.0.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
71
+ swcgeom-0.20.0.dist-info/top_level.txt,sha256=hmLyUXWS61Gxl07haswFEKKefYPBVJYlUlol8ghNkjY,8
72
+ swcgeom-0.20.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp312-cp312-win_amd64
5
5