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.
- swcgeom/_version.py +2 -2
- swcgeom/analysis/volume.py +105 -20
- swcgeom/core/branch_tree.py +2 -3
- swcgeom/core/population.py +6 -7
- swcgeom/core/swc_utils/assembler.py +12 -141
- swcgeom/core/swc_utils/subtree.py +2 -2
- swcgeom/core/tree.py +19 -12
- swcgeom/core/tree_utils.py +23 -5
- swcgeom/core/tree_utils_impl.py +22 -6
- swcgeom/images/folder.py +42 -17
- swcgeom/images/io.py +65 -36
- swcgeom/transforms/base.py +41 -21
- swcgeom/transforms/branch.py +5 -5
- swcgeom/transforms/geometry.py +42 -18
- swcgeom/transforms/image_stack.py +104 -124
- swcgeom/transforms/images.py +2 -2
- swcgeom/transforms/mst.py +5 -13
- swcgeom/transforms/population.py +2 -2
- swcgeom/transforms/tree.py +7 -13
- swcgeom/transforms/tree_assembler.py +85 -19
- swcgeom/utils/__init__.py +1 -1
- swcgeom/utils/sdf.py +167 -10
- swcgeom/utils/solid_geometry.py +26 -0
- swcgeom/utils/volumetric_object.py +504 -0
- swcgeom-0.15.0.dist-info/LICENSE +201 -0
- {swcgeom-0.13.2.dist-info → swcgeom-0.15.0.dist-info}/METADATA +6 -5
- {swcgeom-0.13.2.dist-info → swcgeom-0.15.0.dist-info}/RECORD +29 -29
- swcgeom/utils/geometry_object.py +0 -255
- swcgeom-0.13.2.dist-info/LICENSE +0 -3
- {swcgeom-0.13.2.dist-info → swcgeom-0.15.0.dist-info}/WHEEL +0 -0
- {swcgeom-0.13.2.dist-info → swcgeom-0.15.0.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: swcgeom
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: Neuron geometry library for swc format
|
|
5
5
|
Author-email: yzx9 <yuan.zx@outlook.com>
|
|
6
|
-
License:
|
|
6
|
+
License: Apache-2.0
|
|
7
7
|
Project-URL: repository, https://github.com/yzx9/swcgeom
|
|
8
8
|
Keywords: neuronscience,neuron,neuroanatomy,neuron-morphology
|
|
9
9
|
Requires-Python: >=3.10
|
|
@@ -15,6 +15,7 @@ Requires-Dist: numpy >=1.22.3
|
|
|
15
15
|
Requires-Dist: pandas >=1.4.2
|
|
16
16
|
Requires-Dist: pynrrd >=1.0.0
|
|
17
17
|
Requires-Dist: scipy >=1.9.1
|
|
18
|
+
Requires-Dist: sdflit >=0.2.1
|
|
18
19
|
Requires-Dist: seaborn >=0.12.0
|
|
19
20
|
Requires-Dist: tifffile >=2022.8.12
|
|
20
21
|
Requires-Dist: typing-extensions >=4.4.0
|
|
@@ -30,8 +31,6 @@ Requires-Dist: urllib3 >=1.26.0 ; extra == 'all'
|
|
|
30
31
|
|
|
31
32
|
# SWCGEOM
|
|
32
33
|
|
|
33
|
-
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><br />
|
|
34
|
-
|
|
35
34
|
[](https://github.com/yzx9/swcgeom/actions/workflows/test.yml)
|
|
36
35
|
[](https://github.com/yzx9/swcgeom/releases)
|
|
37
36
|
[](https://pypi.org/project/swcgeom/)
|
|
@@ -67,4 +66,6 @@ Static analysis don't support import hook used in editable install for [PEP660](
|
|
|
67
66
|
|
|
68
67
|
## LICENSE
|
|
69
68
|
|
|
70
|
-
This work is licensed under a <a rel="license" href="
|
|
69
|
+
This work is licensed under a <a rel="license" href="https://www.apache.org/licenses/">Apache-2.0</a>.
|
|
70
|
+
|
|
71
|
+
Copyright (c) 2022-present, Zexin Yuan
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
swcgeom/__init__.py,sha256=z88Zwcjv-ii7c7dYd9QPg9XrUVorQjtrgGbQCsEnQhc,265
|
|
2
|
-
swcgeom/_version.py,sha256=
|
|
2
|
+
swcgeom/_version.py,sha256=oHv-EAjiXbJma3jZ0Tq6UPimiWYyyw2Ao9S8zdq9uWs,413
|
|
3
3
|
swcgeom/analysis/__init__.py,sha256=esL_poW8u-_bmp7vR9ldcumX3_xodtaVfM1USqxQo5w,377
|
|
4
4
|
swcgeom/analysis/branch_features.py,sha256=s6PTMwwvxrVtZXRZlQbUSIw4M9-1IG63kf-Nxc0tMB0,1958
|
|
5
5
|
swcgeom/analysis/feature_extractor.py,sha256=coe07_bJau96BkimcnXzuf4KqjY5_QRLwqaumFsu_tQ,14031
|
|
@@ -8,55 +8,55 @@ swcgeom/analysis/path_features.py,sha256=iE21HBxAoGLxk_qK7MBwQhyUOBqNPcnk4urVHr9
|
|
|
8
8
|
swcgeom/analysis/sholl.py,sha256=9hSW8rZm1gvSIgcEZg8IVPT8kzBgBfwqbwP4E8R7L44,6390
|
|
9
9
|
swcgeom/analysis/trunk.py,sha256=L2tjUIUmrRQpah_W3ZETGWd16bDXJ5F8Sk2XBNGms0Q,5558
|
|
10
10
|
swcgeom/analysis/visualization.py,sha256=mKOpzTPkLpr1ggGL1MZPZRTG92GEg4idLT4eN5z5KOs,5654
|
|
11
|
-
swcgeom/analysis/volume.py,sha256=
|
|
11
|
+
swcgeom/analysis/volume.py,sha256=nWPR7wGOk3Wl5eh97YMws0X-2jk8K7lmFp4-03wL3lY,4628
|
|
12
12
|
swcgeom/core/__init__.py,sha256=ZUudZavxAIUU6Q0lBHrQ4ybmL5lBfvzyYsTtpuih9wg,332
|
|
13
13
|
swcgeom/core/branch.py,sha256=uuJCxaByRu-OdDZVWEffSFcmZWY-6ZWUhHN1M2Awj1s,3980
|
|
14
|
-
swcgeom/core/branch_tree.py,sha256=
|
|
14
|
+
swcgeom/core/branch_tree.py,sha256=Ece6q1VNCRLLMj29N_MjXmmlHT8h4tpWCuDE0uSgKJo,1873
|
|
15
15
|
swcgeom/core/node.py,sha256=HvfgsW4WU01hkRIPci8KF4bQMAkwtAxOGfUL4yUbuBs,3623
|
|
16
16
|
swcgeom/core/path.py,sha256=CsEelHiDR0JPBP1dTvoCSRvX3kBlZxkQilosnncV4nQ,4188
|
|
17
|
-
swcgeom/core/population.py,sha256=
|
|
17
|
+
swcgeom/core/population.py,sha256=I9xSeGUveLhxkOg_5FWLcvf4yJVJ6j9_n03Hna3y_6w,8790
|
|
18
18
|
swcgeom/core/segment.py,sha256=yabRdFj7KlkJP4V67jAlCIRzpHduNnq3bRBIRMuANfA,3158
|
|
19
19
|
swcgeom/core/swc.py,sha256=lSYxAa25l6O8WZ9JtSSET-RZMr6EA1Tq_aXL_x0H9Rc,6795
|
|
20
|
-
swcgeom/core/tree.py,sha256=
|
|
21
|
-
swcgeom/core/tree_utils.py,sha256=
|
|
22
|
-
swcgeom/core/tree_utils_impl.py,sha256=
|
|
20
|
+
swcgeom/core/tree.py,sha256=K2k7o8OZ9r2YeMmIebT8_0qvkwJYtLLzFHECz3fmmts,12365
|
|
21
|
+
swcgeom/core/tree_utils.py,sha256=3aCHghny5Z727sxkt6P8E2MMr34vK6irfPCelMn3vk4,7681
|
|
22
|
+
swcgeom/core/tree_utils_impl.py,sha256=kN2ByjqqQtZUfmC_ac25tXOaE-CMiV2lP58VxFphLEU,1616
|
|
23
23
|
swcgeom/core/swc_utils/__init__.py,sha256=qghRxjtzvq5KKfN4HhvLpZNsGPfZQu-Jj2x62_5-TbQ,575
|
|
24
|
-
swcgeom/core/swc_utils/assembler.py,sha256=
|
|
24
|
+
swcgeom/core/swc_utils/assembler.py,sha256=_ByaVFc61rfCS2p9QUw4g40uF4pZ6NJaDc--TcV4jWo,642
|
|
25
25
|
swcgeom/core/swc_utils/base.py,sha256=huVxjuMLlTHbEb-KSEFDLgU0Ss3723t2Gr4Z_gQtl00,4737
|
|
26
26
|
swcgeom/core/swc_utils/checker.py,sha256=E72GtLZ_1IqQQ7aWQGs0dZ3Z609__bw3EYQqeWrk-EI,2657
|
|
27
27
|
swcgeom/core/swc_utils/io.py,sha256=6_--Qoe8kDja4PWsjwqRAvPJZNMFILFgauHaeWeGikU,6444
|
|
28
28
|
swcgeom/core/swc_utils/normalizer.py,sha256=_Ysi8bSJ2JBnIGB8o6BvAg2mcz6xuJp9rgNLZqpLuR8,5083
|
|
29
|
-
swcgeom/core/swc_utils/subtree.py,sha256=
|
|
29
|
+
swcgeom/core/swc_utils/subtree.py,sha256=43QITYvgXu3b_kfIod2Irrj3dSfrA-gTFev5VxzRafI,1995
|
|
30
30
|
swcgeom/images/__init__.py,sha256=QBP1ZGGo2nWAcV7Krz-vbvW_jN4ChqXrrpoScXcUURs,96
|
|
31
31
|
swcgeom/images/augmentation.py,sha256=v9zluYXmBEbafaDBTpvJovi4_KWJmHZZSvcYHzG0oWo,4099
|
|
32
|
-
swcgeom/images/folder.py,sha256=
|
|
33
|
-
swcgeom/images/io.py,sha256=
|
|
32
|
+
swcgeom/images/folder.py,sha256=2REkrdNghLm1z8kZ2PDVvtsupzog8kARkeMjLuLiLFo,4955
|
|
33
|
+
swcgeom/images/io.py,sha256=jUyKjtau8_5V-PN1kRsPHtP1OtueJi1zEf5-7PZ_tG8,21226
|
|
34
34
|
swcgeom/transforms/__init__.py,sha256=Mi2mOgkQ50JbZ9LmgXgJIuAA5eio67oe2AOs2CCCxTQ,463
|
|
35
|
-
swcgeom/transforms/base.py,sha256=
|
|
36
|
-
swcgeom/transforms/branch.py,sha256=
|
|
37
|
-
swcgeom/transforms/geometry.py,sha256=
|
|
38
|
-
swcgeom/transforms/image_stack.py,sha256=
|
|
39
|
-
swcgeom/transforms/images.py,sha256=
|
|
40
|
-
swcgeom/transforms/mst.py,sha256=
|
|
35
|
+
swcgeom/transforms/base.py,sha256=gN5Iqi-OHkYrsjllSOdxI6Yzav3jJGoi6kUPy-38FAs,4101
|
|
36
|
+
swcgeom/transforms/branch.py,sha256=R0rVti--u70IiUKyHSx6MsDYJyy6zSCf18Uia2Cmh28,5410
|
|
37
|
+
swcgeom/transforms/geometry.py,sha256=XR73fO_8T7otUFIllqKOWW0OnrsXBc7yA01oDT99yMc,7385
|
|
38
|
+
swcgeom/transforms/image_stack.py,sha256=KhZ26Ps88jk_7NkI9dkS2E7NZXUvMaN3Ln9WzJ-vPu0,5823
|
|
39
|
+
swcgeom/transforms/images.py,sha256=l5Hx8x27zoClUz3s11j2oj0Lt9ROh5JJfjoIU_vd3H8,898
|
|
40
|
+
swcgeom/transforms/mst.py,sha256=ceL_EWpCtoSy9zYD6wPP5zO68eNTrDl9JExoPLcE_Lw,6236
|
|
41
41
|
swcgeom/transforms/path.py,sha256=Gk2iunGQMX7vE83bdo8xoDO-KAT1Vvep0iZs7oFLzFQ,1089
|
|
42
|
-
swcgeom/transforms/population.py,sha256=
|
|
43
|
-
swcgeom/transforms/tree.py,sha256=
|
|
44
|
-
swcgeom/transforms/tree_assembler.py,sha256=
|
|
45
|
-
swcgeom/utils/__init__.py,sha256=
|
|
42
|
+
swcgeom/transforms/population.py,sha256=EmZ6ntuOKe8mXJxMW7nCUA-w2DVlEVe2n0IOVz49tCY,833
|
|
43
|
+
swcgeom/transforms/tree.py,sha256=Q45sVA7yi8pINV7ENOWPKz6DnEM3U5uJxwkFKNFS5VE,6262
|
|
44
|
+
swcgeom/transforms/tree_assembler.py,sha256=vi_X9CNo5IxHP5J7bRl2z91PWufU6HmYlz1iyfdPUxE,5121
|
|
45
|
+
swcgeom/utils/__init__.py,sha256=QfezYuQzgmPziNiWz2T1h77V-gjpuBoUi3mC-K-PZlI,427
|
|
46
46
|
swcgeom/utils/debug.py,sha256=qay2qJpViLX82mzxdndxQFn-pi1vaEj9CbLGuGt8Y9k,465
|
|
47
47
|
swcgeom/utils/download.py,sha256=By2qZezo6h1Ke_4YpSIhDgcisOrpjVqRmNzbhynC2xs,2834
|
|
48
48
|
swcgeom/utils/dsu.py,sha256=3aCbtpnl_D0OXnowTS8-kuwnCS4BKBYL5ECiFQ1fUW8,1435
|
|
49
49
|
swcgeom/utils/ellipse.py,sha256=LB3q5CIy75GEUdTauIpKySwIHaDrwXzzkBhOCnjJ8Vw,3259
|
|
50
50
|
swcgeom/utils/file.py,sha256=1hchQDsPgn-i-Vz5OQtcogxav_ajCQ_OaEZCLmqczRg,2515
|
|
51
|
-
swcgeom/utils/geometry_object.py,sha256=ayw9hzrxJXSIcEfYVSOXvPaQl8J9HYCGotGOfuH5RKA,7436
|
|
52
51
|
swcgeom/utils/neuromorpho.py,sha256=CDK2tUM2pNwHv_lEserHhQs_VlY3Rn557-jtV63EFlk,14420
|
|
53
52
|
swcgeom/utils/numpy_helper.py,sha256=A-F-eFdGktCHVAQ_HcXiFB3Y1YhhSNfAmtOl8483Dvo,1292
|
|
54
53
|
swcgeom/utils/renderer.py,sha256=xHVZ06Z1MeKBPC3nKzuwA1HryzR0ga79y6johZA4-q0,7290
|
|
55
|
-
swcgeom/utils/sdf.py,sha256=
|
|
56
|
-
swcgeom/utils/solid_geometry.py,sha256=
|
|
54
|
+
swcgeom/utils/sdf.py,sha256=K-LSnwwNsGF85GW1kNBaqXAAVesxZAEsrn0nvOA2LcA,10614
|
|
55
|
+
swcgeom/utils/solid_geometry.py,sha256=TV02jhcoCLCqtYA9hfE50LFD_VRfixMiOSiHB5Jb2_U,2431
|
|
57
56
|
swcgeom/utils/transforms.py,sha256=PmP5fL_iVguq4GR2aqXhM0TeCsiFVnrPZMZG6zLohrE,6983
|
|
58
|
-
swcgeom
|
|
59
|
-
swcgeom-0.
|
|
60
|
-
swcgeom-0.
|
|
61
|
-
swcgeom-0.
|
|
62
|
-
swcgeom-0.
|
|
57
|
+
swcgeom/utils/volumetric_object.py,sha256=DVRGGmQrAL0oaW6hbNtp5TStbic9DfyJdCzsv2FNw2c,15134
|
|
58
|
+
swcgeom-0.15.0.dist-info/LICENSE,sha256=JPtohhZ4XURqoKI0ZqnMYb7dobCOoZR_n5EpnaLTp3E,11344
|
|
59
|
+
swcgeom-0.15.0.dist-info/METADATA,sha256=VJX8KlMJWpYCZ-bMUYy_kL05sAHXmvHLMqUepQaKNTQ,2347
|
|
60
|
+
swcgeom-0.15.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
61
|
+
swcgeom-0.15.0.dist-info/top_level.txt,sha256=hmLyUXWS61Gxl07haswFEKKefYPBVJYlUlol8ghNkjY,8
|
|
62
|
+
swcgeom-0.15.0.dist-info/RECORD,,
|
swcgeom/utils/geometry_object.py
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
"""Geometry object."""
|
|
2
|
-
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from functools import lru_cache
|
|
5
|
-
|
|
6
|
-
import numpy as np
|
|
7
|
-
import numpy.typing as npt
|
|
8
|
-
|
|
9
|
-
from swcgeom.utils.solid_geometry import (
|
|
10
|
-
find_sphere_line_intersection,
|
|
11
|
-
find_unit_vector_on_plane,
|
|
12
|
-
project_point_on_line,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
__all__ = ["GeomObject", "GeomSphere", "GeomFrustumCone"]
|
|
16
|
-
|
|
17
|
-
eps = 1e-6
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class GeomObject(ABC):
|
|
21
|
-
"""Geometry object."""
|
|
22
|
-
|
|
23
|
-
@abstractmethod
|
|
24
|
-
def get_volume(self) -> float:
|
|
25
|
-
"""Get volume."""
|
|
26
|
-
raise NotImplementedError()
|
|
27
|
-
|
|
28
|
-
@abstractmethod
|
|
29
|
-
def get_intersect_volume(self, obj: "GeomObject") -> float:
|
|
30
|
-
"""Get intersect volume.
|
|
31
|
-
|
|
32
|
-
Parameters
|
|
33
|
-
----------
|
|
34
|
-
obj : GeometryObject
|
|
35
|
-
Another geometry object.
|
|
36
|
-
|
|
37
|
-
Returns
|
|
38
|
-
-------
|
|
39
|
-
volume : float
|
|
40
|
-
Intersect volume.
|
|
41
|
-
"""
|
|
42
|
-
raise NotImplementedError()
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class GeomSphere(GeomObject):
|
|
46
|
-
"""Geometry Sphere."""
|
|
47
|
-
|
|
48
|
-
def __init__(self, center: npt.ArrayLike, radius: float):
|
|
49
|
-
super().__init__()
|
|
50
|
-
|
|
51
|
-
self.center = np.array(center)
|
|
52
|
-
assert len(self.center) == 3
|
|
53
|
-
|
|
54
|
-
self.radius = radius
|
|
55
|
-
|
|
56
|
-
def get_volume(self) -> float:
|
|
57
|
-
return self.calc_volume(self.radius)
|
|
58
|
-
|
|
59
|
-
def get_volume_spherical_cap(self, h: float) -> float:
|
|
60
|
-
return self.calc_volume_spherical_cap(self.radius, h)
|
|
61
|
-
|
|
62
|
-
def get_intersect_volume_sphere(self, obj: "GeomSphere") -> float:
|
|
63
|
-
return self.calc_intersect_volume_sphere(self, obj)
|
|
64
|
-
|
|
65
|
-
def get_intersect_volume_sphere_frustum_cone(
|
|
66
|
-
self, frustum_cone: "GeomFrustumCone"
|
|
67
|
-
) -> float:
|
|
68
|
-
return calc_intersect_volume_sphere_frustum_cone(self, frustum_cone)
|
|
69
|
-
|
|
70
|
-
def get_intersect_volume(self, obj: GeomObject) -> float:
|
|
71
|
-
if isinstance(obj, GeomSphere):
|
|
72
|
-
return self.get_intersect_volume_sphere(obj)
|
|
73
|
-
|
|
74
|
-
if isinstance(obj, GeomFrustumCone):
|
|
75
|
-
return self.get_intersect_volume_sphere_frustum_cone(obj)
|
|
76
|
-
|
|
77
|
-
classname = obj.__class__.__name__
|
|
78
|
-
raise NotImplementedError(f"unsupported geometry object: {classname}")
|
|
79
|
-
|
|
80
|
-
@staticmethod
|
|
81
|
-
def calc_volume(radius: float) -> float:
|
|
82
|
-
r"""Calculate volume of sphere.
|
|
83
|
-
|
|
84
|
-
\being{equation}
|
|
85
|
-
V = \frac{4}{3} * π * r^3
|
|
86
|
-
\end{equation}
|
|
87
|
-
|
|
88
|
-
Returns
|
|
89
|
-
-------
|
|
90
|
-
volume : float
|
|
91
|
-
Volume.
|
|
92
|
-
"""
|
|
93
|
-
return 4 / 3 * np.pi * radius**3
|
|
94
|
-
|
|
95
|
-
@staticmethod
|
|
96
|
-
def calc_volume_spherical_cap(r: float, h: float) -> float:
|
|
97
|
-
r"""Calculate the volume of a spherical cap.
|
|
98
|
-
|
|
99
|
-
\being{equation}
|
|
100
|
-
V = π * h^2 * (3r - h) / 3
|
|
101
|
-
\end{equation}
|
|
102
|
-
|
|
103
|
-
Parameters
|
|
104
|
-
----------
|
|
105
|
-
r : float
|
|
106
|
-
radius of the sphere
|
|
107
|
-
h : float
|
|
108
|
-
height of the spherical cap
|
|
109
|
-
|
|
110
|
-
Returns
|
|
111
|
-
-------
|
|
112
|
-
volume : float
|
|
113
|
-
volume of the spherical cap
|
|
114
|
-
"""
|
|
115
|
-
return np.pi * h**2 * (3 * r - h) / 3
|
|
116
|
-
|
|
117
|
-
@classmethod
|
|
118
|
-
def calc_intersect_volume_sphere(
|
|
119
|
-
cls, obj1: "GeomSphere", obj2: "GeomSphere"
|
|
120
|
-
) -> float:
|
|
121
|
-
r"""Calculate intersect volume of two spheres.
|
|
122
|
-
|
|
123
|
-
\being{equation}
|
|
124
|
-
V = \frac{\pi}{12d} * (r_1 + r_2 - d)^2 (d^2 + 2d r_1 - 3r_1^2 + 2d r_2 - 3r_2^2 + 6 r_1r_2)
|
|
125
|
-
\end{equation}
|
|
126
|
-
|
|
127
|
-
Returns
|
|
128
|
-
-------
|
|
129
|
-
volume : float
|
|
130
|
-
Intersect volume.
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
r1, r2 = obj1.radius, obj2.radius
|
|
134
|
-
d = np.linalg.norm(obj1.center - obj2.center).item()
|
|
135
|
-
if d > r1 + r2:
|
|
136
|
-
return 0
|
|
137
|
-
|
|
138
|
-
if d <= abs(r1 - r2):
|
|
139
|
-
return cls.calc_volume(min(r1, r2))
|
|
140
|
-
|
|
141
|
-
part1 = (np.pi / (12 * d)) * (r1 + r2 - d) ** 2
|
|
142
|
-
part2 = (
|
|
143
|
-
d**2 + 2 * d * r1 - 3 * r1**2 + 2 * d * r2 - 3 * r2**2 + 6 * r1 * r2
|
|
144
|
-
)
|
|
145
|
-
return part1 * part2
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
class GeomFrustumCone(GeomObject):
|
|
149
|
-
"""Geometry Frustum."""
|
|
150
|
-
|
|
151
|
-
def __init__(self, c1: npt.ArrayLike, r1: float, c2: npt.ArrayLike, r2: float):
|
|
152
|
-
super().__init__()
|
|
153
|
-
|
|
154
|
-
self.c1 = np.array(c1)
|
|
155
|
-
assert len(self.c1) == 3
|
|
156
|
-
|
|
157
|
-
self.c2 = np.array(c2)
|
|
158
|
-
assert len(self.c2) == 3
|
|
159
|
-
|
|
160
|
-
self.r1 = r1
|
|
161
|
-
self.r2 = r2
|
|
162
|
-
|
|
163
|
-
def height(self) -> float:
|
|
164
|
-
"""Get height of frustum."""
|
|
165
|
-
return np.linalg.norm(self.c1 - self.c2).item()
|
|
166
|
-
|
|
167
|
-
def get_volume(self) -> float:
|
|
168
|
-
return self.calc_volume(self.r1, self.r2, self.height())
|
|
169
|
-
|
|
170
|
-
def get_intersect_volume_sphere(self, sphere: GeomSphere) -> float:
|
|
171
|
-
return calc_intersect_volume_sphere_frustum_cone(sphere, self)
|
|
172
|
-
|
|
173
|
-
def get_intersect_volume(self, obj: GeomObject) -> float:
|
|
174
|
-
if isinstance(obj, GeomSphere):
|
|
175
|
-
return self.get_intersect_volume_sphere(obj)
|
|
176
|
-
|
|
177
|
-
classname = obj.__class__.__name__
|
|
178
|
-
raise NotImplementedError(f"unsupported geometry object: {classname}")
|
|
179
|
-
|
|
180
|
-
@staticmethod
|
|
181
|
-
def calc_volume(r1: float, r2: float, height: float) -> float:
|
|
182
|
-
r"""Calculate volume of frustum.
|
|
183
|
-
|
|
184
|
-
\being{equation}
|
|
185
|
-
V = \frac{1}{3} * π * h * (r^2 + r * R + R^2)
|
|
186
|
-
\end{equation}
|
|
187
|
-
|
|
188
|
-
Returns
|
|
189
|
-
-------
|
|
190
|
-
volume : float
|
|
191
|
-
Volume.
|
|
192
|
-
"""
|
|
193
|
-
return (1 / 3) * np.pi * height * (r1**2 + r1 * r2 + r2**2)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@lru_cache
|
|
197
|
-
def calc_intersect_volume_sphere_frustum_cone(
|
|
198
|
-
sphere: GeomSphere, frustum_cone: GeomFrustumCone
|
|
199
|
-
) -> float:
|
|
200
|
-
r"""Calculate intersect volume of sphere and frustum cone.
|
|
201
|
-
|
|
202
|
-
Returns
|
|
203
|
-
-------
|
|
204
|
-
volume : float
|
|
205
|
-
Intersect volume.
|
|
206
|
-
"""
|
|
207
|
-
h = frustum_cone.height()
|
|
208
|
-
c1, r1 = sphere.center, sphere.radius
|
|
209
|
-
if np.allclose(c1, frustum_cone.c1) and np.allclose(r1, frustum_cone.r1):
|
|
210
|
-
c2, r2 = frustum_cone.c2, frustum_cone.r2
|
|
211
|
-
elif np.allclose(c1, frustum_cone.c2) and np.allclose(r1, frustum_cone.r2):
|
|
212
|
-
c2, r2 = frustum_cone.c1, frustum_cone.r1
|
|
213
|
-
else:
|
|
214
|
-
raise NotImplementedError("unsupported to calculate intersect volume")
|
|
215
|
-
|
|
216
|
-
# Fast-Path: The surface of the frustum concentric with the sphere
|
|
217
|
-
# is the surface with smaller radius
|
|
218
|
-
if r2 - r1 >= -eps: # r2 >= r1:
|
|
219
|
-
v_himisphere = GeomSphere.calc_volume_spherical_cap(r1, r1)
|
|
220
|
-
if h >= r1:
|
|
221
|
-
# The hemisphere is completely inside the frustum cone
|
|
222
|
-
return v_himisphere
|
|
223
|
-
|
|
224
|
-
# The frustum cone is lower than the hemisphere
|
|
225
|
-
v_cap = GeomSphere.calc_volume_spherical_cap(r1, r1 - h)
|
|
226
|
-
return v_himisphere - v_cap
|
|
227
|
-
|
|
228
|
-
up = (c2 - c1) / np.linalg.norm(c2 - c1)
|
|
229
|
-
v = find_unit_vector_on_plane(up)
|
|
230
|
-
|
|
231
|
-
intersections = find_sphere_line_intersection(c1, r1, c1 + r1 * v, c2 + r2 * v)
|
|
232
|
-
if len(intersections) == 0:
|
|
233
|
-
# Tricky case: Since the intersection point not found with
|
|
234
|
-
# numerical precision, we can simply assume that there are two
|
|
235
|
-
# intersection points and at the same position
|
|
236
|
-
intersections = [(0, c1 + r1 * v), (0, c1 + r1 * v)]
|
|
237
|
-
assert len(intersections) == 2
|
|
238
|
-
t, p = max(intersections, key=lambda x: x[0])
|
|
239
|
-
|
|
240
|
-
# Fast-Path: The frustum cone is completely inside the sphere
|
|
241
|
-
if t > 1 + eps:
|
|
242
|
-
return frustum_cone.get_volume()
|
|
243
|
-
|
|
244
|
-
M = project_point_on_line(c1, up, p)
|
|
245
|
-
h1 = np.linalg.norm(M - c1).item()
|
|
246
|
-
r3 = np.linalg.norm(M - p).item()
|
|
247
|
-
v_cap1 = GeomSphere.calc_volume_spherical_cap(r1, r1 - h1)
|
|
248
|
-
v_frustum = GeomFrustumCone.calc_volume(r1, r3, h1)
|
|
249
|
-
|
|
250
|
-
# Fast-Path: The frustum cone is higher than the sphere
|
|
251
|
-
if h >= r1:
|
|
252
|
-
return v_cap1 + v_frustum
|
|
253
|
-
|
|
254
|
-
v_cap2 = GeomSphere.calc_volume_spherical_cap(r1, r1 - h)
|
|
255
|
-
return v_cap1 + v_frustum - v_cap2
|
swcgeom-0.13.2.dist-info/LICENSE
DELETED
|
File without changes
|
|
File without changes
|