flkit 0.1.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.
- flkit/__init__.py +2 -0
- flkit/alg/__init__.py +68 -0
- flkit/alg/build_idx.py +9 -0
- flkit/alg/c_track.py +88 -0
- flkit/alg/collierydb.py +1217 -0
- flkit/alg/del2.py +421 -0
- flkit/alg/delaunay_triangulation.py +476 -0
- flkit/alg/finite_plane_distance.py +239 -0
- flkit/alg/get_up_low.py +32 -0
- flkit/alg/in_tin.py +139 -0
- flkit/alg/index.py +9 -0
- flkit/alg/inter/BasePredictor.py +69 -0
- flkit/alg/inter/Kriging.py +226 -0
- flkit/alg/inter/KrigingM.py +262 -0
- flkit/alg/inter/LossFuncs.py +164 -0
- flkit/alg/inter/__init__.py +178 -0
- flkit/alg/inter/idw.py +253 -0
- flkit/alg/inter/midw.py +145 -0
- flkit/alg/inter/nearest_neighbor.py +445 -0
- flkit/alg/inter/rbf.py +345 -0
- flkit/alg/inter/trilinear.py +572 -0
- flkit/alg/point_in_polygon.py +126 -0
- flkit/alg/point_in_volume.py +188 -0
- flkit/alg/point_in_volume_rs.pyi +7 -0
- flkit/alg/point_to_triangle.py +116 -0
- flkit/alg/point_to_triangle_mesh.py +309 -0
- flkit/alg/point_to_triangle_rs.pyi +9 -0
- flkit/alg/rotated_rect.py +184 -0
- flkit/db/__init__.py +17 -0
- flkit/db/db_model.py +724 -0
- flkit/db/duck_model.py +151 -0
- flkit/db/fileobj.py +72 -0
- flkit/db/path_utl.py +174 -0
- flkit/mtcli/__init__.py +18 -0
- flkit/mtcli/core.py +489 -0
- flkit/mtcli/parse.py +218 -0
- flkit/py.typed +0 -0
- flkit/tools/__init__.py +28 -0
- flkit/tools/del_key.py +13 -0
- flkit/tools/dict_to_list.py +10 -0
- flkit/tools/get_region.py +42 -0
- flkit/tools/max_key.py +7 -0
- flkit/tools/mongo.py +119 -0
- flkit/tools/mt_dash.py +121 -0
- flkit/tools/prase_type.py +27 -0
- flkit/typr/Res.py +21 -0
- flkit/typr/__init__.py +107 -0
- flkit/typr/c_grid.py +425 -0
- flkit/typr/console.py +283 -0
- flkit/typr/engine.py +59 -0
- flkit/typr/getenv.py +89 -0
- flkit/typr/result.py +81 -0
- flkit/typr/table.py +34 -0
- flkit/typr/utils.py +542 -0
- flkit/typr/verify.py +64 -0
- flkit/typr/webview.py +219 -0
- flkit/typr/wstask.py +344 -0
- flkit-0.1.0.dist-info/METADATA +186 -0
- flkit-0.1.0.dist-info/RECORD +60 -0
- flkit-0.1.0.dist-info/WHEEL +4 -0
flkit/alg/collierydb.py
ADDED
|
@@ -0,0 +1,1217 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import shutil
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pyvista as pv
|
|
7
|
+
import flkit.alg as alg
|
|
8
|
+
import flkit.typr as typr
|
|
9
|
+
from flkit.alg.in_tin import is_point_in_triangles
|
|
10
|
+
from flkit.alg.point_in_polygon import PointInPolygon
|
|
11
|
+
from flkit.alg.rotated_rect import RotatedRect
|
|
12
|
+
from flkit.model.engine import engine
|
|
13
|
+
from flkit.utils import FileDB, getvar
|
|
14
|
+
|
|
15
|
+
import datetime
|
|
16
|
+
import tkinter.messagebox as messagebox
|
|
17
|
+
|
|
18
|
+
from flkit.utils.c_grid import Grid3D
|
|
19
|
+
from flkit.utils.utils import send_f
|
|
20
|
+
|
|
21
|
+
COAL_FACE_FMT = "=id"
|
|
22
|
+
COAL_VOXEL_FMT = "=i"
|
|
23
|
+
STRATUM_FACE_FMT = "=d"
|
|
24
|
+
STRATUM_VOXEL_FMT = "=i"
|
|
25
|
+
BOUDN_FMT = "=i"
|
|
26
|
+
FAULTAGE_FMT = "=iii"
|
|
27
|
+
MASHGAS_FMT = "=d"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# region 计算点是否在断层内
|
|
31
|
+
def comp_in_fault(range, distance, in_fault):
|
|
32
|
+
if not in_fault:
|
|
33
|
+
return 0
|
|
34
|
+
if distance > range * 10:
|
|
35
|
+
return 0
|
|
36
|
+
return 1
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# endregion 计算点是否在断层内
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# region 颜色映射
|
|
43
|
+
def get_colors(name: str):
|
|
44
|
+
"""获取颜色列表"""
|
|
45
|
+
color_map = getvar(
|
|
46
|
+
"colliery",
|
|
47
|
+
"color_map",
|
|
48
|
+
{
|
|
49
|
+
"地表": "red",
|
|
50
|
+
"松散": "blue",
|
|
51
|
+
"砂岩层": "green",
|
|
52
|
+
"松散层2": "green",
|
|
53
|
+
"二1煤": "orange",
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
return color_map.get(name, "red")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# endregion 颜色映射
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# region 当前时间
|
|
63
|
+
def now() -> datetime.datetime:
|
|
64
|
+
return datetime.datetime.now().isoformat()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# endregion 当前时间
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# region 煤矿数据库元数据
|
|
71
|
+
class CollieryMeta:
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
name: str, # 名称
|
|
75
|
+
create_at: None, # 创建时间
|
|
76
|
+
creator: str, # 创建人
|
|
77
|
+
bound: False, # 边界
|
|
78
|
+
blocks: False, # 块
|
|
79
|
+
voxel: False, # 体素参数
|
|
80
|
+
mashgas: False, # 瓦斯
|
|
81
|
+
tunnels: False, # 巷道
|
|
82
|
+
faultage: False, # 断层
|
|
83
|
+
stratum: False, # 地层
|
|
84
|
+
volume: False, # 地质体
|
|
85
|
+
coal: False, # 底抽巷煤层
|
|
86
|
+
):
|
|
87
|
+
self.name = name
|
|
88
|
+
self.create_at = create_at if create_at else now()
|
|
89
|
+
self.creator = creator
|
|
90
|
+
self.bound = bound
|
|
91
|
+
self.blocks = blocks
|
|
92
|
+
if not voxel:
|
|
93
|
+
voxel = {"mode": "", "params": {}, "gen": False}
|
|
94
|
+
self.voxel = voxel
|
|
95
|
+
self.mashgas = mashgas
|
|
96
|
+
if not tunnels:
|
|
97
|
+
tunnels = {"names": [], "gen": False}
|
|
98
|
+
self.tunnels = tunnels
|
|
99
|
+
if not faultage:
|
|
100
|
+
faultage = {"names": [], "gen": False}
|
|
101
|
+
self.faultage = faultage
|
|
102
|
+
if not stratum:
|
|
103
|
+
stratum = {"names": [], "gen": False}
|
|
104
|
+
self.stratum = stratum
|
|
105
|
+
if not volume:
|
|
106
|
+
volume = {"names": [], "gen": False}
|
|
107
|
+
self.volume = volume
|
|
108
|
+
if not coal:
|
|
109
|
+
coal = {"names": [], "gen": False}
|
|
110
|
+
self.coal = coal
|
|
111
|
+
|
|
112
|
+
def to_dict(self):
|
|
113
|
+
return {
|
|
114
|
+
"name": self.name,
|
|
115
|
+
"create_at": self.create_at,
|
|
116
|
+
"creator": self.creator,
|
|
117
|
+
"bound": self.bound,
|
|
118
|
+
"blocks": self.blocks,
|
|
119
|
+
"voxel": self.voxel,
|
|
120
|
+
"mashgas": self.mashgas,
|
|
121
|
+
"tunnels": self.tunnels,
|
|
122
|
+
"faultage": self.faultage,
|
|
123
|
+
"stratum": self.stratum,
|
|
124
|
+
"volume": self.volume,
|
|
125
|
+
"coal": self.coal,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def save(self, path: Path):
|
|
129
|
+
path.write_text(
|
|
130
|
+
json.dumps(self.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def init(base: Path) -> "CollieryMeta":
|
|
135
|
+
config_path = base / "meta.json"
|
|
136
|
+
# 如果不存在侧初始化meta文件
|
|
137
|
+
if not config_path.exists():
|
|
138
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
139
|
+
meta = CollieryMeta(
|
|
140
|
+
base.stem,
|
|
141
|
+
None,
|
|
142
|
+
None,
|
|
143
|
+
False,
|
|
144
|
+
False,
|
|
145
|
+
False,
|
|
146
|
+
False,
|
|
147
|
+
False,
|
|
148
|
+
False,
|
|
149
|
+
False,
|
|
150
|
+
False,
|
|
151
|
+
False,
|
|
152
|
+
)
|
|
153
|
+
meta.save(config_path)
|
|
154
|
+
return meta
|
|
155
|
+
# 从文件中加载meta
|
|
156
|
+
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
157
|
+
return CollieryMeta(**data)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# endregion 煤矿数据库元数据
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# region 煤矿数据库
|
|
164
|
+
class CollieryDB:
|
|
165
|
+
def __init__(self, name: str, auto_init: bool = True):
|
|
166
|
+
self.name = name
|
|
167
|
+
self.base = Path(typr.getenv("JSON_DB_DIR", ".json_db")) / self.name
|
|
168
|
+
if not self.base.exists():
|
|
169
|
+
if auto_init:
|
|
170
|
+
self.base.mkdir(parents=True, exist_ok=True)
|
|
171
|
+
else:
|
|
172
|
+
raise ValueError(f"煤矿 {self.name} 不存在")
|
|
173
|
+
self.meta = CollieryMeta.init(self.base)
|
|
174
|
+
|
|
175
|
+
# region 边界
|
|
176
|
+
def set_bound(self, path: Path):
|
|
177
|
+
"""
|
|
178
|
+
设置煤矿边界
|
|
179
|
+
|
|
180
|
+
bound文件结构:
|
|
181
|
+
[
|
|
182
|
+
{
|
|
183
|
+
"x": 12,
|
|
184
|
+
"y": 34,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"x": 45,
|
|
188
|
+
"y": 6,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"x": 12,
|
|
192
|
+
"y": 34,
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
"""
|
|
196
|
+
data = pd.read_excel(path, sheet_name="info")
|
|
197
|
+
if "x" not in data.columns or "y" not in data.columns:
|
|
198
|
+
raise ValueError(f"Excel 文件 {path} 中必须包含 x, y 列")
|
|
199
|
+
data = data[["x", "y"]].values
|
|
200
|
+
if not np.array_equal(data[0], data[-1]):
|
|
201
|
+
raise ValueError(f"Excel 文件 {path} 中首尾坐标不一致,不是封闭区域")
|
|
202
|
+
np.save(self.base / "bound.npy", np.array(data))
|
|
203
|
+
self.meta.bound = False
|
|
204
|
+
self.meta.save(self.base / "meta.json")
|
|
205
|
+
|
|
206
|
+
def comp_bound(self):
|
|
207
|
+
"""
|
|
208
|
+
计算煤矿边界
|
|
209
|
+
"""
|
|
210
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
211
|
+
data = np.load(self.base / "bound.npy")
|
|
212
|
+
ppn = PointInPolygon(data)
|
|
213
|
+
file_db = FileDB(self.base / "voxel" / "bound" / "bound.cube", BOUDN_FMT)("wb")
|
|
214
|
+
up_in = False
|
|
215
|
+
up_index = [None, None]
|
|
216
|
+
total = grid3d.sum()
|
|
217
|
+
for idx, index, point in grid3d.enumerate():
|
|
218
|
+
if idx % 1000000 == 0:
|
|
219
|
+
print(f"已处理 {idx} 个点,共 {total} 个点 进度 {idx / total:.2%}")
|
|
220
|
+
if index[0] != up_index[0] or index[1] != up_index[1]:
|
|
221
|
+
up_index = index
|
|
222
|
+
up_in = ppn.is_inside(point)
|
|
223
|
+
file_db.append([int(up_in)])
|
|
224
|
+
self.meta.bound = True
|
|
225
|
+
self.meta.save(self.base / "meta.json")
|
|
226
|
+
|
|
227
|
+
# endregion 边界
|
|
228
|
+
|
|
229
|
+
# region 块段
|
|
230
|
+
def set_blocks(self, path: Path):
|
|
231
|
+
raise ValueError("尚未实现设置块功能")
|
|
232
|
+
|
|
233
|
+
# endregion 块
|
|
234
|
+
|
|
235
|
+
# region 体素
|
|
236
|
+
def set_voxel(
|
|
237
|
+
self,
|
|
238
|
+
mode: str,
|
|
239
|
+
z_min: float,
|
|
240
|
+
z_max: float,
|
|
241
|
+
x_min: float = None,
|
|
242
|
+
x_max: float = None,
|
|
243
|
+
y_min: float = None,
|
|
244
|
+
y_max: float = None,
|
|
245
|
+
x_step: float = None,
|
|
246
|
+
y_step: float = None,
|
|
247
|
+
z_step: float = None,
|
|
248
|
+
from_bound: bool = False,
|
|
249
|
+
):
|
|
250
|
+
voxel_path = self.base / "voxel"
|
|
251
|
+
# 统一检查边界文件是否存在,避免后续重复判断
|
|
252
|
+
bound_exists = (self.base / "bound.npy").exists()
|
|
253
|
+
if from_bound:
|
|
254
|
+
if not bound_exists:
|
|
255
|
+
raise ValueError("未设置煤矿边界,无法依照边界生成范围")
|
|
256
|
+
bound = np.load(self.base / "bound.npy")
|
|
257
|
+
x_min, x_max = np.min(bound[:, 0]), np.max(bound[:, 0])
|
|
258
|
+
y_min, y_max = np.min(bound[:, 1]), np.max(bound[:, 1])
|
|
259
|
+
|
|
260
|
+
if mode == "signed":
|
|
261
|
+
# 提前检查边界是否存在,避免后续重复判断
|
|
262
|
+
if not bound_exists:
|
|
263
|
+
raise ValueError("未设置煤矿边界,无法生成体素")
|
|
264
|
+
if None in [
|
|
265
|
+
x_min,
|
|
266
|
+
x_max,
|
|
267
|
+
y_min,
|
|
268
|
+
y_max,
|
|
269
|
+
z_min,
|
|
270
|
+
z_max,
|
|
271
|
+
x_step,
|
|
272
|
+
y_step,
|
|
273
|
+
z_step,
|
|
274
|
+
]:
|
|
275
|
+
raise ValueError("signed 模式下必须提供所有参数")
|
|
276
|
+
if x_min >= x_max or y_min >= y_max or z_min >= z_max:
|
|
277
|
+
raise ValueError("体素参数中,最小坐标必须小于最大坐标")
|
|
278
|
+
if x_step <= 0 or y_step <= 0 or z_step <= 0:
|
|
279
|
+
raise ValueError("体素参数中,步长必须大于0")
|
|
280
|
+
# 检查目录是否存在前,先确保不会误删
|
|
281
|
+
if voxel_path.exists():
|
|
282
|
+
raise ValueError(
|
|
283
|
+
f"voxel 目录 {voxel_path} 已存在,为防止数据丢失,已拒绝操作"
|
|
284
|
+
)
|
|
285
|
+
voxel_path.mkdir(parents=True, exist_ok=True)
|
|
286
|
+
self.meta.voxel["mode"] = mode
|
|
287
|
+
self.meta.voxel["params"] = {
|
|
288
|
+
"x_min": x_min,
|
|
289
|
+
"x_max": x_max,
|
|
290
|
+
"y_min": y_min,
|
|
291
|
+
"y_max": y_max,
|
|
292
|
+
"z_min": z_min,
|
|
293
|
+
"z_max": z_max,
|
|
294
|
+
"x_step": x_step,
|
|
295
|
+
"y_step": y_step,
|
|
296
|
+
"z_step": z_step,
|
|
297
|
+
}
|
|
298
|
+
self.meta.voxel["gen"] = False
|
|
299
|
+
self.meta.save(self.base / "meta.json")
|
|
300
|
+
elif mode == "auto":
|
|
301
|
+
if None in [x_min, x_max, y_min, y_max, z_min, z_max]:
|
|
302
|
+
raise ValueError("auto 模式下必须提供所有参数")
|
|
303
|
+
if voxel_path.exists():
|
|
304
|
+
raise ValueError(
|
|
305
|
+
f"voxel 目录 {voxel_path} 已存在,为防止数据丢失,已拒绝操作"
|
|
306
|
+
)
|
|
307
|
+
voxel_path.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
# 此处可预留自动体素生成逻辑
|
|
309
|
+
raise ValueError("auto 模式下未实现自动体素生成功能")
|
|
310
|
+
else:
|
|
311
|
+
raise ValueError(f"不支持的体素生成模式: {mode}")
|
|
312
|
+
|
|
313
|
+
def show_one_voxel(
|
|
314
|
+
self,
|
|
315
|
+
typ: str = "bound",
|
|
316
|
+
opacity: float = 1,
|
|
317
|
+
edge_color: str = "black",
|
|
318
|
+
cmap: str = "viridis",
|
|
319
|
+
line_width: float = 0.5,
|
|
320
|
+
show_edges: bool = False,
|
|
321
|
+
show_scalar_bar: bool = False,
|
|
322
|
+
scalar_bar_args: dict = None,
|
|
323
|
+
bound: bool = True,
|
|
324
|
+
):
|
|
325
|
+
dtype = np.int32
|
|
326
|
+
if typ == "stratum":
|
|
327
|
+
|
|
328
|
+
def l_map(values: list):
|
|
329
|
+
if values[0] == 0:
|
|
330
|
+
return False, 0
|
|
331
|
+
# if values[0] != 3:
|
|
332
|
+
# return False, 0
|
|
333
|
+
return True, values[0] # 使用整数标签而不是字符串
|
|
334
|
+
|
|
335
|
+
file_path = self.base / "voxel" / "stratum" / "stratum.cube"
|
|
336
|
+
fmt = STRATUM_VOXEL_FMT
|
|
337
|
+
elif typ == "mashgas":
|
|
338
|
+
|
|
339
|
+
def l_map(values: list):
|
|
340
|
+
# 检查是否为NaN值,如果是则跳过
|
|
341
|
+
if np.isnan(values[0]):
|
|
342
|
+
return False, 0
|
|
343
|
+
return True, values[0]
|
|
344
|
+
|
|
345
|
+
file_path = self.base / "voxel" / "mashgas" / "mashgas.cube"
|
|
346
|
+
fmt = MASHGAS_FMT
|
|
347
|
+
cmap = "cool"
|
|
348
|
+
elif typ == "coal":
|
|
349
|
+
|
|
350
|
+
def l_map(values: list):
|
|
351
|
+
if values[0] == 0:
|
|
352
|
+
return False, 0
|
|
353
|
+
return True, values[0]
|
|
354
|
+
|
|
355
|
+
file_path = self.base / "voxel" / "coal" / "coal.cube"
|
|
356
|
+
fmt = COAL_VOXEL_FMT
|
|
357
|
+
elif typ == "faultage":
|
|
358
|
+
|
|
359
|
+
def l_map(values: list):
|
|
360
|
+
if values[0] == 0:
|
|
361
|
+
return False, 0
|
|
362
|
+
return True, values[-1]
|
|
363
|
+
|
|
364
|
+
file_path = self.base / "voxel" / "faultage" / "faultage.cube"
|
|
365
|
+
fmt = FAULTAGE_FMT
|
|
366
|
+
else:
|
|
367
|
+
raise ValueError(f"不支持的体素类型: {typ}")
|
|
368
|
+
|
|
369
|
+
grid = Grid3D.from_step(**self.meta.voxel["params"])
|
|
370
|
+
bound_db = FileDB(self.base / "voxel" / "bound" / "bound.cube", "=i")("rb")
|
|
371
|
+
file_db = FileDB(file_path, fmt)("rb")
|
|
372
|
+
nx = int((grid.info.x_max - grid.info.x_min) // grid.info.x_step)
|
|
373
|
+
ny = int((grid.info.y_max - grid.info.y_min) // grid.info.y_step)
|
|
374
|
+
nz = int((grid.info.z_max - grid.info.z_min) // grid.info.z_step)
|
|
375
|
+
cell_nx, cell_ny, cell_nz = nx - 1, ny - 1, nz - 1
|
|
376
|
+
cell_label_ids = np.zeros((cell_nx, cell_ny, cell_nz), dtype=dtype)
|
|
377
|
+
vtk = engine()
|
|
378
|
+
sum_voxel = 0
|
|
379
|
+
for idx, index, point in grid.enumerate():
|
|
380
|
+
if idx % 1000000 == 0:
|
|
381
|
+
send_f(
|
|
382
|
+
f"结构进度: {idx}/{grid.sum()} ({idx / (grid.sum()) * 100:.2f}%)"
|
|
383
|
+
)
|
|
384
|
+
if bound:
|
|
385
|
+
in_bound = bound_db.search(idx)[0] == 1
|
|
386
|
+
if not in_bound:
|
|
387
|
+
continue
|
|
388
|
+
values = file_db.search(idx)
|
|
389
|
+
visible, label = l_map(values)
|
|
390
|
+
if not visible:
|
|
391
|
+
continue
|
|
392
|
+
x_i, y_i, z_i = index
|
|
393
|
+
if 0 <= x_i < cell_nx and 0 <= y_i < cell_ny and 0 <= z_i < cell_nz:
|
|
394
|
+
cell_label_ids[x_i, y_i, z_i] = label
|
|
395
|
+
sum_voxel += 1
|
|
396
|
+
|
|
397
|
+
if sum_voxel == 0:
|
|
398
|
+
send_f("没有可见体素")
|
|
399
|
+
return
|
|
400
|
+
else:
|
|
401
|
+
send_f(f"共发现 {sum_voxel} 个可见体素")
|
|
402
|
+
|
|
403
|
+
volume = pv.ImageData(
|
|
404
|
+
dimensions=(nx, ny, nz),
|
|
405
|
+
spacing=(grid.info.x_step, grid.info.y_step, grid.info.z_step),
|
|
406
|
+
origin=(grid.info.x_min, grid.info.y_min, grid.info.z_min),
|
|
407
|
+
)
|
|
408
|
+
volume.cell_data["label_id"] = cell_label_ids.flatten(order="F")
|
|
409
|
+
filtered_volume = volume.threshold(0.5, scalars="label_id")
|
|
410
|
+
vtk.add_mesh(
|
|
411
|
+
filtered_volume,
|
|
412
|
+
cmap=cmap,
|
|
413
|
+
show_edges=show_edges,
|
|
414
|
+
edge_color=edge_color,
|
|
415
|
+
line_width=line_width,
|
|
416
|
+
opacity=opacity,
|
|
417
|
+
interpolate_before_map=False,
|
|
418
|
+
show_scalar_bar=show_scalar_bar,
|
|
419
|
+
scalar_bar_args=scalar_bar_args
|
|
420
|
+
or {
|
|
421
|
+
"title": "地质结构",
|
|
422
|
+
"position_x": 0.02,
|
|
423
|
+
"position_y": 0.1,
|
|
424
|
+
},
|
|
425
|
+
)
|
|
426
|
+
send_f("体素可视化完成")
|
|
427
|
+
|
|
428
|
+
def show_voxel(
|
|
429
|
+
self,
|
|
430
|
+
opacity: float = 1,
|
|
431
|
+
edge_color: str = "black",
|
|
432
|
+
cmap: str = "viridis",
|
|
433
|
+
att_cmap: str = "cool",
|
|
434
|
+
line_width: float = 0.5,
|
|
435
|
+
show_edges: bool = False,
|
|
436
|
+
show_scalar_bar: bool = False,
|
|
437
|
+
scalar_bar_args: dict = None,
|
|
438
|
+
bound: bool = True,
|
|
439
|
+
):
|
|
440
|
+
bound_db = FileDB(self.base / "voxel" / "bound" / "bound.cube", BOUDN_FMT)("rb")
|
|
441
|
+
stratum_db = FileDB(
|
|
442
|
+
self.base / "voxel" / "stratum" / "stratum.cube", STRATUM_VOXEL_FMT
|
|
443
|
+
)("rb")
|
|
444
|
+
coal_db = FileDB(self.base / "voxel" / "coal" / "coal.cube", COAL_VOXEL_FMT)(
|
|
445
|
+
"rb"
|
|
446
|
+
)
|
|
447
|
+
mashgas_db = FileDB(
|
|
448
|
+
self.base / "voxel" / "mashgas" / "mashgas.cube", MASHGAS_FMT
|
|
449
|
+
)("rb")
|
|
450
|
+
faultage_db = FileDB(
|
|
451
|
+
self.base / "voxel" / "faultage" / "faultage.cube", FAULTAGE_FMT
|
|
452
|
+
)("rb")
|
|
453
|
+
grid = Grid3D.from_step(**self.meta.voxel["params"])
|
|
454
|
+
nx = int((grid.info.x_max - grid.info.x_min) // grid.info.x_step)
|
|
455
|
+
ny = int((grid.info.y_max - grid.info.y_min) // grid.info.y_step)
|
|
456
|
+
nz = int((grid.info.z_max - grid.info.z_min) // grid.info.z_step)
|
|
457
|
+
cell_nx, cell_ny, cell_nz = nx - 1, ny - 1, nz - 1
|
|
458
|
+
cell_label_ids = np.zeros((cell_nx, cell_ny, cell_nz), dtype=np.float64)
|
|
459
|
+
cell_label_ids2 = np.zeros((cell_nx, cell_ny, cell_nz), dtype=np.float64)
|
|
460
|
+
cell_idx = np.zeros((cell_nx, cell_ny, cell_nz), dtype=np.int32)
|
|
461
|
+
|
|
462
|
+
all_labels = []
|
|
463
|
+
voxel_labels = (
|
|
464
|
+
self.meta.stratum["names"]
|
|
465
|
+
+ self.meta.coal["names"]
|
|
466
|
+
+ self.meta.faultage["names"]
|
|
467
|
+
)
|
|
468
|
+
print(voxel_labels)
|
|
469
|
+
labels = {
|
|
470
|
+
"x": [],
|
|
471
|
+
"y": [],
|
|
472
|
+
"z": [],
|
|
473
|
+
"stratum": [],
|
|
474
|
+
"coal": [],
|
|
475
|
+
"mashgas": [],
|
|
476
|
+
"faultage": [],
|
|
477
|
+
"voxel": [],
|
|
478
|
+
}
|
|
479
|
+
vtk = engine()
|
|
480
|
+
label_idx = 0
|
|
481
|
+
for idx, index, point in grid.enumerate():
|
|
482
|
+
if bound:
|
|
483
|
+
in_bound = bound_db.search(idx)[0] == 1
|
|
484
|
+
if not in_bound:
|
|
485
|
+
continue
|
|
486
|
+
stratum_val = stratum_db.search(idx)
|
|
487
|
+
if stratum_val[0] == 0:
|
|
488
|
+
continue
|
|
489
|
+
coal_val = coal_db.search(idx)
|
|
490
|
+
mashgas_val = mashgas_db.search(idx)
|
|
491
|
+
faultage_val = faultage_db.search(idx)
|
|
492
|
+
labels["x"].append(point[0])
|
|
493
|
+
labels["y"].append(point[1])
|
|
494
|
+
labels["z"].append(point[2])
|
|
495
|
+
stratum_idx = (
|
|
496
|
+
voxel_labels.index(self.meta.stratum["names"][stratum_val[0] - 1]) + 1
|
|
497
|
+
)
|
|
498
|
+
faultage_idx = (
|
|
499
|
+
voxel_labels.index(self.meta.faultage["names"][faultage_val[-1] - 1])
|
|
500
|
+
+ 1
|
|
501
|
+
)
|
|
502
|
+
voxel_l = stratum_idx
|
|
503
|
+
all_labels.append(
|
|
504
|
+
{
|
|
505
|
+
"地层名称": voxel_labels[stratum_idx],
|
|
506
|
+
"工作面采区": (
|
|
507
|
+
self.meta.coal["names"][coal_val[0] - 1]
|
|
508
|
+
if coal_val[0] != 0
|
|
509
|
+
else "无"
|
|
510
|
+
).replace("gzm", "4103工作面"),
|
|
511
|
+
"断层编号": (
|
|
512
|
+
self.meta.faultage["names"][faultage_val[-1] - 1]
|
|
513
|
+
if faultage_val[-1] != 0
|
|
514
|
+
else "无"
|
|
515
|
+
).replace("id_7", "23-1"),
|
|
516
|
+
"瓦斯含量": mashgas_val[0],
|
|
517
|
+
}
|
|
518
|
+
)
|
|
519
|
+
label_idx += 1
|
|
520
|
+
voxel_l2 = 0
|
|
521
|
+
if coal_val[0] != 0:
|
|
522
|
+
voxel_l = (
|
|
523
|
+
voxel_labels.index(self.meta.coal["names"][coal_val[0] - 1]) + 1
|
|
524
|
+
)
|
|
525
|
+
voxel_l2 = 0
|
|
526
|
+
if "砂岩层" in voxel_labels[stratum_idx - 1]:
|
|
527
|
+
voxel_l = 0
|
|
528
|
+
voxel_l2 = mashgas_val[0]
|
|
529
|
+
if faultage_val[0] == 1:
|
|
530
|
+
voxel_l = faultage_idx
|
|
531
|
+
voxel_l2 = 0
|
|
532
|
+
x_i, y_i, z_i = index
|
|
533
|
+
if 0 <= x_i < cell_nx and 0 <= y_i < cell_ny and 0 <= z_i < cell_nz:
|
|
534
|
+
cell_label_ids[x_i, y_i, z_i] = voxel_l
|
|
535
|
+
cell_label_ids2[x_i, y_i, z_i] = voxel_l2
|
|
536
|
+
cell_idx[x_i, y_i, z_i] = label_idx
|
|
537
|
+
if idx % 1000000 == 0:
|
|
538
|
+
send_f(
|
|
539
|
+
f"结构进度: {idx}/{grid.sum()} ({idx / (grid.sum()) * 100:.2f}%)"
|
|
540
|
+
)
|
|
541
|
+
volume = pv.ImageData(
|
|
542
|
+
dimensions=(nx, ny, nz),
|
|
543
|
+
spacing=(grid.info.x_step, grid.info.y_step, grid.info.z_step),
|
|
544
|
+
origin=(grid.info.x_min, grid.info.y_min, grid.info.z_min),
|
|
545
|
+
)
|
|
546
|
+
volume.cell_data["label_id"] = cell_label_ids.flatten(order="F")
|
|
547
|
+
volume.cell_data["label_idx"] = cell_idx.flatten(order="F")
|
|
548
|
+
filtered_volume = volume.threshold(0.5, scalars="label_id")
|
|
549
|
+
volume2 = pv.ImageData(
|
|
550
|
+
dimensions=(nx, ny, nz),
|
|
551
|
+
spacing=(grid.info.x_step, grid.info.y_step, grid.info.z_step),
|
|
552
|
+
origin=(grid.info.x_min, grid.info.y_min, grid.info.z_min),
|
|
553
|
+
)
|
|
554
|
+
volume2.cell_data["label_id"] = cell_label_ids2.flatten(order="F")
|
|
555
|
+
volume2.cell_data["label_idx"] = cell_idx.flatten(order="F")
|
|
556
|
+
filtered_volume2 = volume2.threshold(0.5, scalars="label_id")
|
|
557
|
+
print(f"共有体素:{filtered_volume2.n_cells}")
|
|
558
|
+
|
|
559
|
+
def pick_callback(point):
|
|
560
|
+
"""鼠标点击回调:显示被点击位置最近体素的信息"""
|
|
561
|
+
print("=" * 50)
|
|
562
|
+
print("🔍 体素点击事件触发")
|
|
563
|
+
print(f"📍 点击坐标: ({point[0]:.2f}, {point[1]:.2f}, {point[2]:.2f})")
|
|
564
|
+
|
|
565
|
+
try:
|
|
566
|
+
# 在第一个体素中查找最近的单元
|
|
567
|
+
cell_id = filtered_volume.find_closest_cell(point)
|
|
568
|
+
if cell_id is None or cell_id < 0 or cell_id >= filtered_volume.n_cells:
|
|
569
|
+
print("❌ 未找到有效体素")
|
|
570
|
+
print("=" * 50)
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
label_id = int(filtered_volume.cell_data["label_id"][cell_id])
|
|
574
|
+
label_idx = int(filtered_volume.cell_data["label_idx"][cell_id])
|
|
575
|
+
label_info = all_labels[label_idx]
|
|
576
|
+
print(f"✅ 最近体素ID: {cell_id},label_id = {label_id}")
|
|
577
|
+
print(f"📋 标签信息: {label_info}")
|
|
578
|
+
|
|
579
|
+
# 尝试使用 messagebox,如果失败则只用 print
|
|
580
|
+
try:
|
|
581
|
+
import tkinter as tk
|
|
582
|
+
|
|
583
|
+
root = tk.Tk()
|
|
584
|
+
root.withdraw() # 隐藏主窗口
|
|
585
|
+
# 将标签信息格式化为更易读的字符串
|
|
586
|
+
formatted_labels = []
|
|
587
|
+
for key, val in label_info.items():
|
|
588
|
+
if isinstance(val, (int, float)) and not isinstance(val, bool):
|
|
589
|
+
formatted_labels.append(f"{key}: {val:.2f}")
|
|
590
|
+
else:
|
|
591
|
+
formatted_labels.append(f"{key}: {val}")
|
|
592
|
+
label_str = "\n".join(formatted_labels)
|
|
593
|
+
|
|
594
|
+
messagebox.showinfo(
|
|
595
|
+
"体素信息",
|
|
596
|
+
f"体素ID: {cell_id}\nlabel_id: {voxel_labels[label_id - 1]}\n标签信息:\n{label_str}\n坐标: ({point[0]:.2f}, {point[1]:.2f}, {point[2]:.2f})",
|
|
597
|
+
)
|
|
598
|
+
root.destroy()
|
|
599
|
+
except Exception as e:
|
|
600
|
+
print(f"⚠️ 消息框显示失败: {e}")
|
|
601
|
+
print(
|
|
602
|
+
f"📋 体素信息: ID={cell_id}, label_id={label_id}, 标签={label_info}"
|
|
603
|
+
)
|
|
604
|
+
except Exception as e:
|
|
605
|
+
print(f"❌ 获取体素信息失败: {e}")
|
|
606
|
+
print("=" * 50)
|
|
607
|
+
|
|
608
|
+
vtk.add_mesh(
|
|
609
|
+
filtered_volume,
|
|
610
|
+
cmap=cmap,
|
|
611
|
+
show_edges=show_edges,
|
|
612
|
+
edge_color=edge_color,
|
|
613
|
+
line_width=line_width,
|
|
614
|
+
opacity=opacity,
|
|
615
|
+
interpolate_before_map=False,
|
|
616
|
+
show_scalar_bar=show_scalar_bar,
|
|
617
|
+
scalar_bar_args=scalar_bar_args
|
|
618
|
+
or {
|
|
619
|
+
"title": "地质结构",
|
|
620
|
+
"position_x": 0.06,
|
|
621
|
+
"position_y": 0.2,
|
|
622
|
+
},
|
|
623
|
+
pickable=True,
|
|
624
|
+
)
|
|
625
|
+
vtk.add_mesh(
|
|
626
|
+
filtered_volume2,
|
|
627
|
+
cmap=att_cmap,
|
|
628
|
+
show_edges=show_edges,
|
|
629
|
+
edge_color=edge_color,
|
|
630
|
+
line_width=line_width,
|
|
631
|
+
opacity=opacity,
|
|
632
|
+
interpolate_before_map=False,
|
|
633
|
+
show_scalar_bar=show_scalar_bar,
|
|
634
|
+
scalar_bar_args=scalar_bar_args
|
|
635
|
+
or {
|
|
636
|
+
"title": "瓦斯结构",
|
|
637
|
+
"position_x": 0.06,
|
|
638
|
+
"position_y": 0.1,
|
|
639
|
+
},
|
|
640
|
+
pickable=True,
|
|
641
|
+
)
|
|
642
|
+
# 启用点击拾取功能
|
|
643
|
+
vtk.enable_point_picking(callback=pick_callback, show_message=True)
|
|
644
|
+
send_f("体素可视化完成")
|
|
645
|
+
|
|
646
|
+
# endregion 体素
|
|
647
|
+
|
|
648
|
+
# region 瓦斯
|
|
649
|
+
def set_mashgas(self, path: Path):
|
|
650
|
+
data = pd.read_excel(path, sheet_name="info")
|
|
651
|
+
if (
|
|
652
|
+
"x" not in data.columns
|
|
653
|
+
or "y" not in data.columns
|
|
654
|
+
or "z" not in data.columns
|
|
655
|
+
):
|
|
656
|
+
raise ValueError(f"Excel 文件 {path} 中必须包含 x, y, z 列")
|
|
657
|
+
# 以numpy数组的形式保存,保存为npy文件
|
|
658
|
+
data = data[["x", "y", "z", "value"]].values
|
|
659
|
+
np.save(self.base / "mashgas.npy", data)
|
|
660
|
+
self.meta.mashgas = False
|
|
661
|
+
self.meta.save(self.base / "meta.json")
|
|
662
|
+
|
|
663
|
+
def comp_mashgas(self, func: str = "idw"):
|
|
664
|
+
max_value = 0
|
|
665
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
666
|
+
data = np.load(self.base / "mashgas.npy") # (N,4) [x,y,z,value]
|
|
667
|
+
interp = alg.inter.make_interpolator(func, dim=3).fit(data)
|
|
668
|
+
db = FileDB(self.base / "voxel" / "mashgas" / "mashgas.cube", MASHGAS_FMT)(
|
|
669
|
+
"w+b"
|
|
670
|
+
)
|
|
671
|
+
total = grid3d.sum()
|
|
672
|
+
for idx, index, point in grid3d.enumerate():
|
|
673
|
+
if idx % 1000000 == 0:
|
|
674
|
+
print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
|
|
675
|
+
val = interp.predict(np.array(point))
|
|
676
|
+
max_value = max(max_value, abs(val))
|
|
677
|
+
db.append([val])
|
|
678
|
+
self.meta.mashgas = True
|
|
679
|
+
self.meta.save(self.base / "meta.json")
|
|
680
|
+
|
|
681
|
+
# endregion mashgas
|
|
682
|
+
|
|
683
|
+
# region 巷道
|
|
684
|
+
def set_tunnels(self, path: Path):
|
|
685
|
+
raise ValueError("尚未实现设置巷道功能")
|
|
686
|
+
|
|
687
|
+
# endregion 巷道
|
|
688
|
+
|
|
689
|
+
# region 断层
|
|
690
|
+
def set_faultage(self, path: Path):
|
|
691
|
+
path_obj = self.base / "faultage.npy"
|
|
692
|
+
|
|
693
|
+
data = pd.read_excel(path, sheet_name="info")
|
|
694
|
+
if (
|
|
695
|
+
"x1" not in data.columns
|
|
696
|
+
or "y1" not in data.columns
|
|
697
|
+
or "z1" not in data.columns
|
|
698
|
+
or "x2" not in data.columns
|
|
699
|
+
or "y2" not in data.columns
|
|
700
|
+
or "z2" not in data.columns
|
|
701
|
+
or "x3" not in data.columns
|
|
702
|
+
or "y3" not in data.columns
|
|
703
|
+
or "z3" not in data.columns
|
|
704
|
+
or "gc" not in data.columns
|
|
705
|
+
or "lx" not in data.columns
|
|
706
|
+
or "range" not in data.columns
|
|
707
|
+
or "dcbh" not in data.columns
|
|
708
|
+
):
|
|
709
|
+
raise ValueError(
|
|
710
|
+
f"Excel 文件 {path} 中必须包含 x1, y1, z1, x2, y2, z2, x3, y3, z3, gc, lx, range, dcbh 列"
|
|
711
|
+
)
|
|
712
|
+
data = data[
|
|
713
|
+
[
|
|
714
|
+
"x1",
|
|
715
|
+
"y1",
|
|
716
|
+
"z1",
|
|
717
|
+
"x2",
|
|
718
|
+
"y2",
|
|
719
|
+
"z2",
|
|
720
|
+
"x3",
|
|
721
|
+
"y3",
|
|
722
|
+
"z3",
|
|
723
|
+
"gc",
|
|
724
|
+
"lx",
|
|
725
|
+
"range",
|
|
726
|
+
"dcbh",
|
|
727
|
+
]
|
|
728
|
+
].values
|
|
729
|
+
if path_obj.exists():
|
|
730
|
+
old_data = np.load(self.base / "faultage.npy", allow_pickle=True)
|
|
731
|
+
data = np.vstack((old_data, data))
|
|
732
|
+
np.save(path_obj, data)
|
|
733
|
+
zkbhs = np.unique(data[:, -1])
|
|
734
|
+
self.meta.faultage["names"] = list(zkbhs)
|
|
735
|
+
self.meta.faultage["gen"] = False
|
|
736
|
+
self.meta.save(self.base / "meta.json")
|
|
737
|
+
|
|
738
|
+
def comp_faultage(self):
|
|
739
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
740
|
+
voxel_faultage_dir = self.base / "voxel"
|
|
741
|
+
voxel_faultage_dir.mkdir(parents=True, exist_ok=True)
|
|
742
|
+
file_db = FileDB(
|
|
743
|
+
self.base / "voxel" / "faultage" / "faultage.cube", FAULTAGE_FMT
|
|
744
|
+
)("w+b")
|
|
745
|
+
total = grid3d.sum()
|
|
746
|
+
if not (self.base / "faultage.npy").exists():
|
|
747
|
+
raise ValueError(f"断层数据文件不存在: {self.base / 'faultage.npy'}")
|
|
748
|
+
faultage_data = np.load(self.base / "faultage.npy", allow_pickle=True)
|
|
749
|
+
|
|
750
|
+
if faultage_data.size == 0:
|
|
751
|
+
raise ValueError("没有找到任何断层数据文件")
|
|
752
|
+
tins = faultage_data[:, :9].reshape(-1, 3, 3)
|
|
753
|
+
|
|
754
|
+
for idx, _, point in grid3d.enumerate():
|
|
755
|
+
if idx % 1000000 == 0:
|
|
756
|
+
print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
|
|
757
|
+
is_inside, triangle_idx = is_point_in_triangles(
|
|
758
|
+
point, tins, faultage_data[:, -2]
|
|
759
|
+
)
|
|
760
|
+
file_db.append(
|
|
761
|
+
[
|
|
762
|
+
is_inside,
|
|
763
|
+
1,
|
|
764
|
+
self.meta.faultage["names"].index(faultage_data[triangle_idx, -1])
|
|
765
|
+
+ 1
|
|
766
|
+
if is_inside
|
|
767
|
+
else 0,
|
|
768
|
+
]
|
|
769
|
+
)
|
|
770
|
+
self.meta.faultage["gen"] = True
|
|
771
|
+
self.meta.save(self.base / "meta.json")
|
|
772
|
+
|
|
773
|
+
# endregion 断层
|
|
774
|
+
|
|
775
|
+
# region 地层
|
|
776
|
+
def set_stratum(self, names: list, path: Path):
|
|
777
|
+
if path.suffix != "":
|
|
778
|
+
raise ValueError(f"路径 {path} 必须是目录")
|
|
779
|
+
stratum_path = self.base / "voxel" / "stratum"
|
|
780
|
+
if stratum_path.exists():
|
|
781
|
+
shutil.rmtree(stratum_path)
|
|
782
|
+
stratum_path.mkdir(parents=True, exist_ok=True)
|
|
783
|
+
for name in names:
|
|
784
|
+
name_path = path / f"{name}.xlsx"
|
|
785
|
+
if not name_path.exists():
|
|
786
|
+
raise ValueError(f"找不到文件 {name_path}")
|
|
787
|
+
# 读取Excel文件,检查是否包含 x, y, z 列
|
|
788
|
+
data = pd.read_excel(name_path, sheet_name="info")
|
|
789
|
+
if (
|
|
790
|
+
"x" not in data.columns
|
|
791
|
+
or "y" not in data.columns
|
|
792
|
+
or "z" not in data.columns
|
|
793
|
+
):
|
|
794
|
+
raise ValueError(f"Excel 文件 {name_path} 中必须包含 x, y, z 列")
|
|
795
|
+
# 以numpy数组的形式保存,保存为npy文件
|
|
796
|
+
data = data[["x", "y", "z"]].values
|
|
797
|
+
np.save(stratum_path / f"{name}.npy", data)
|
|
798
|
+
self.meta.stratum["names"] = names
|
|
799
|
+
self.meta.save(self.base / "meta.json")
|
|
800
|
+
|
|
801
|
+
def comp_stratum(self, func: str = "idw"):
|
|
802
|
+
self.face_stratum(func)
|
|
803
|
+
print("完成面层插值")
|
|
804
|
+
self.class_stratum()
|
|
805
|
+
print("完成地层分类")
|
|
806
|
+
|
|
807
|
+
def face_stratum(self, func: str = "idw"):
|
|
808
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
809
|
+
grid = grid3d.to_2d()
|
|
810
|
+
for name in self.meta.stratum["names"]:
|
|
811
|
+
data = np.load(self.base / "stratum" / f"{name}.npy") # (N,3) [x,y,z]
|
|
812
|
+
interp = alg.inter.make_interpolator(func, dim=2).fit(data)
|
|
813
|
+
db = FileDB(
|
|
814
|
+
self.base / "voxel" / "stratum" / f"{name}.bin", STRATUM_FACE_FMT
|
|
815
|
+
)("w+b")
|
|
816
|
+
total = grid.sum()
|
|
817
|
+
for idx, _, p in grid.enumerate():
|
|
818
|
+
if idx % 100000 == 0:
|
|
819
|
+
print(f"{name} 处理体素 {idx}/{total} 进度 {idx / total:.2%}")
|
|
820
|
+
z = interp.predict([np.array(p[:2])])[0]
|
|
821
|
+
db.append([z])
|
|
822
|
+
|
|
823
|
+
def class_stratum(self):
|
|
824
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
825
|
+
grid = grid3d.to_2d()
|
|
826
|
+
stratums = self.meta.stratum["names"]
|
|
827
|
+
dbs = [
|
|
828
|
+
FileDB(self.base / "voxel" / "stratum" / f"{name}.bin", STRATUM_FACE_FMT)
|
|
829
|
+
for name in stratums
|
|
830
|
+
]
|
|
831
|
+
for db in dbs:
|
|
832
|
+
db("r+b")
|
|
833
|
+
total = grid3d.sum()
|
|
834
|
+
file_db = FileDB(
|
|
835
|
+
self.base / "voxel" / "stratum" / "stratum.cube", STRATUM_VOXEL_FMT
|
|
836
|
+
)
|
|
837
|
+
file_db("w+b")
|
|
838
|
+
up_lays = []
|
|
839
|
+
up_index = [None, None]
|
|
840
|
+
for idx, index, point in grid3d.enumerate():
|
|
841
|
+
if idx % 1000000 == 0:
|
|
842
|
+
print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
|
|
843
|
+
z = point[2]
|
|
844
|
+
|
|
845
|
+
# 检查是否需要更新缓存(当x,y坐标发生变化时)
|
|
846
|
+
if up_index[0] != index[0] or up_index[1] != index[1]:
|
|
847
|
+
# 新的(x,y)位置,更新缓存
|
|
848
|
+
up_index[0] = index[0]
|
|
849
|
+
up_index[1] = index[1]
|
|
850
|
+
idx2d = grid.get_idx(index)
|
|
851
|
+
|
|
852
|
+
# 一次性查询所有地层数据并缓存到up_lays
|
|
853
|
+
up_lays = []
|
|
854
|
+
for lay_idx in range(len(stratums)):
|
|
855
|
+
up_lays.append(dbs[lay_idx].search(idx2d)[0])
|
|
856
|
+
|
|
857
|
+
# 使用缓存的数据进行地层分类
|
|
858
|
+
label = 0
|
|
859
|
+
for lay_idx in range(1, len(stratums)):
|
|
860
|
+
up_val = up_lays[lay_idx - 1]
|
|
861
|
+
down_val = up_lays[lay_idx]
|
|
862
|
+
if down_val < z <= up_val:
|
|
863
|
+
label = lay_idx
|
|
864
|
+
break
|
|
865
|
+
file_db.append([label])
|
|
866
|
+
self.meta.stratum["gen"] = True
|
|
867
|
+
self.meta.save(self.base / "meta.json")
|
|
868
|
+
|
|
869
|
+
def show_stratum(self, names: str = "*"):
|
|
870
|
+
if names == "*":
|
|
871
|
+
names = self.meta.stratum["names"]
|
|
872
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
873
|
+
grid = grid3d.to_2d()
|
|
874
|
+
vtk = engine()
|
|
875
|
+
for name in names:
|
|
876
|
+
filedb = FileDB(
|
|
877
|
+
self.base / "voxel" / "stratum" / f"{name}.bin", STRATUM_FACE_FMT
|
|
878
|
+
)
|
|
879
|
+
filedb("r+b")
|
|
880
|
+
total = grid.sum()
|
|
881
|
+
layers_grid = []
|
|
882
|
+
for idx, index, point in grid.enumerate():
|
|
883
|
+
if idx % 1000000 == 0:
|
|
884
|
+
print(f"{name} 处理体素 {idx}/{total} 进度 {idx / total:.2%}")
|
|
885
|
+
pre_v = filedb.search(idx)[0]
|
|
886
|
+
layers_grid.append((point[0], point[1], pre_v))
|
|
887
|
+
layers_grid_array = np.array(layers_grid)
|
|
888
|
+
points = np.column_stack(
|
|
889
|
+
(
|
|
890
|
+
layers_grid_array[:, 0],
|
|
891
|
+
layers_grid_array[:, 1],
|
|
892
|
+
layers_grid_array[:, 2],
|
|
893
|
+
)
|
|
894
|
+
)
|
|
895
|
+
surface = pv.PolyData(points).delaunay_2d()
|
|
896
|
+
vtk.add_mesh(surface, scalars=points[:, 2], color=get_colors(name))
|
|
897
|
+
|
|
898
|
+
input("按任意键继续")
|
|
899
|
+
else:
|
|
900
|
+
for name in names.split(","):
|
|
901
|
+
data = np.load(self.base / "stratum" / f"{name}.npy")
|
|
902
|
+
send_f(f"已展示地层数据 - {name} ({data.shape[0]} 个点)")
|
|
903
|
+
|
|
904
|
+
# endregion 地层
|
|
905
|
+
|
|
906
|
+
# region 地质体
|
|
907
|
+
def set_volume(self, path: Path):
|
|
908
|
+
raise ValueError("尚未实现设置地质体功能")
|
|
909
|
+
|
|
910
|
+
# endregion 地质体
|
|
911
|
+
|
|
912
|
+
# region 煤体
|
|
913
|
+
def set_coal(self, path: Path, name: str):
|
|
914
|
+
# 确保coal目录存在
|
|
915
|
+
coal_dir = self.base / "coal"
|
|
916
|
+
coal_dir.mkdir(parents=True, exist_ok=True)
|
|
917
|
+
|
|
918
|
+
path_obj = coal_dir / f"{name}.npy"
|
|
919
|
+
if path_obj.exists():
|
|
920
|
+
raise ValueError(f"煤体名称 {name} 已存在")
|
|
921
|
+
data = pd.read_excel(path, sheet_name="info")
|
|
922
|
+
if (
|
|
923
|
+
"x1" not in data.columns
|
|
924
|
+
or "y1" not in data.columns
|
|
925
|
+
or "z1" not in data.columns
|
|
926
|
+
or "x2" not in data.columns
|
|
927
|
+
or "y2" not in data.columns
|
|
928
|
+
or "z2" not in data.columns
|
|
929
|
+
):
|
|
930
|
+
raise ValueError(f"Excel 文件 {path} 中必须包含 x1, y1, z1, x2, y2, z2 列")
|
|
931
|
+
# 以numpy数组的形式保存,保存为npy文件
|
|
932
|
+
data = data[["x1", "y1", "z1", "x2", "y2", "z2"]].values
|
|
933
|
+
np.save(path_obj, data)
|
|
934
|
+
self.meta.coal["names"].append(name)
|
|
935
|
+
|
|
936
|
+
def comp_coal(self, func: str = "idw", auto_bound: bool = True):
|
|
937
|
+
self.face_coal(func, auto_bound)
|
|
938
|
+
send_f("煤体分类完成")
|
|
939
|
+
self.class_coal(auto_bound)
|
|
940
|
+
send_f("煤体分类完成")
|
|
941
|
+
|
|
942
|
+
def face_coal(self, func: str = "idw", auto_bound: bool = True):
|
|
943
|
+
# 确保voxel/coal目录存在
|
|
944
|
+
voxel_coal_dir = self.base / "voxel" / "coal"
|
|
945
|
+
voxel_coal_dir.mkdir(parents=True, exist_ok=True)
|
|
946
|
+
|
|
947
|
+
coals = self.meta.coal["names"]
|
|
948
|
+
if not coals:
|
|
949
|
+
raise ValueError("未设置煤体")
|
|
950
|
+
for coal in coals:
|
|
951
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
952
|
+
grid = grid3d.to_2d()
|
|
953
|
+
file_db_up = FileDB(
|
|
954
|
+
self.base / "voxel" / "coal" / f"{coal}_up.bin", COAL_FACE_FMT
|
|
955
|
+
)("w+b")
|
|
956
|
+
file_db_down = FileDB(
|
|
957
|
+
self.base / "voxel" / "coal" / f"{coal}_down.bin", COAL_FACE_FMT
|
|
958
|
+
)("w+b")
|
|
959
|
+
total = grid.sum()
|
|
960
|
+
data = np.load(
|
|
961
|
+
self.base / "coal" / f"{coal}.npy"
|
|
962
|
+
) # shape: (N, 6) [x1,y1,z1,x2,y2,z2]
|
|
963
|
+
|
|
964
|
+
# 分离顶板和底板数据
|
|
965
|
+
top_data = data[:, [0, 1, 2]] # [x1, y1, z1] - 顶板数据
|
|
966
|
+
bottom_data = data[:, [3, 4, 5]] # [x2, y2, z2] - 底板数据
|
|
967
|
+
|
|
968
|
+
if auto_bound:
|
|
969
|
+
# 使用xy坐标范围计算边界框(使用顶板数据的xy坐标)
|
|
970
|
+
rotate_rect = RotatedRect(bottom_data[:, :2])
|
|
971
|
+
|
|
972
|
+
# 分别训练顶板和底板的插值器
|
|
973
|
+
# IDW2D和Kriging2D的fit方法期望(N,3)数组[x,y,z]
|
|
974
|
+
interp_up = alg.inter.make_interpolator(func, dim=2).fit(top_data)
|
|
975
|
+
interp_down = alg.inter.make_interpolator(func, dim=2).fit(bottom_data)
|
|
976
|
+
|
|
977
|
+
if auto_bound:
|
|
978
|
+
box, _ = rotate_rect.min_bounding_rect()
|
|
979
|
+
vi_voxel = 0
|
|
980
|
+
for idx, index, point in grid.enumerate():
|
|
981
|
+
if idx % 1000000 == 0:
|
|
982
|
+
print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
|
|
983
|
+
is_coal = False
|
|
984
|
+
up_val = 0
|
|
985
|
+
down_val = 0
|
|
986
|
+
if auto_bound:
|
|
987
|
+
if rotate_rect.in_box(np.array(point[:2]).astype(np.float32), box):
|
|
988
|
+
is_coal = True
|
|
989
|
+
vi_voxel += 1
|
|
990
|
+
up_val = interp_up.predict(np.array(point[:2]))
|
|
991
|
+
down_val = interp_down.predict(np.array(point[:2]))
|
|
992
|
+
else:
|
|
993
|
+
is_coal = True
|
|
994
|
+
vi_voxel += 1
|
|
995
|
+
up_val = interp_up.predict(np.array(point[:2]))
|
|
996
|
+
down_val = interp_down.predict(np.array(point[:2]))
|
|
997
|
+
file_db_up.append([int(is_coal), up_val])
|
|
998
|
+
file_db_down.append([int(is_coal), down_val])
|
|
999
|
+
|
|
1000
|
+
def class_coal(self, auto_bound: bool = True):
|
|
1001
|
+
coals = self.meta.coal["names"]
|
|
1002
|
+
if not coals:
|
|
1003
|
+
raise ValueError("未设置煤体")
|
|
1004
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
1005
|
+
grid = grid3d.to_2d()
|
|
1006
|
+
coal_obj = []
|
|
1007
|
+
for coal in coals:
|
|
1008
|
+
# 只读取之前face_coal生成的数据,不写入
|
|
1009
|
+
up_db = FileDB(
|
|
1010
|
+
self.base / "voxel" / "coal" / f"{coal}_up.bin", COAL_FACE_FMT
|
|
1011
|
+
)("rb")
|
|
1012
|
+
down_db = FileDB(
|
|
1013
|
+
self.base / "voxel" / "coal" / f"{coal}_down.bin", COAL_FACE_FMT
|
|
1014
|
+
)("rb")
|
|
1015
|
+
data = np.load(
|
|
1016
|
+
self.base / "coal" / f"{coal}.npy"
|
|
1017
|
+
) # shape: (N, 6) [x1,y1,z1,x2,y2,z2]
|
|
1018
|
+
|
|
1019
|
+
# 分离顶板和底板数据
|
|
1020
|
+
top_data = data[:, [0, 1, 2]] # [x1, y1, z1] - 顶板数据
|
|
1021
|
+
bottom_data = data[:, [3, 4, 5]] # [x2, y2, z2] - 底板数据
|
|
1022
|
+
if auto_bound:
|
|
1023
|
+
# 使用xy坐标范围计算边界框(使用顶板数据的xy坐标)
|
|
1024
|
+
rotate_rect = RotatedRect(bottom_data[:, :2])
|
|
1025
|
+
box, _ = rotate_rect.min_bounding_rect()
|
|
1026
|
+
print(box)
|
|
1027
|
+
else:
|
|
1028
|
+
rotate_rect = None
|
|
1029
|
+
box = None
|
|
1030
|
+
coal_obj.append(
|
|
1031
|
+
{
|
|
1032
|
+
"name": coal,
|
|
1033
|
+
"up_db": up_db,
|
|
1034
|
+
"down_db": down_db,
|
|
1035
|
+
"box": box,
|
|
1036
|
+
"rotate_rect": rotate_rect,
|
|
1037
|
+
}
|
|
1038
|
+
)
|
|
1039
|
+
db_3d = FileDB(self.base / "voxel" / "coal" / "coal.cube", COAL_VOXEL_FMT)(
|
|
1040
|
+
"w+b"
|
|
1041
|
+
)
|
|
1042
|
+
total = grid3d.sum()
|
|
1043
|
+
up_index = [None, None]
|
|
1044
|
+
up_coal_valuse = []
|
|
1045
|
+
up_sum = 0
|
|
1046
|
+
for idx, index, point in grid3d.enumerate():
|
|
1047
|
+
if idx % 1000000 == 0:
|
|
1048
|
+
print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
|
|
1049
|
+
if index[0] != up_index[0] or index[1] != up_index[1]:
|
|
1050
|
+
up_index = index
|
|
1051
|
+
up_coal_valuse = []
|
|
1052
|
+
for coal in coal_obj:
|
|
1053
|
+
idx2d = grid.get_idx(index)
|
|
1054
|
+
up_in, up_val_up = coal["up_db"].search(idx2d)
|
|
1055
|
+
_, up_val_down = coal["down_db"].search(idx2d)
|
|
1056
|
+
up_coal_valuse.append(
|
|
1057
|
+
{
|
|
1058
|
+
"name": coal["name"],
|
|
1059
|
+
"up_val_up": up_val_up,
|
|
1060
|
+
"up_val_down": up_val_down,
|
|
1061
|
+
}
|
|
1062
|
+
)
|
|
1063
|
+
coal_idx = 0
|
|
1064
|
+
for idx, coal in enumerate(up_coal_valuse):
|
|
1065
|
+
if auto_bound:
|
|
1066
|
+
if coal_obj[idx]["rotate_rect"].in_box(
|
|
1067
|
+
np.array(point[:2]).astype(np.float32), coal_obj[idx]["box"]
|
|
1068
|
+
):
|
|
1069
|
+
if (
|
|
1070
|
+
up_in == 1
|
|
1071
|
+
and coal["up_val_down"] <= point[2] <= coal["up_val_up"]
|
|
1072
|
+
):
|
|
1073
|
+
coal_idx = self.meta.coal["names"].index(coal["name"]) + 1
|
|
1074
|
+
up_sum += 1
|
|
1075
|
+
else:
|
|
1076
|
+
if (
|
|
1077
|
+
up_in == 1
|
|
1078
|
+
and coal["up_val_down"] <= point[2] <= coal["up_val_up"]
|
|
1079
|
+
):
|
|
1080
|
+
coal_idx = self.meta.coal["names"].index(coal["name"]) + 1
|
|
1081
|
+
up_sum += 1
|
|
1082
|
+
db_3d.append([coal_idx])
|
|
1083
|
+
if up_sum == 0:
|
|
1084
|
+
raise ValueError("未找到任何煤体")
|
|
1085
|
+
self.meta.coal["gen"] = True
|
|
1086
|
+
self.meta.save(self.base / "meta.json")
|
|
1087
|
+
|
|
1088
|
+
def show_coal(self, names: str = "*"):
|
|
1089
|
+
if names == "*":
|
|
1090
|
+
names = self.meta.coal["names"]
|
|
1091
|
+
grid3d = Grid3D.from_step(**self.meta.voxel["params"])
|
|
1092
|
+
grid = grid3d.to_2d()
|
|
1093
|
+
vtk = engine()
|
|
1094
|
+
for name in names:
|
|
1095
|
+
filedb_up = FileDB(
|
|
1096
|
+
self.base / "voxel" / "coal" / f"{name}_up.bin", COAL_FACE_FMT
|
|
1097
|
+
)
|
|
1098
|
+
filedb_down = FileDB(
|
|
1099
|
+
self.base / "voxel" / "coal" / f"{name}_down.bin", COAL_FACE_FMT
|
|
1100
|
+
)
|
|
1101
|
+
filedb_up("r+b")
|
|
1102
|
+
filedb_down("r+b")
|
|
1103
|
+
data = np.load(self.base / "coal" / f"{name}.npy")
|
|
1104
|
+
bottom_data = data[:, [3, 4, 5]]
|
|
1105
|
+
rotate_rect = RotatedRect(bottom_data[:, :2])
|
|
1106
|
+
box, _ = rotate_rect.min_bounding_rect()
|
|
1107
|
+
total = grid.sum()
|
|
1108
|
+
layers_up = []
|
|
1109
|
+
layers_down = []
|
|
1110
|
+
for idx, index, point in grid.enumerate():
|
|
1111
|
+
if idx % 1000000 == 0:
|
|
1112
|
+
print(f"{name} 处理体素 {idx}/{total} 进度 {idx / total:.2%}")
|
|
1113
|
+
if rotate_rect.in_box(np.array(point[:2]), box):
|
|
1114
|
+
_, pre_v_up = filedb_up.search(idx)
|
|
1115
|
+
_, pre_v_down = filedb_down.search(idx)
|
|
1116
|
+
layers_up.append((point[0], point[1], pre_v_up))
|
|
1117
|
+
layers_down.append((point[0], point[1], pre_v_down))
|
|
1118
|
+
layers_up_array = np.array(layers_up)
|
|
1119
|
+
layers_down_array = np.array(layers_down)
|
|
1120
|
+
points_up = np.column_stack(
|
|
1121
|
+
(
|
|
1122
|
+
layers_up_array[:, 0],
|
|
1123
|
+
layers_up_array[:, 1],
|
|
1124
|
+
layers_up_array[:, 2],
|
|
1125
|
+
)
|
|
1126
|
+
)
|
|
1127
|
+
points_down = np.column_stack(
|
|
1128
|
+
(
|
|
1129
|
+
layers_down_array[:, 0],
|
|
1130
|
+
layers_down_array[:, 1],
|
|
1131
|
+
layers_down_array[:, 2],
|
|
1132
|
+
)
|
|
1133
|
+
)
|
|
1134
|
+
surface_up = pv.PolyData(points_up).delaunay_2d()
|
|
1135
|
+
vtk.add_mesh(
|
|
1136
|
+
surface_up, scalars=points_up[:, 2], color=get_colors(name)
|
|
1137
|
+
)
|
|
1138
|
+
surface_down = pv.PolyData(points_down).delaunay_2d()
|
|
1139
|
+
vtk.add_mesh(
|
|
1140
|
+
surface_down, scalars=points_down[:, 2], color=get_colors(name)
|
|
1141
|
+
)
|
|
1142
|
+
input("按任意键继续")
|
|
1143
|
+
else:
|
|
1144
|
+
for name in names.split(","):
|
|
1145
|
+
data = np.load(self.base / "stratum" / f"{name}.npy")
|
|
1146
|
+
|
|
1147
|
+
# endregion 煤体分类
|
|
1148
|
+
|
|
1149
|
+
# region 体素
|
|
1150
|
+
def comp_voxel(self):
|
|
1151
|
+
all_cube = {}
|
|
1152
|
+
if self.meta.bound:
|
|
1153
|
+
all_cube["bound"] = FileDB(self.base / "voxel" / "stratum.cube", "=i")(
|
|
1154
|
+
"r+b"
|
|
1155
|
+
)
|
|
1156
|
+
if self.meta.blocks:
|
|
1157
|
+
raise ValueError("未实现")
|
|
1158
|
+
if self.meta.mashgas:
|
|
1159
|
+
all_cube["mashgas"] = FileDB(
|
|
1160
|
+
self.base / "voxel" / "mashgas" / "mashgas.cube", "=i"
|
|
1161
|
+
)("r+b")
|
|
1162
|
+
if self.meta.tunnels:
|
|
1163
|
+
raise ValueError("未实现")
|
|
1164
|
+
if self.meta.faultage["gen"]:
|
|
1165
|
+
all_cube["faultage"] = []
|
|
1166
|
+
faultages = list((self.base / "faultage").glob("*.npy"))
|
|
1167
|
+
for faultage in faultages:
|
|
1168
|
+
all_cube["faultage"].append(
|
|
1169
|
+
FileDB(
|
|
1170
|
+
self.base / "voxel" / "faultage" / f"{faultage.stem}.cube", "=i"
|
|
1171
|
+
)("r+b")
|
|
1172
|
+
)
|
|
1173
|
+
if self.meta.stratum["gen"]:
|
|
1174
|
+
all_cube["stratum"] = FileDB(self.base / "voxel" / "stratum.cube", "=i")(
|
|
1175
|
+
"r+b"
|
|
1176
|
+
)
|
|
1177
|
+
if self.meta.volume:
|
|
1178
|
+
raise ValueError("未实现")
|
|
1179
|
+
if self.meta.coal["gen"]:
|
|
1180
|
+
all_cube["coal"] = FileDB(self.base / "voxel" / "coal.cube", "=i")("r+b")
|
|
1181
|
+
self.meta.voxel["gen"] = True
|
|
1182
|
+
self.meta.save(self.base / "meta.json")
|
|
1183
|
+
|
|
1184
|
+
# endregion 体素
|
|
1185
|
+
|
|
1186
|
+
# region 删除
|
|
1187
|
+
def delete(self):
|
|
1188
|
+
# 删除所有数据
|
|
1189
|
+
shutil.rmtree(self.base)
|
|
1190
|
+
|
|
1191
|
+
# endregion 删除
|
|
1192
|
+
|
|
1193
|
+
# region 清空模型
|
|
1194
|
+
def clearm(self):
|
|
1195
|
+
# 清空模型
|
|
1196
|
+
shutil.rmtree(self.base / "voxel")
|
|
1197
|
+
|
|
1198
|
+
# endregion 清空模型
|
|
1199
|
+
|
|
1200
|
+
# region 判断是否是新数据库
|
|
1201
|
+
def is_new(self) -> bool:
|
|
1202
|
+
return not self.base.exists()
|
|
1203
|
+
|
|
1204
|
+
# endregion 新数据库
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
# endregion 煤矿数据库
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
# region 所有数据库
|
|
1211
|
+
def all_db():
|
|
1212
|
+
base = Path(typr.getenv("JSON_DB_DIR", ".json_db"))
|
|
1213
|
+
# 返回数据库名列表
|
|
1214
|
+
return [db.name for db in base.iterdir() if db.is_dir()]
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
# endregion 所有数据库
|