swcgeom 0.13.2__py3-none-any.whl → 0.15.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.

Potentially problematic release.


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

@@ -2,60 +2,55 @@
2
2
 
3
3
  Notes
4
4
  -----
5
- - This module has been deprecated, try `github.com/yzx9/swc2skeleton`
6
- - All denpendencies need to be installed, try:
5
+ All denpendencies need to be installed, try:
7
6
 
8
7
  ```sh
9
8
  pip install swcgeom[all]
10
9
  ```
11
10
  """
12
11
 
13
- import math
14
12
  import os
15
13
  import re
16
14
  import time
17
- from typing import Any, Iterable, List, Tuple, cast
15
+ from typing import Iterable, List, Optional, Tuple
18
16
 
19
17
  import numpy as np
20
18
  import numpy.typing as npt
21
19
  import tifffile
20
+ from sdflit import (
21
+ ColoredMaterial,
22
+ ObjectsScene,
23
+ RangeSampler,
24
+ RoundCone,
25
+ Scene,
26
+ SDFObject,
27
+ )
22
28
 
23
29
  from swcgeom.core import Population, Tree
24
30
  from swcgeom.transforms.base import Transform
25
- from swcgeom.utils import SDF, SDFCompose, SDFRoundCone
26
31
 
27
32
  __all__ = ["ToImageStack"]
28
33
 
29
34
 
30
- # TODO: migrate to github.com/yzx9/swc2skeleton
31
35
  class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
32
36
  r"""Transform tree to image stack."""
33
37
 
34
38
  resolution: npt.NDArray[np.float32]
35
- msaa: int
36
- z_per_iter: int = 1
37
39
 
38
- def __init__(
39
- self, resolution: float | npt.ArrayLike = 1, msaa: int = 8, z_per_iter: int = 1
40
- ) -> None:
40
+ def __init__(self, resolution: int | float | npt.ArrayLike = 1) -> None:
41
41
  """Transform tree to image stack.
42
42
 
43
43
  Parameters
44
44
  ----------
45
45
  resolution : int | (x, y, z), default `(1, 1, 1)`
46
46
  Resolution of image stack.
47
- mass : int, default `8`
48
- Multi-sample anti-aliasing.
49
47
  """
50
48
 
51
- if isinstance(resolution, float):
52
- resolution = [resolution, resolution, resolution]
53
-
54
- self.resolution = (resolution := np.array(resolution, dtype=np.float32))
55
- assert tuple(resolution.shape) == (3,), "resolution shoule be vector of 3d."
49
+ if isinstance(resolution, (int, float, np.integer, np.floating)):
50
+ resolution = [resolution, resolution, resolution] # type: ignore
56
51
 
57
- self.msaa = msaa
58
- self.z_per_iter = z_per_iter
52
+ self.resolution = np.array(resolution, dtype=np.float32)
53
+ assert len(self.resolution) == 3, "resolution shoule be vector of 3d."
59
54
 
60
55
  def __call__(self, x: Tree) -> npt.NDArray[np.uint8]:
61
56
  """Transform tree to image stack.
@@ -63,142 +58,121 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
63
58
  Notes
64
59
  -----
65
60
  This method loads the entire image stack into memory, so it
66
- ONLY works for small image stacks, use
67
- :meth`transform_and_save` for big image stack.
61
+ ONLY works for small image stacks, use :meth`transform_and_save`
62
+ for big image stack.
68
63
  """
69
- return np.concatenate(list(self.transfrom(x, verbose=False)), axis=0)
70
-
71
- def __repr__(self) -> str:
72
- return (
73
- "ToImageStack"
74
- + f"-resolution-{'-'.join(self.resolution)}"
75
- + f"-mass-{self.msaa}"
76
- + f"-z-{self.z_per_iter}"
77
- )
64
+ return np.stack(list(self.transfrom(x, verbose=False)), axis=0)
78
65
 
79
66
  def transfrom(
80
- self, x: Tree, verbose: bool = True
67
+ self,
68
+ x: Tree,
69
+ verbose: bool = True,
70
+ *,
71
+ ranges: Optional[Tuple[npt.ArrayLike, npt.ArrayLike]] = None,
81
72
  ) -> Iterable[npt.NDArray[np.uint8]]:
82
- # pylint: disable=too-many-locals
83
- from tqdm import tqdm
84
-
85
73
  if verbose:
86
74
  print("To image stack: " + x.source)
87
75
  time_start = time.time()
88
76
 
89
- sdf = self.get_sdf(x)
77
+ scene = self._get_scene(x)
78
+
79
+ if ranges is None:
80
+ xyz, r = x.xyz(), x.r().reshape(-1, 1)
81
+ coord_min = np.floor(np.min(xyz - r, axis=0))
82
+ coord_max = np.ceil(np.max(xyz + r, axis=0))
83
+ else:
84
+ assert len(ranges) == 2
85
+ coord_min = np.array(ranges[0])
86
+ coord_max = np.array(ranges[1])
87
+ assert len(coord_min) == len(coord_max) == 3
90
88
 
91
- xyz, r = x.xyz(), np.stack([x.r(), x.r(), x.r()], axis=1) # TODO: perf
92
- coord_min = np.floor(np.min(xyz - r, axis=0)) # TODO: snap to grid
93
- coord_max = np.ceil(np.max(xyz + r, axis=0))
94
- grids, total = self.get_grids(coord_min, coord_max)
89
+ samplers = self._get_samplers(coord_min, coord_max)
95
90
 
96
91
  if verbose:
92
+ from tqdm import tqdm
93
+
94
+ total = (coord_max[2] - coord_min[2]) / self.resolution[2]
95
+ samplers = tqdm(samplers, total=total.astype(np.int64).item())
96
+
97
97
  time_end = time.time()
98
98
  print("Prepare in: ", time_end - time_start, "s") # type: ignore
99
99
 
100
- for grid in tqdm(grids, total=total) if verbose else grids:
101
- is_in = sdf.is_in(grid.reshape(-1, 3)).reshape(*grid.shape[:4])
102
- level = np.sum(is_in, axis=3, dtype=np.uint8)
103
- voxel = (255 / self.msaa * level).astype(np.uint8)
104
- for i in range(voxel.shape[2]):
105
- yield voxel[:, :, i]
100
+ for sampler in samplers:
101
+ voxel = sampler.sample(scene) # should be shape of (x, y, z, 3) and z = 1
102
+ frame = (255 * voxel[..., 0, 0]).astype(np.uint8)
103
+ yield frame
106
104
 
107
- def transform_and_save(self, fname: str, x: Tree, verbose: bool = True) -> None:
108
- self.save_tif(fname, self.transfrom(x, verbose=verbose))
105
+ def transform_and_save(
106
+ self, fname: str, x: Tree, verbose: bool = True, **kwargs
107
+ ) -> None:
108
+ self.save_tif(fname, self.transfrom(x, verbose=verbose, **kwargs))
109
109
 
110
110
  def transform_population(
111
111
  self, population: Population | str, verbose: bool = True
112
112
  ) -> None:
113
- if isinstance(population, str):
114
- population = Population.from_swc(population)
113
+ trees = (
114
+ Population.from_swc(population)
115
+ if isinstance(population, str)
116
+ else population
117
+ )
118
+
119
+ if verbose:
120
+ from tqdm import tqdm
121
+
122
+ trees = tqdm(trees)
115
123
 
116
124
  # TODO: multiprocess
117
- for tree in population:
125
+ for tree in trees:
118
126
  tif = re.sub(r".swc$", ".tif", tree.source)
119
127
  if not os.path.isfile(tif):
120
- self.transform_and_save(tif, tree, verbose=verbose)
128
+ self.transform_and_save(tif, tree, verbose=False)
129
+
130
+ def extra_repr(self):
131
+ res = ",".join(f"{a:.4f}" for a in self.resolution)
132
+ return f"resolution=({res})"
133
+
134
+ def _get_scene(self, x: Tree) -> Scene:
135
+ material = ColoredMaterial((1, 0, 0)).into()
136
+ scene = ObjectsScene()
137
+ scene.set_background((0, 0, 0))
138
+
139
+ def leave(n: Tree.Node, children: List[Tree.Node]) -> Tree.Node:
140
+ for c in children:
141
+ sdf = RoundCone(_tp3f(n.xyz()), _tp3f(c.xyz()), n.r, c.r).into()
142
+ scene.add_object(SDFObject(sdf, material).into())
121
143
 
122
- def get_grids(
123
- self, coord_min: npt.ArrayLike, coord_max: npt.ArrayLike
124
- ) -> Tuple[Iterable[npt.NDArray[np.float32]], int]:
125
- """Get point grid.
144
+ return n
145
+
146
+ x.traverse(leave=leave)
147
+ scene.build_bvh()
148
+ return scene.into()
149
+
150
+ def _get_samplers(
151
+ self,
152
+ coord_min: npt.NDArray,
153
+ coord_max: npt.NDArray,
154
+ offset: Optional[npt.NDArray] = None,
155
+ ) -> Iterable[RangeSampler]:
156
+ """Get Samplers.
126
157
 
127
158
  Parameters
128
159
  ----------
129
160
  coord_min, coord_max: npt.ArrayLike
130
161
  Coordinates array of shape (3,).
131
- z_per_iter : int
132
- Yeild z per iter, raising this option speeds up processing,
133
- but consumes more memory.
134
-
135
- Returns
136
- -------
137
- grid : npt.NDArray[np.float32]
138
- Array of shape (nx, ny, z_per_iter, k, 3).
139
162
  """
140
163
 
141
- k = np.cbrt(self.msaa)
142
- coord_min, coord_max = np.array(coord_min), np.array(coord_max)
143
- assert tuple(coord_min.shape) == (3,), "coord_min shoule be vector of 3d."
144
- assert tuple(coord_max.shape) == (3,), "coord_max shoule be vector of 3d."
145
-
146
- point_grid = np.mgrid[
147
- coord_min[0] : coord_max[0] : self.resolution[0],
148
- coord_min[1] : coord_max[1] : self.resolution[1],
149
- coord_min[2] : coord_max[2] : self.resolution[2],
150
- ] # (3, nx, ny, nz)
151
- point_grid = np.rollaxis(point_grid, 0, 4) # (nx, ny, nz, 3)
152
-
153
- step = self.resolution / (k + 1)
154
- ends = self.resolution - step / 2
155
- inter_grid = np.mgrid[
156
- step[0] : ends[0] : step[0],
157
- step[1] : ends[1] : step[1],
158
- step[2] : ends[2] : step[2],
159
- ] # (3, kx, ky, kz)
160
- inter_grid = np.rollaxis(inter_grid, 0, 4).reshape(-1, 3) # (k, 3)
161
-
162
- grids = np.expand_dims(point_grid, 3).repeat(k**3, axis=3)
163
- grids = cast(Any, grids + inter_grid)
164
-
165
- return (
166
- grids[:, :, i : i + self.z_per_iter]
167
- for i in range(0, grids.shape[2], self.z_per_iter)
168
- ), math.ceil(grids.shape[2] / self.z_per_iter)
169
-
170
- def get_sdf(self, x: Tree) -> SDF:
171
- T = Tuple[Tree.Node, List[SDF], SDF | None]
172
-
173
- def collect(n: Tree.Node, pre: List[T]) -> T:
174
- if len(pre) == 0:
175
- return (n, [], None)
176
-
177
- if len(pre) == 1:
178
- child, sub_sdfs, last = pre[0]
179
- sub_sdfs.append(SDFRoundCone(n.xyz(), child.xyz(), n.r, child.r))
180
- return (n, sub_sdfs, last)
181
-
182
- sdfs: List[SDF] = []
183
- for child, sub_sdfs, last in pre:
184
- sub_sdfs.append(SDFRoundCone(n.xyz(), child.xyz(), n.r, child.r))
185
- sdfs.append(SDFCompose.compose(sub_sdfs))
186
- if last is not None:
187
- sdfs.append(last)
188
-
189
- return (n, [], SDFCompose.compose(sdfs))
190
-
191
- _, sdfs, last = x.traverse(leave=collect)
192
- if len(sdfs) != 0:
193
- sdf = SDFCompose.compose(sdfs)
194
- if last is not None:
195
- sdf = SDFCompose.compose([sdf, last])
196
- elif last is not None:
197
- sdf = last
198
- else:
199
- raise ValueError("empty tree")
164
+ eps = 1e-6
165
+ stride = self.resolution
166
+ offset = offset or (stride / 2)
200
167
 
201
- return sdf
168
+ xmin, ymin, zmin = _tp3f(coord_min + offset)
169
+ xmax, ymax, zmax = _tp3f(coord_max)
170
+ z = zmin
171
+ while z < zmax:
172
+ yield RangeSampler(
173
+ (xmin, ymin, z), (xmax, ymax, z + stride[2] - eps), _tp3f(stride)
174
+ )
175
+ z += stride[2]
202
176
 
203
177
  @staticmethod
204
178
  def save_tif(
@@ -218,3 +192,9 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
218
192
  "axes": "ZXY",
219
193
  },
220
194
  )
195
+
196
+
197
+ def _tp3f(x: npt.NDArray) -> Tuple[float, float, float]:
198
+ """Convert to tuple of 3 floats."""
199
+ assert len(x) == 3
200
+ return (float(x[0]), float(x[1]), float(x[2]))
@@ -28,5 +28,5 @@ class Center(Transform[npt.NDArray[np.float32], npt.NDArray[np.float32]]):
28
28
  e = np.add(s, self.shape_out)
29
29
  return x[s[0] : e[0], s[1] : e[1], s[2] : e[2], :]
30
30
 
31
- def __repr__(self) -> str:
32
- return f"Center-{self.shape_out[0]}-{self.shape_out[1]}-{self.shape_out[2]}"
31
+ def extra_repr(self) -> str:
32
+ return f"shape_out=({','.join(str(a) for a in self.shape_out)})"
swcgeom/transforms/mst.py CHANGED
@@ -140,13 +140,8 @@ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
140
140
  t = sort_tree(t)
141
141
  return t
142
142
 
143
- def __repr__(self) -> str:
144
- return (
145
- f"PointsToCuntzMST"
146
- f"-bf-{self.bf}"
147
- f"-furcations-{self.furcations}"
148
- f"-{'exclude-soma' if self.exclude_soma else 'include-soma'}"
149
- ) # TODO: names, types
143
+ def extra_repr(self) -> str: # TODO: names, types
144
+ return f"bf={self.bf:.4f}, furcations={self.furcations}, exclude_soma={self.exclude_soma}, sort={self.sort}"
150
145
 
151
146
 
152
147
  class PointsToMST(PointsToCuntzMST): # pylint: disable=too-few-public-methods
@@ -173,6 +168,7 @@ class PointsToMST(PointsToCuntzMST): # pylint: disable=too-few-public-methods
173
168
  names : SWCNames, optional
174
169
  types : SWCTypes, optional
175
170
  """
171
+
176
172
  if k_furcations is not None:
177
173
  warnings.warn(
178
174
  "`PointsToMST(k_furcations=...)` has been renamed to "
@@ -191,9 +187,5 @@ class PointsToMST(PointsToCuntzMST): # pylint: disable=too-few-public-methods
191
187
  **kwargs,
192
188
  )
193
189
 
194
- def __repr__(self) -> str:
195
- return (
196
- f"PointsToMST"
197
- f"-furcations-{self.furcations}"
198
- f"-{'exclude-soma' if self.exclude_soma else 'include-soma'}"
199
- )
190
+ def extra_repr(self) -> str:
191
+ return f"furcations-{self.furcations}, exclude-soma={self.exclude_soma}"
@@ -25,5 +25,5 @@ class PopulationTransform(Transform[Population, Population]):
25
25
 
26
26
  return Population(trees, root=population.root)
27
27
 
28
- def __repr__(self) -> str:
29
- return f"pop({self.transform})"
28
+ def extra_repr(self) -> str:
29
+ return f"transform={self.transform}"
@@ -64,8 +64,8 @@ class TreeSmoother(Transform[Tree, Tree]): # pylint: disable=missing-class-docs
64
64
 
65
65
  return x
66
66
 
67
- def __repr__(self) -> str:
68
- return f"TreeSmoother-{self.n_nodes}"
67
+ def extra_repr(self):
68
+ return f"n_nodes={self.n_nodes}"
69
69
 
70
70
 
71
71
  class TreeNormalizer(Normalizer[Tree]):
@@ -107,8 +107,8 @@ class CutByType(Transform[Tree, Tree]):
107
107
  y = to_subtree(x, removals)
108
108
  return y
109
109
 
110
- def __repr__(self) -> str:
111
- return f"CutByType-{self.type}"
110
+ def extra_repr(self):
111
+ return f"type={self.type}"
112
112
 
113
113
 
114
114
  class CutAxonTree(CutByType):
@@ -118,9 +118,6 @@ class CutAxonTree(CutByType):
118
118
  types = get_types(types)
119
119
  super().__init__(type=types.axon)
120
120
 
121
- def __repr__(self) -> str:
122
- return "CutAxonTree"
123
-
124
121
 
125
122
  class CutDendriteTree(CutByType):
126
123
  """Cut dendrite tree."""
@@ -129,9 +126,6 @@ class CutDendriteTree(CutByType):
129
126
  types = get_types(types)
130
127
  super().__init__(type=types.basal_dendrite) # TODO: apical dendrite
131
128
 
132
- def __repr__(self) -> str:
133
- return "CutDenriteTree"
134
-
135
129
 
136
130
  class CutByBifurcationOrder(Transform[Tree, Tree]):
137
131
  """Cut tree by bifurcation order."""
@@ -177,9 +171,6 @@ class CutShortTipBranch(Transform[Tree, Tree]):
177
171
  if callback is not None:
178
172
  self.callbacks.append(callback)
179
173
 
180
- def __repr__(self) -> str:
181
- return f"CutShortTipBranch-{self.thre}"
182
-
183
174
  def __call__(self, x: Tree) -> Tree:
184
175
  removals: List[int] = []
185
176
  self.callbacks.append(lambda br: removals.append(br[1].id))
@@ -187,6 +178,9 @@ class CutShortTipBranch(Transform[Tree, Tree]):
187
178
  self.callbacks.pop()
188
179
  return to_subtree(x, removals)
189
180
 
181
+ def extra_repr(self):
182
+ return f"threshold={self.thre}"
183
+
190
184
  def _leave(
191
185
  self, n: Tree.Node, children: List[Tuple[float, Tree.Node] | None]
192
186
  ) -> Tuple[float, Tree.Node] | None:
@@ -1,17 +1,22 @@
1
1
  """Assemble a tree."""
2
2
 
3
+ from copy import copy
3
4
  from typing import Iterable, List, Optional, Tuple
4
5
 
6
+ import numpy as np
5
7
  import pandas as pd
6
8
 
7
9
  from swcgeom.core import Tree
8
- from swcgeom.core.swc_utils import SWCNames
9
- from swcgeom.core.swc_utils.assembler import (
10
- assemble_lines_impl,
11
- try_assemble_lines_impl,
10
+ from swcgeom.core.swc_utils import (
11
+ SWCNames,
12
+ get_names,
13
+ link_roots_to_nearest_,
14
+ sort_nodes_,
12
15
  )
13
16
  from swcgeom.transforms.base import Transform
14
17
 
18
+ EPS = 1e-5
19
+
15
20
 
16
21
  class LinesToTree(Transform[List[pd.DataFrame], Tree]):
17
22
  """Assemble lines to swc."""
@@ -35,11 +40,12 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
35
40
  ):
36
41
  return self.assemble(lines, names=names)
37
42
 
38
- def __repr__(self) -> str:
39
- return f"LinesToTree-thre-{self.thre}-{'undirected' if self.undirected else 'directed'}"
40
-
41
43
  def assemble(
42
- self, lines: Iterable[pd.DataFrame], *, names: Optional[SWCNames] = None
44
+ self,
45
+ lines: Iterable[pd.DataFrame],
46
+ *,
47
+ undirected: bool = True,
48
+ names: Optional[SWCNames] = None,
43
49
  ) -> pd.DataFrame:
44
50
  """Assemble lines to a tree.
45
51
 
@@ -51,6 +57,8 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
51
57
  lines : List of ~pd.DataFrame
52
58
  An array of tables containing a line, columns should follwing
53
59
  the swc.
60
+ undirected : bool, default `True`
61
+ Forwarding to `self.try_assemble`.
54
62
  names : SWCNames, optional
55
63
  Forwarding to `self.try_assemble`.
56
64
 
@@ -60,17 +68,33 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
60
68
 
61
69
  See Also
62
70
  --------
63
- self.try_assemble_lines
71
+ self.try_assemble
64
72
  """
65
- return assemble_lines_impl(
66
- lines, thre=self.thre, undirected=self.undirected, names=names
73
+
74
+ tree, lines = self.try_assemble(
75
+ lines, sort_nodes=False, undirected=undirected, names=names
67
76
  )
77
+ while len(lines) > 0:
78
+ t, lines = self.try_assemble(
79
+ lines,
80
+ id_offset=len(tree),
81
+ sort_nodes=False,
82
+ undirected=undirected,
83
+ names=names,
84
+ )
85
+ tree = pd.concat([tree, t])
86
+
87
+ tree = tree.reset_index()
88
+ link_roots_to_nearest_(tree)
89
+ sort_nodes_(tree)
90
+ return tree
68
91
 
69
92
  def try_assemble(
70
93
  self,
71
94
  lines: Iterable[pd.DataFrame],
72
95
  *,
73
96
  id_offset: int = 0,
97
+ undirected: bool = True,
74
98
  sort_nodes: bool = True,
75
99
  names: Optional[SWCNames] = None,
76
100
  ) -> Tuple[pd.DataFrame, List[pd.DataFrame]]:
@@ -88,6 +112,9 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
88
112
  the swc.
89
113
  id_offset : int, default `0`
90
114
  The offset of the line node id.
115
+ undirected : bool, default `True`
116
+ Both ends of a line can be considered connection point. If
117
+ `False`, only the starting point.
91
118
  sort_nodes : bool, default `True`
92
119
  sort nodes of subtree.
93
120
  names : SWCNames, optional
@@ -97,11 +124,50 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
97
124
  tree : ~pandas.DataFrame
98
125
  remaining_lines : List of ~pandas.DataFrame
99
126
  """
100
- return try_assemble_lines_impl(
101
- lines,
102
- undirected=self.undirected,
103
- thre=self.thre,
104
- id_offset=id_offset,
105
- sort_nodes=sort_nodes,
106
- names=names,
107
- )
127
+
128
+ names = get_names(names)
129
+ lines = copy(list(lines))
130
+
131
+ tree = lines[0]
132
+ tree[names.id] = id_offset + np.arange(len(tree))
133
+ tree[names.pid] = tree[names.id] - 1
134
+ tree.at[0, names.pid] = -1
135
+ del lines[0]
136
+
137
+ while True:
138
+ for i, line in enumerate(lines):
139
+ for p in [0, -1] if undirected else [0]:
140
+ xyz = [names.x, names.y, names.z]
141
+ vs = tree[xyz] - line.iloc[p][xyz]
142
+ dis = np.linalg.norm(vs, axis=1)
143
+ ind = np.argmin(dis)
144
+ if dis[ind] > self.thre:
145
+ continue
146
+
147
+ if dis[ind] < EPS:
148
+ line = line.drop((p + len(line)) % len(line)).reset_index(
149
+ drop=True
150
+ )
151
+
152
+ line[names.id] = id_offset + len(tree) + np.arange(len(line))
153
+ line[names.pid] = line[names.id] + (-1 if p == 0 else 1)
154
+ line.at[(p + len(line)) % len(line), names.pid] = tree.iloc[ind][
155
+ names.id
156
+ ]
157
+ tree = pd.concat([tree, line])
158
+ del lines[i]
159
+ break
160
+ else:
161
+ continue
162
+
163
+ break
164
+ else:
165
+ break
166
+
167
+ if sort_nodes:
168
+ sort_nodes_(tree)
169
+
170
+ return tree, lines
171
+
172
+ def extra_repr(self):
173
+ return f"thre={self.thre}, undirected={self.undirected}"
swcgeom/utils/__init__.py CHANGED
@@ -4,10 +4,10 @@ from swcgeom.utils.debug import *
4
4
  from swcgeom.utils.dsu import *
5
5
  from swcgeom.utils.ellipse import *
6
6
  from swcgeom.utils.file import *
7
- from swcgeom.utils.geometry_object import *
8
7
  from swcgeom.utils.neuromorpho import *
9
8
  from swcgeom.utils.numpy_helper import *
10
9
  from swcgeom.utils.renderer import *
11
10
  from swcgeom.utils.sdf import *
12
11
  from swcgeom.utils.solid_geometry import *
13
12
  from swcgeom.utils.transforms import *
13
+ from swcgeom.utils.volumetric_object import *