swcgeom 0.11.1__py3-none-any.whl → 0.13.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/__init__.py +4 -4
- swcgeom/_version.py +14 -2
- swcgeom/analysis/__init__.py +7 -7
- swcgeom/analysis/branch_features.py +1 -1
- swcgeom/analysis/feature_extractor.py +25 -12
- swcgeom/analysis/node_features.py +1 -1
- swcgeom/analysis/path_features.py +1 -1
- swcgeom/analysis/sholl.py +11 -7
- swcgeom/analysis/trunk.py +5 -5
- swcgeom/analysis/visualization.py +2 -2
- swcgeom/analysis/volume.py +80 -0
- swcgeom/core/__init__.py +9 -9
- swcgeom/core/branch.py +8 -4
- swcgeom/core/branch_tree.py +4 -5
- swcgeom/core/node.py +5 -3
- swcgeom/core/path.py +6 -3
- swcgeom/core/population.py +2 -2
- swcgeom/core/segment.py +8 -4
- swcgeom/core/swc.py +24 -3
- swcgeom/core/swc_utils/__init__.py +6 -6
- swcgeom/core/swc_utils/assembler.py +2 -2
- swcgeom/core/swc_utils/base.py +30 -1
- swcgeom/core/swc_utils/checker.py +30 -6
- swcgeom/core/swc_utils/io.py +31 -30
- swcgeom/core/swc_utils/normalizer.py +1 -1
- swcgeom/core/swc_utils/subtree.py +1 -1
- swcgeom/core/tree.py +38 -14
- swcgeom/core/tree_utils.py +47 -41
- swcgeom/core/tree_utils_impl.py +39 -0
- swcgeom/images/__init__.py +2 -2
- swcgeom/images/folder.py +2 -2
- swcgeom/images/io.py +48 -9
- swcgeom/transforms/__init__.py +10 -8
- swcgeom/transforms/branch.py +3 -3
- swcgeom/transforms/geometry.py +11 -4
- swcgeom/transforms/image_stack.py +3 -3
- swcgeom/transforms/images.py +1 -1
- swcgeom/transforms/mst.py +68 -13
- swcgeom/transforms/path.py +48 -0
- swcgeom/transforms/population.py +2 -2
- swcgeom/transforms/tree.py +18 -9
- swcgeom/transforms/tree_assembler.py +7 -4
- swcgeom/utils/__init__.py +10 -7
- swcgeom/utils/dsu.py +42 -0
- swcgeom/utils/file.py +91 -0
- swcgeom/utils/geometry_object.py +299 -0
- swcgeom/utils/neuromorpho.py +33 -11
- swcgeom/utils/renderer.py +5 -4
- swcgeom/utils/transforms.py +26 -1
- {swcgeom-0.11.1.dist-info → swcgeom-0.13.0.dist-info}/METADATA +8 -8
- swcgeom-0.13.0.dist-info/RECORD +61 -0
- {swcgeom-0.11.1.dist-info → swcgeom-0.13.0.dist-info}/WHEEL +1 -1
- swcgeom-0.11.1.dist-info/RECORD +0 -55
- /swcgeom/utils/{numpy.py → numpy_helper.py} +0 -0
- {swcgeom-0.11.1.dist-info → swcgeom-0.13.0.dist-info}/LICENSE +0 -0
- {swcgeom-0.11.1.dist-info → swcgeom-0.13.0.dist-info}/top_level.txt +0 -0
swcgeom/transforms/images.py
CHANGED
swcgeom/transforms/mst.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"""Minimum spanning tree."""
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
|
-
import numpy.typing as npt
|
|
7
7
|
import pandas as pd
|
|
8
8
|
from numpy import ma
|
|
9
|
+
from numpy import typing as npt
|
|
9
10
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from .base import Transform
|
|
11
|
+
from swcgeom.core import Tree, sort_tree
|
|
12
|
+
from swcgeom.core.swc_utils import SWCNames, SWCTypes, get_names, get_types
|
|
13
|
+
from swcgeom.transforms.base import Transform
|
|
13
14
|
|
|
14
15
|
__all__ = ["PointsToCuntzMST", "PointsToMST"]
|
|
15
16
|
|
|
@@ -30,7 +31,14 @@ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
|
|
|
30
31
|
"""
|
|
31
32
|
|
|
32
33
|
def __init__(
|
|
33
|
-
self,
|
|
34
|
+
self,
|
|
35
|
+
*,
|
|
36
|
+
bf: float = 0.4,
|
|
37
|
+
furcations: int = 2,
|
|
38
|
+
exclude_soma: bool = True,
|
|
39
|
+
sort: bool = True,
|
|
40
|
+
names: Optional[SWCNames] = None,
|
|
41
|
+
types: Optional[SWCTypes] = None,
|
|
34
42
|
) -> None:
|
|
35
43
|
"""
|
|
36
44
|
Parameters
|
|
@@ -42,10 +50,15 @@ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
|
|
|
42
50
|
no suppression.
|
|
43
51
|
exclude_soma : bool, default `True`
|
|
44
52
|
Suppress multi-furcations exclude soma.
|
|
53
|
+
names : SWCNames, optional
|
|
54
|
+
types : SWCTypes, optional
|
|
45
55
|
"""
|
|
46
56
|
self.bf = np.clip(bf, 0, 1)
|
|
47
57
|
self.furcations = furcations
|
|
48
58
|
self.exclude_soma = exclude_soma
|
|
59
|
+
self.sort = sort
|
|
60
|
+
self.names = get_names(names)
|
|
61
|
+
self.types = get_types(types)
|
|
49
62
|
|
|
50
63
|
def __call__( # pylint: disable=too-many-locals
|
|
51
64
|
self,
|
|
@@ -63,7 +76,17 @@ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
|
|
|
63
76
|
Position of soma. If none, use the first point as soma.
|
|
64
77
|
names : SWCNames, optional
|
|
65
78
|
"""
|
|
66
|
-
names
|
|
79
|
+
if names is None:
|
|
80
|
+
names = self.names
|
|
81
|
+
else:
|
|
82
|
+
warnings.warn(
|
|
83
|
+
"`PointsToCuntzMST(...)(names=...)` has been "
|
|
84
|
+
"replaced by `PointsToCuntzMST(...,names=...)` since "
|
|
85
|
+
"v0.12.0, and will be removed in next version",
|
|
86
|
+
DeprecationWarning,
|
|
87
|
+
)
|
|
88
|
+
names = get_names(names) # TODO: remove it
|
|
89
|
+
|
|
67
90
|
if soma is not None:
|
|
68
91
|
soma = np.array(soma)
|
|
69
92
|
assert soma.shape == (3,)
|
|
@@ -103,16 +126,19 @@ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
|
|
|
103
126
|
|
|
104
127
|
dic = {
|
|
105
128
|
names.id: np.arange(n),
|
|
106
|
-
names.type: np.full(n, fill_value=
|
|
129
|
+
names.type: np.full(n, fill_value=self.types.glia_processes),
|
|
107
130
|
names.x: points[:, 0],
|
|
108
131
|
names.y: points[:, 1],
|
|
109
132
|
names.z: points[:, 2],
|
|
110
133
|
names.r: 1,
|
|
111
134
|
names.pid: pid,
|
|
112
135
|
}
|
|
113
|
-
dic[names.type][0] =
|
|
136
|
+
dic[names.type][0] = self.types.soma
|
|
114
137
|
df = pd.DataFrame.from_dict(dic)
|
|
115
|
-
|
|
138
|
+
t = Tree.from_data_frame(df, names=names)
|
|
139
|
+
if self.sort:
|
|
140
|
+
t = sort_tree(t)
|
|
141
|
+
return t
|
|
116
142
|
|
|
117
143
|
def __repr__(self) -> str:
|
|
118
144
|
return (
|
|
@@ -120,21 +146,50 @@ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
|
|
|
120
146
|
f"-bf-{self.bf}"
|
|
121
147
|
f"-furcations-{self.furcations}"
|
|
122
148
|
f"-{'exclude-soma' if self.exclude_soma else 'include-soma'}"
|
|
123
|
-
)
|
|
149
|
+
) # TODO: names, types
|
|
124
150
|
|
|
125
151
|
|
|
126
152
|
class PointsToMST(PointsToCuntzMST): # pylint: disable=too-few-public-methods
|
|
127
153
|
"""Create minimum spanning tree from points."""
|
|
128
154
|
|
|
129
|
-
def __init__(
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
furcations: int = 2,
|
|
158
|
+
*,
|
|
159
|
+
k_furcations: Optional[int] = None,
|
|
160
|
+
exclude_soma: bool = True,
|
|
161
|
+
names: Optional[SWCNames] = None,
|
|
162
|
+
types: Optional[SWCTypes] = None,
|
|
163
|
+
**kwargs,
|
|
164
|
+
) -> None:
|
|
130
165
|
"""
|
|
131
166
|
Parameters
|
|
132
167
|
----------
|
|
133
|
-
|
|
168
|
+
furcations : int, default `2`
|
|
134
169
|
Suppress multifurcations which more than k. If set to -1,
|
|
135
170
|
no suppression.
|
|
171
|
+
exclude_soma : bool, default `True`
|
|
172
|
+
Suppress multi-furcations exclude soma.
|
|
173
|
+
names : SWCNames, optional
|
|
174
|
+
types : SWCTypes, optional
|
|
136
175
|
"""
|
|
137
|
-
|
|
176
|
+
if k_furcations is not None:
|
|
177
|
+
warnings.warn(
|
|
178
|
+
"`PointsToMST(k_furcations=...)` has been renamed to "
|
|
179
|
+
"`PointsToMST(furcations=...)` since v0.12.0, and will "
|
|
180
|
+
"be removed in next version",
|
|
181
|
+
DeprecationWarning,
|
|
182
|
+
)
|
|
183
|
+
furcations = k_furcations
|
|
184
|
+
|
|
185
|
+
super().__init__(
|
|
186
|
+
bf=0,
|
|
187
|
+
furcations=furcations,
|
|
188
|
+
exclude_soma=exclude_soma,
|
|
189
|
+
names=names,
|
|
190
|
+
types=types,
|
|
191
|
+
**kwargs,
|
|
192
|
+
)
|
|
138
193
|
|
|
139
194
|
def __repr__(self) -> str:
|
|
140
195
|
return (
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Transformation in path."""
|
|
2
|
+
|
|
3
|
+
from swcgeom.core import Path, Tree, redirect_tree
|
|
4
|
+
from swcgeom.transforms.base import Transform
|
|
5
|
+
|
|
6
|
+
__all__ = ["PathToTree", "PathReverser"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PathToTree(Transform[Path, Tree]):
|
|
10
|
+
"""Transform path to tree."""
|
|
11
|
+
|
|
12
|
+
def __call__(self, x: Path) -> Tree:
|
|
13
|
+
t = Tree(
|
|
14
|
+
x.number_of_nodes(),
|
|
15
|
+
type=x.type(),
|
|
16
|
+
id=x.id(),
|
|
17
|
+
x=x.x(),
|
|
18
|
+
y=x.y(),
|
|
19
|
+
z=x.z(),
|
|
20
|
+
r=x.r(),
|
|
21
|
+
pid=x.pid(),
|
|
22
|
+
source=x.source,
|
|
23
|
+
comments=x.comments.copy(),
|
|
24
|
+
names=x.names,
|
|
25
|
+
)
|
|
26
|
+
return t
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PathReverser(Transform[Path, Path]):
|
|
30
|
+
r"""Reverse path.
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
a -> b -> ... -> y -> z
|
|
34
|
+
// to
|
|
35
|
+
a <- b <- ... <- y <- z
|
|
36
|
+
```
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self) -> None:
|
|
40
|
+
super().__init__()
|
|
41
|
+
self.to_tree = PathToTree()
|
|
42
|
+
|
|
43
|
+
def __call__(self, x: Path) -> Path:
|
|
44
|
+
x[0].type, x[-1].type = x[-1].type, x[0].type
|
|
45
|
+
t = self.to_tree(x)
|
|
46
|
+
t = redirect_tree(t, x[-1].id)
|
|
47
|
+
p = t.get_paths()[0]
|
|
48
|
+
return p
|
swcgeom/transforms/population.py
CHANGED
swcgeom/transforms/tree.py
CHANGED
|
@@ -5,10 +5,11 @@ from typing import Callable, List, Optional, Tuple
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
11
|
-
from .
|
|
8
|
+
from swcgeom.core import BranchTree, DictSWC, Path, Tree, cut_tree, to_subtree
|
|
9
|
+
from swcgeom.core.swc_utils import SWCTypes, get_types
|
|
10
|
+
from swcgeom.transforms.base import Transform
|
|
11
|
+
from swcgeom.transforms.branch import BranchConvSmoother
|
|
12
|
+
from swcgeom.transforms.geometry import Normalizer
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"ToBranchTree",
|
|
@@ -34,10 +35,16 @@ class ToBranchTree(Transform[Tree, BranchTree]):
|
|
|
34
35
|
class ToLongestPath(Transform[Tree, Path[DictSWC]]):
|
|
35
36
|
"""Transform tree to longest path."""
|
|
36
37
|
|
|
38
|
+
def __init__(self, *, detach: bool = True) -> None:
|
|
39
|
+
self.detach = detach
|
|
40
|
+
|
|
37
41
|
def __call__(self, x: Tree) -> Path[DictSWC]:
|
|
38
42
|
paths = x.get_paths()
|
|
39
43
|
idx = np.argmax([p.length() for p in paths])
|
|
40
|
-
|
|
44
|
+
path = paths[idx]
|
|
45
|
+
if self.detach:
|
|
46
|
+
path = path.detach()
|
|
47
|
+
return path # type: ignore
|
|
41
48
|
|
|
42
49
|
|
|
43
50
|
class TreeSmoother(Transform[Tree, Tree]): # pylint: disable=missing-class-docstring
|
|
@@ -107,8 +114,9 @@ class CutByType(Transform[Tree, Tree]):
|
|
|
107
114
|
class CutAxonTree(CutByType):
|
|
108
115
|
"""Cut axon tree."""
|
|
109
116
|
|
|
110
|
-
def __init__(self) -> None:
|
|
111
|
-
|
|
117
|
+
def __init__(self, types: Optional[SWCTypes] = None) -> None:
|
|
118
|
+
types = get_types(types)
|
|
119
|
+
super().__init__(type=types.axon)
|
|
112
120
|
|
|
113
121
|
def __repr__(self) -> str:
|
|
114
122
|
return "CutAxonTree"
|
|
@@ -117,8 +125,9 @@ class CutAxonTree(CutByType):
|
|
|
117
125
|
class CutDendriteTree(CutByType):
|
|
118
126
|
"""Cut dendrite tree."""
|
|
119
127
|
|
|
120
|
-
def __init__(self) -> None:
|
|
121
|
-
|
|
128
|
+
def __init__(self, types: Optional[SWCTypes] = None) -> None:
|
|
129
|
+
types = get_types(types)
|
|
130
|
+
super().__init__(type=types.basal_dendrite) # TODO: apical dendrite
|
|
122
131
|
|
|
123
132
|
def __repr__(self) -> str:
|
|
124
133
|
return "CutDenriteTree"
|
|
@@ -4,10 +4,13 @@ from typing import Iterable, List, Optional, Tuple
|
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
|
|
7
|
+
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,
|
|
12
|
+
)
|
|
13
|
+
from swcgeom.transforms.base import Transform
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
class LinesToTree(Transform[List[pd.DataFrame], Tree]):
|
swcgeom/utils/__init__.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""Utils."""
|
|
2
2
|
|
|
3
|
-
from .debug import *
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
8
|
-
from .
|
|
9
|
-
from .
|
|
3
|
+
from swcgeom.utils.debug import *
|
|
4
|
+
from swcgeom.utils.dsu import *
|
|
5
|
+
from swcgeom.utils.ellipse import *
|
|
6
|
+
from swcgeom.utils.file import *
|
|
7
|
+
from swcgeom.utils.geometry_object import *
|
|
8
|
+
from swcgeom.utils.neuromorpho import *
|
|
9
|
+
from swcgeom.utils.numpy_helper import *
|
|
10
|
+
from swcgeom.utils.renderer import *
|
|
11
|
+
from swcgeom.utils.sdf import *
|
|
12
|
+
from swcgeom.utils.transforms import *
|
swcgeom/utils/dsu.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Disjoint Set Union Impl."""
|
|
2
|
+
|
|
3
|
+
__all__ = ["DisjointSetUnion"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DisjointSetUnion:
|
|
7
|
+
"""Disjoint Set Union.
|
|
8
|
+
|
|
9
|
+
DSU with path compression and union by rank.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, node_number: int):
|
|
13
|
+
self.element_parent = [i for i in range(node_number)]
|
|
14
|
+
self.rank = [0 for _ in range(node_number)]
|
|
15
|
+
|
|
16
|
+
def find_parent(self, node_id: int) -> int:
|
|
17
|
+
if node_id != self.element_parent[node_id]:
|
|
18
|
+
self.element_parent[node_id] = self.find_parent(
|
|
19
|
+
self.element_parent[node_id]
|
|
20
|
+
)
|
|
21
|
+
return self.element_parent[node_id]
|
|
22
|
+
|
|
23
|
+
def union_sets(self, node_a: int, node_b: int) -> None:
|
|
24
|
+
assert self.validate_node(node_a) and self.validate_node(node_b)
|
|
25
|
+
|
|
26
|
+
root_a = self.find_parent(node_a)
|
|
27
|
+
root_b = self.find_parent(node_b)
|
|
28
|
+
if root_a != root_b:
|
|
29
|
+
# union by rank
|
|
30
|
+
if self.rank[root_a] < self.rank[root_b]:
|
|
31
|
+
self.element_parent[root_a] = root_b
|
|
32
|
+
elif self.rank[root_a] > self.rank[root_b]:
|
|
33
|
+
self.element_parent[root_b] = root_a
|
|
34
|
+
else:
|
|
35
|
+
self.element_parent[root_b] = root_a
|
|
36
|
+
self.rank[root_a] += 1
|
|
37
|
+
|
|
38
|
+
def is_same_set(self, node_a: int, node_b: int) -> bool:
|
|
39
|
+
return self.find_parent(node_a) == self.find_parent(node_b)
|
|
40
|
+
|
|
41
|
+
def validate_node(self, node_id: int) -> bool:
|
|
42
|
+
return 0 <= node_id < len(self.element_parent)
|
swcgeom/utils/file.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""File related utils.
|
|
2
|
+
|
|
3
|
+
Notes
|
|
4
|
+
-----
|
|
5
|
+
If character coding is enabled, all denpendencies need to be installed,
|
|
6
|
+
try:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install swcgeom[all]
|
|
10
|
+
```
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import warnings
|
|
14
|
+
from io import BytesIO, TextIOBase, TextIOWrapper
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
__all__ = ["FileReader", "PathOrIO"]
|
|
18
|
+
|
|
19
|
+
PathOrIO = int | str | bytes | BytesIO | TextIOBase
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FileReader:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
fname: PathOrIO,
|
|
26
|
+
*,
|
|
27
|
+
encoding: Literal["detect"] | str = "utf-8",
|
|
28
|
+
low_confidence: float = 0.9,
|
|
29
|
+
**kwargs,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Read file.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
fname : PathOrIO
|
|
36
|
+
encoding : str | 'detect', default `utf-8`
|
|
37
|
+
The name of the encoding used to decode the file. If is
|
|
38
|
+
`detect`, we will try to detect the character encoding.
|
|
39
|
+
low_confidence : float, default to 0.9
|
|
40
|
+
Used for detect character endocing, raising warning when
|
|
41
|
+
parsing with low confidence.
|
|
42
|
+
"""
|
|
43
|
+
# TODO: support StringIO
|
|
44
|
+
self.fname, self.fb, self.f = "", None, None
|
|
45
|
+
if isinstance(fname, TextIOBase):
|
|
46
|
+
self.f = fname
|
|
47
|
+
encoding = fname.encoding # skip detect
|
|
48
|
+
elif isinstance(fname, BytesIO):
|
|
49
|
+
self.fb = fname
|
|
50
|
+
else:
|
|
51
|
+
self.fname = fname
|
|
52
|
+
|
|
53
|
+
if encoding == "detect":
|
|
54
|
+
encoding = detect_encoding(fname, low_confidence=low_confidence)
|
|
55
|
+
self.encoding = encoding
|
|
56
|
+
self.kwargs = kwargs
|
|
57
|
+
|
|
58
|
+
def __enter__(self) -> TextIOBase:
|
|
59
|
+
if isinstance(self.fb, BytesIO):
|
|
60
|
+
self.f = TextIOWrapper(self.fb, encoding=self.encoding)
|
|
61
|
+
elif self.f is None:
|
|
62
|
+
self.f = open(self.fname, "r", encoding=self.encoding, **self.kwargs)
|
|
63
|
+
|
|
64
|
+
return self.f
|
|
65
|
+
|
|
66
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
67
|
+
if self.f:
|
|
68
|
+
self.f.close()
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def detect_encoding(fname: PathOrIO, *, low_confidence: float = 0.9) -> str:
|
|
73
|
+
import chardet
|
|
74
|
+
|
|
75
|
+
if isinstance(fname, TextIOBase):
|
|
76
|
+
return fname.encoding
|
|
77
|
+
elif isinstance(fname, BytesIO):
|
|
78
|
+
data = fname.read()
|
|
79
|
+
fname.seek(0, 0)
|
|
80
|
+
else:
|
|
81
|
+
with open(fname, "rb") as f:
|
|
82
|
+
data = f.read()
|
|
83
|
+
|
|
84
|
+
result = chardet.detect(data)
|
|
85
|
+
encoding = result["encoding"] or "utf-8"
|
|
86
|
+
if result["confidence"] < low_confidence:
|
|
87
|
+
warnings.warn(
|
|
88
|
+
f"parse as `{encoding}` with low confidence "
|
|
89
|
+
f"{result['confidence']} in `{fname}`"
|
|
90
|
+
)
|
|
91
|
+
return encoding
|