LoopStructural 1.6.1__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 LoopStructural might be problematic. Click here for more details.
- LoopStructural/__init__.py +52 -0
- LoopStructural/datasets/__init__.py +23 -0
- LoopStructural/datasets/_base.py +301 -0
- LoopStructural/datasets/_example_models.py +10 -0
- LoopStructural/datasets/data/claudius.csv +21049 -0
- LoopStructural/datasets/data/claudiusbb.txt +2 -0
- LoopStructural/datasets/data/duplex.csv +126 -0
- LoopStructural/datasets/data/duplexbb.txt +2 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
- LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
- LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
- LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
- LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
- LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
- LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
- LoopStructural/datasets/data/intrusion.csv +1017 -0
- LoopStructural/datasets/data/intrusionbb.txt +2 -0
- LoopStructural/datasets/data/onefoldbb.txt +2 -0
- LoopStructural/datasets/data/onefolddata.csv +2226 -0
- LoopStructural/datasets/data/refolded_bb.txt +2 -0
- LoopStructural/datasets/data/refolded_fold.csv +205 -0
- LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
- LoopStructural/datatypes/__init__.py +4 -0
- LoopStructural/datatypes/_bounding_box.py +422 -0
- LoopStructural/datatypes/_point.py +166 -0
- LoopStructural/datatypes/_structured_grid.py +94 -0
- LoopStructural/datatypes/_surface.py +184 -0
- LoopStructural/export/exporters.py +554 -0
- LoopStructural/export/file_formats.py +15 -0
- LoopStructural/export/geoh5.py +100 -0
- LoopStructural/export/gocad.py +126 -0
- LoopStructural/export/omf_wrapper.py +88 -0
- LoopStructural/interpolators/__init__.py +105 -0
- LoopStructural/interpolators/_api.py +143 -0
- LoopStructural/interpolators/_builders.py +149 -0
- LoopStructural/interpolators/_cython/__init__.py +0 -0
- LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
- LoopStructural/interpolators/_discrete_interpolator.py +692 -0
- LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
- LoopStructural/interpolators/_geological_interpolator.py +380 -0
- LoopStructural/interpolators/_interpolator_factory.py +89 -0
- LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
- LoopStructural/interpolators/_operator.py +38 -0
- LoopStructural/interpolators/_p1interpolator.py +228 -0
- LoopStructural/interpolators/_p2interpolator.py +277 -0
- LoopStructural/interpolators/_surfe_wrapper.py +174 -0
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
- LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
- LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
- LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
- LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
- LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
- LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
- LoopStructural/interpolators/supports/__init__.py +55 -0
- LoopStructural/interpolators/supports/_aabb.py +77 -0
- LoopStructural/interpolators/supports/_base_support.py +114 -0
- LoopStructural/interpolators/supports/_face_table.py +70 -0
- LoopStructural/interpolators/supports/_support_factory.py +32 -0
- LoopStructural/modelling/__init__.py +29 -0
- LoopStructural/modelling/core/__init__.py +0 -0
- LoopStructural/modelling/core/geological_model.py +1867 -0
- LoopStructural/modelling/features/__init__.py +32 -0
- LoopStructural/modelling/features/_analytical_feature.py +79 -0
- LoopStructural/modelling/features/_base_geological_feature.py +364 -0
- LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
- LoopStructural/modelling/features/_geological_feature.py +288 -0
- LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
- LoopStructural/modelling/features/_region.py +18 -0
- LoopStructural/modelling/features/_structural_frame.py +186 -0
- LoopStructural/modelling/features/_unconformity_feature.py +83 -0
- LoopStructural/modelling/features/builders/__init__.py +5 -0
- LoopStructural/modelling/features/builders/_base_builder.py +111 -0
- LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
- LoopStructural/modelling/features/fault/__init__.py +3 -0
- LoopStructural/modelling/features/fault/_fault_function.py +444 -0
- LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
- LoopStructural/modelling/features/fold/__init__.py +9 -0
- LoopStructural/modelling/features/fold/_fold.py +167 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
- LoopStructural/modelling/features/fold/_foldframe.py +194 -0
- LoopStructural/modelling/features/fold/_svariogram.py +188 -0
- LoopStructural/modelling/input/__init__.py +2 -0
- LoopStructural/modelling/input/fault_network.py +80 -0
- LoopStructural/modelling/input/map2loop_processor.py +165 -0
- LoopStructural/modelling/input/process_data.py +650 -0
- LoopStructural/modelling/input/project_file.py +84 -0
- LoopStructural/modelling/intrusions/__init__.py +25 -0
- LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
- LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
- LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
- LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
- LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
- LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
- LoopStructural/utils/__init__.py +38 -0
- LoopStructural/utils/_surface.py +143 -0
- LoopStructural/utils/_transformation.py +76 -0
- LoopStructural/utils/config.py +18 -0
- LoopStructural/utils/dtm_creator.py +17 -0
- LoopStructural/utils/exceptions.py +31 -0
- LoopStructural/utils/helper.py +292 -0
- LoopStructural/utils/json_encoder.py +18 -0
- LoopStructural/utils/linalg.py +8 -0
- LoopStructural/utils/logging.py +79 -0
- LoopStructural/utils/maths.py +245 -0
- LoopStructural/utils/regions.py +103 -0
- LoopStructural/utils/typing.py +7 -0
- LoopStructural/utils/utils.py +68 -0
- LoopStructural/version.py +1 -0
- LoopStructural/visualisation/__init__.py +11 -0
- LoopStructural-1.6.1.dist-info/LICENSE +21 -0
- LoopStructural-1.6.1.dist-info/METADATA +81 -0
- LoopStructural-1.6.1.dist-info/RECORD +129 -0
- LoopStructural-1.6.1.dist-info/WHEEL +5 -0
- LoopStructural-1.6.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Routines to export geological model data to file in a variety of formats
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pyevtk.hl import unstructuredGridToVTK, pointsToVTK
|
|
7
|
+
from pyevtk.vtk import VtkTriangle
|
|
8
|
+
import numpy as np
|
|
9
|
+
from skimage.measure import marching_cubes
|
|
10
|
+
|
|
11
|
+
from LoopStructural.utils.helper import create_box
|
|
12
|
+
from LoopStructural.export.file_formats import FileFormat
|
|
13
|
+
from LoopStructural.datatypes import Surface
|
|
14
|
+
|
|
15
|
+
from ..utils import getLogger
|
|
16
|
+
|
|
17
|
+
logger = getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def write_feat_surfs(
|
|
21
|
+
model, featurename, file_format=FileFormat.NUMPY, file_name=None, isovalue=0.0
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
Writes out features from a model as 3d surfaces
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
model: GeologicalModel object
|
|
29
|
+
Geological model to export
|
|
30
|
+
file_name: string
|
|
31
|
+
Name of file that model is exported to, including path, but without the file extension
|
|
32
|
+
file_format: export.fileformats.FileFormat object
|
|
33
|
+
OPTIONAL desired format of exported file. Supports GOCAD, VTK & NUMPY. Default is NUMPY
|
|
34
|
+
target_feats: list
|
|
35
|
+
OPTIONAL list of feature names to export, if omitted all faults are exported
|
|
36
|
+
isovalue: float
|
|
37
|
+
OPTIONAL isovalue point at which surface is generated, default is 0.0
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
Tuple of (boolean, [ SimpleNamespace() ... ])
|
|
42
|
+
If successful, boolean is True and SimpleNamespace() objects have the following attributes:
|
|
43
|
+
verts: vertices, numpy ndarray with dtype = float64 & shape = (N,3)
|
|
44
|
+
faces: faces, numpy ndarray with dtype = int32 & shape = (M,3)
|
|
45
|
+
values: values, numpy ndarray with dtype = float32 & shape = (N,)
|
|
46
|
+
normals: normals, numpy ndarray with dtype = float32 & shape = (N,3)
|
|
47
|
+
name: name of feature e.g. fault or supergroup, string
|
|
48
|
+
If not successful, it returns (False, [])
|
|
49
|
+
"""
|
|
50
|
+
# Set up the type of file writer function
|
|
51
|
+
if file_format == FileFormat.GOCAD:
|
|
52
|
+
write_fn = _write_feat_surfs_gocad
|
|
53
|
+
elif file_format == FileFormat.VTK:
|
|
54
|
+
write_fn = _write_feat_surfs_evtk
|
|
55
|
+
elif file_format == FileFormat.NUMPY:
|
|
56
|
+
write_fn = None
|
|
57
|
+
else:
|
|
58
|
+
logger.warning(f"Cannot export to surface file - format {file_format} not supported yet")
|
|
59
|
+
return False, []
|
|
60
|
+
# Skip if not a requested feature
|
|
61
|
+
if featurename not in model:
|
|
62
|
+
logger.warning("{featurename} is not in the model, skipping")
|
|
63
|
+
return False, []
|
|
64
|
+
has_feats = True
|
|
65
|
+
|
|
66
|
+
x = np.linspace(model.bounding_box.bb[0, 0], model.bounding_box.bb[1, 0], model.nsteps[0])
|
|
67
|
+
y = np.linspace(model.bounding_box.bb[0, 1], model.bounding_box.bb[1, 1], model.nsteps[1])
|
|
68
|
+
z = np.linspace(model.bounding_box.bb[1, 2], model.bounding_box.bb[0, 2], model.nsteps[2])
|
|
69
|
+
xx, yy, zz = np.meshgrid(x, y, z, indexing="ij")
|
|
70
|
+
points = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
|
|
71
|
+
val = model[featurename].evaluate_value(points)
|
|
72
|
+
step_vector = np.array([x[1] - x[0], y[1] - y[0], z[1] - z[0]])
|
|
73
|
+
logger.info(f"Creating isosurface of {featurename} at {isovalue}")
|
|
74
|
+
|
|
75
|
+
if isovalue > np.nanmax(val) or isovalue < np.nanmin(val):
|
|
76
|
+
logger.warning(
|
|
77
|
+
f"For {featurename} isovalue {isovalue} doesn't exist inside bounding box, skipping"
|
|
78
|
+
)
|
|
79
|
+
return False, []
|
|
80
|
+
try:
|
|
81
|
+
verts, faces, normals, values = marching_cubes(
|
|
82
|
+
val.reshape(model.nsteps, order="C"), isovalue, spacing=step_vector
|
|
83
|
+
)
|
|
84
|
+
verts += np.array(
|
|
85
|
+
[
|
|
86
|
+
model.bounding_box.bb[0, 0],
|
|
87
|
+
model.bounding_box.bb[0, 1],
|
|
88
|
+
model.bounding_box.bb[1, 2],
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
model.rescale(verts)
|
|
92
|
+
surf = Surface(
|
|
93
|
+
vertices=verts,
|
|
94
|
+
triangles=faces,
|
|
95
|
+
normals=normals,
|
|
96
|
+
values=values,
|
|
97
|
+
name=featurename.replace(" ", "-"),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
except (ValueError, RuntimeError) as e:
|
|
101
|
+
logger.debug(f"Exception creating feature surface {featurename}: {e}")
|
|
102
|
+
logger.warning(f"Cannot isosurface {featurename} at {isovalue}, skipping")
|
|
103
|
+
return False, []
|
|
104
|
+
|
|
105
|
+
if not has_feats:
|
|
106
|
+
logger.warning("Cannot locate features in model")
|
|
107
|
+
return False, []
|
|
108
|
+
|
|
109
|
+
# Call the file writer function
|
|
110
|
+
result = True
|
|
111
|
+
if write_fn is not None:
|
|
112
|
+
result = write_fn(surf, file_name)
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _write_feat_surfs_evtk(surf, file_name):
|
|
117
|
+
"""
|
|
118
|
+
Writes out an unstructured VTK file containing a 2d fault surface
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
surf_list: [ SimpleNamespace() ... ]
|
|
123
|
+
Details of the surfaces, as a list of SimpleNamespace() objects. Fields are:
|
|
124
|
+
verts: vertices, numpy ndarray with dtype = float64 & shape = (N,3)
|
|
125
|
+
faces: faces, numpy ndarray with dtype = int32 & shape = (M,3)
|
|
126
|
+
values: values, numpy ndarray with dtype = float32 & shape = (N,)
|
|
127
|
+
normals: normals, numpy ndarray with dtype = float32 & shape = (N,3)
|
|
128
|
+
name: name of feature e.g. fault or supergroup, string
|
|
129
|
+
|
|
130
|
+
file_name: string
|
|
131
|
+
file name to be written out without extension
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
True if successful
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
connectivity = np.zeros(0)
|
|
139
|
+
offsets = np.zeros(0)
|
|
140
|
+
cell_types = np.zeros(0)
|
|
141
|
+
x = np.zeros(0)
|
|
142
|
+
y = np.zeros(0)
|
|
143
|
+
z = np.zeros(0)
|
|
144
|
+
conn_idx = 0
|
|
145
|
+
offset_idx = 0
|
|
146
|
+
pointData = np.zeros(0)
|
|
147
|
+
|
|
148
|
+
# Concatenate the geometry of all features into x, y, z, connectivity, offsets & cell_types
|
|
149
|
+
|
|
150
|
+
# Accumulate x,y,z values in 1-d array
|
|
151
|
+
x = np.append(x, np.ascontiguousarray(surf.verts[:, 0]))
|
|
152
|
+
y = np.append(y, np.ascontiguousarray(surf.verts[:, 1]))
|
|
153
|
+
z = np.append(z, np.ascontiguousarray(surf.verts[:, 2]))
|
|
154
|
+
|
|
155
|
+
# Convert faces to int64
|
|
156
|
+
conn = np.array(surf.faces, dtype=np.int64)
|
|
157
|
+
|
|
158
|
+
# Reshape connections to 1d
|
|
159
|
+
conn = conn.reshape(conn.size)
|
|
160
|
+
conn += conn_idx
|
|
161
|
+
|
|
162
|
+
# Make offsets into connection array
|
|
163
|
+
offs = np.array(list(range(3 + offset_idx, len(conn) + offset_idx, 3)), dtype=np.int64)
|
|
164
|
+
|
|
165
|
+
# Set VTK datatype as triangles
|
|
166
|
+
ctype = np.zeros(offs.size)
|
|
167
|
+
ctype.fill(VtkTriangle.tid)
|
|
168
|
+
|
|
169
|
+
# Accumulate values in arrays
|
|
170
|
+
connectivity = np.append(connectivity, conn)
|
|
171
|
+
|
|
172
|
+
offsets = np.append(offsets, offs)
|
|
173
|
+
cell_types = np.append(cell_types, ctype)
|
|
174
|
+
|
|
175
|
+
pointData = np.append(pointData, surf.values.reshape(surf.values.size))
|
|
176
|
+
|
|
177
|
+
# Enable connections to point to next set of vertices
|
|
178
|
+
conn_idx = x.size
|
|
179
|
+
|
|
180
|
+
# Enable offsets to point to next set of connections
|
|
181
|
+
offset_idx = connectivity.size
|
|
182
|
+
|
|
183
|
+
# Write out file
|
|
184
|
+
try:
|
|
185
|
+
logger.info(f"Writing file {file_name}.vtu")
|
|
186
|
+
unstructuredGridToVTK(
|
|
187
|
+
f"{file_name}",
|
|
188
|
+
x,
|
|
189
|
+
y,
|
|
190
|
+
z,
|
|
191
|
+
connectivity=connectivity,
|
|
192
|
+
offsets=offsets,
|
|
193
|
+
cell_types=cell_types,
|
|
194
|
+
pointData={"values": pointData},
|
|
195
|
+
)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.warning(f"Cannot export fault surface to VTK file {file_name}: {e}")
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _write_feat_surfs_gocad(surf, file_name):
|
|
204
|
+
"""
|
|
205
|
+
Writes out a GOCAD TSURF file for each surface in list
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
surf_list: [ SimpleNamespace() ... ]
|
|
210
|
+
Details of the surfaces, as a list of SimpleNamespace() objects. Fields are:
|
|
211
|
+
verts: vertices, numpy ndarray with dtype = float64 & shape = (N,3)
|
|
212
|
+
faces: faces, numpy ndarray with dtype = int32 & shape = (M,3)
|
|
213
|
+
values: values, numpy ndarray with dtype = float32 & shape = (N,)
|
|
214
|
+
normals: normals, numpy ndarray with dtype = float32 & shape = (N,3)
|
|
215
|
+
name: name of feature e.g. fault or supergroup, string
|
|
216
|
+
|
|
217
|
+
file_name: string
|
|
218
|
+
Desired filename
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
True if successful
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
from pathlib import Path
|
|
226
|
+
|
|
227
|
+
file_name = Path(file_name).with_suffix(".ts")
|
|
228
|
+
with open(f"{file_name}", "w") as fd:
|
|
229
|
+
fd.write(
|
|
230
|
+
f"""GOCAD TSurf 1
|
|
231
|
+
HEADER {{
|
|
232
|
+
*solid*color: #ffa500
|
|
233
|
+
ivolmap: false
|
|
234
|
+
imap: false
|
|
235
|
+
name: {surf.name}
|
|
236
|
+
}}
|
|
237
|
+
GOCAD_ORIGINAL_COORDINATE_SYSTEM
|
|
238
|
+
NAME Default
|
|
239
|
+
PROJECTION Unknown
|
|
240
|
+
DATUM Unknown
|
|
241
|
+
AXIS_NAME X Y Z
|
|
242
|
+
AXIS_UNIT m m m
|
|
243
|
+
ZPOSITIVE Elevation
|
|
244
|
+
END_ORIGINAL_COORDINATE_SYSTEM
|
|
245
|
+
GEOLOGICAL_FEATURE {surf.name}
|
|
246
|
+
GEOLOGICAL_TYPE fault
|
|
247
|
+
PROPERTY_CLASS_HEADER X {{
|
|
248
|
+
kind: X
|
|
249
|
+
unit: m
|
|
250
|
+
}}
|
|
251
|
+
PROPERTY_CLASS_HEADER Y {{
|
|
252
|
+
kind: Y
|
|
253
|
+
unit: m
|
|
254
|
+
}}
|
|
255
|
+
PROPERTY_CLASS_HEADER Z {{
|
|
256
|
+
kind: Z
|
|
257
|
+
unit: m
|
|
258
|
+
is_z: on
|
|
259
|
+
}}
|
|
260
|
+
PROPERTY_CLASS_HEADER vector3d {{
|
|
261
|
+
kind: Length
|
|
262
|
+
unit: m
|
|
263
|
+
}}
|
|
264
|
+
TFACE
|
|
265
|
+
"""
|
|
266
|
+
)
|
|
267
|
+
v_idx = 1
|
|
268
|
+
v_map = {}
|
|
269
|
+
for idx, vert in enumerate(surf.vertices):
|
|
270
|
+
if not np.isnan(vert[0]) and not np.isnan(vert[1]) and not np.isnan(vert[2]):
|
|
271
|
+
fd.write(f"VRTX {v_idx:} {vert[0]} {vert[1]} {vert[2]} \n")
|
|
272
|
+
v_map[idx] = v_idx
|
|
273
|
+
v_idx += 1
|
|
274
|
+
for face in surf.triangles:
|
|
275
|
+
if face[0] in v_map and face[1] in v_map and face[2] in v_map:
|
|
276
|
+
fd.write(f"TRGL {v_map[face[0]]} {v_map[face[1]]} {v_map[face[2]]} \n")
|
|
277
|
+
fd.write("END\n")
|
|
278
|
+
return True
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def write_cubeface(model, file_name, data_label, nsteps, file_format):
|
|
282
|
+
"""
|
|
283
|
+
Writes out the model as a cuboid with six rectangular surfaces
|
|
284
|
+
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
model : GeologicalModel object
|
|
288
|
+
Geological model to export
|
|
289
|
+
file_name : string
|
|
290
|
+
Name of file that model is exported to, including path, but without the file extension
|
|
291
|
+
data_label : string
|
|
292
|
+
A data label to insert into export file
|
|
293
|
+
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
|
|
294
|
+
3d array dimensions
|
|
295
|
+
file_format: export.fileformats.FileFormat object
|
|
296
|
+
Desired format of exported file. Supports VTK
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
True if successful
|
|
301
|
+
|
|
302
|
+
"""
|
|
303
|
+
if file_format == FileFormat.VTK:
|
|
304
|
+
return _write_cubeface_evtk(model, file_name, data_label, nsteps)
|
|
305
|
+
|
|
306
|
+
logger.warning(f"Cannot export to file - format {file_format} not supported yet")
|
|
307
|
+
return False
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def write_vol(model, file_name, data_label, nsteps, file_format):
|
|
311
|
+
"""
|
|
312
|
+
Writes out the model as a 3d volume grid
|
|
313
|
+
|
|
314
|
+
Parameters
|
|
315
|
+
----------
|
|
316
|
+
model : GeologicalModel object
|
|
317
|
+
Geological model to export
|
|
318
|
+
file_name : string
|
|
319
|
+
Name of file that model is exported to, including path, but without the file extension
|
|
320
|
+
data_label : string
|
|
321
|
+
A data label to insert into export file
|
|
322
|
+
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
|
|
323
|
+
3d array dimensions
|
|
324
|
+
file_format: export.fileformats.FileFormat object
|
|
325
|
+
Desired format of exported file. Supports VTK and GOCAD
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
-------
|
|
329
|
+
True if successful
|
|
330
|
+
|
|
331
|
+
"""
|
|
332
|
+
if file_format == FileFormat.VTK:
|
|
333
|
+
return _write_vol_evtk(model, file_name, data_label, nsteps)
|
|
334
|
+
if file_format == FileFormat.GOCAD:
|
|
335
|
+
return _write_vol_gocad(model, file_name, data_label, nsteps)
|
|
336
|
+
|
|
337
|
+
logger.warning(f"Cannot export to file - format {file_format} not supported yet")
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _write_cubeface_evtk(model, file_name, data_label, nsteps, real_coords=True):
|
|
342
|
+
"""
|
|
343
|
+
Writes out the model as a cuboid with six rectangular surfaces in VTK unstructured grid format
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
model : GeologicalModel object
|
|
348
|
+
Geological model to export
|
|
349
|
+
file_name : string
|
|
350
|
+
Name of file that model is exported to, including path, but without the file extension
|
|
351
|
+
data_label : string
|
|
352
|
+
A data label to insert into export file
|
|
353
|
+
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
|
|
354
|
+
3d array dimensions
|
|
355
|
+
|
|
356
|
+
Returns
|
|
357
|
+
-------
|
|
358
|
+
True if successful
|
|
359
|
+
|
|
360
|
+
"""
|
|
361
|
+
# Evaluate model at points
|
|
362
|
+
points, tri = create_box(model.bounding_box, nsteps)
|
|
363
|
+
val = model.evaluate_model(points, scale=False)
|
|
364
|
+
if real_coords:
|
|
365
|
+
model.rescale(points)
|
|
366
|
+
|
|
367
|
+
# Define vertices
|
|
368
|
+
x = np.zeros(points.shape[0])
|
|
369
|
+
y = np.zeros(points.shape[0])
|
|
370
|
+
z = np.zeros(points.shape[0])
|
|
371
|
+
for i in range(points.shape[0]):
|
|
372
|
+
x[i], y[i], z[i] = points[i][0], points[i][1], points[i][2]
|
|
373
|
+
|
|
374
|
+
# Define connectivity or vertices that belongs to each element
|
|
375
|
+
conn = np.zeros(tri.shape[0] * 3)
|
|
376
|
+
for i in range(tri.shape[0]):
|
|
377
|
+
conn[i * 3], conn[i * 3 + 1], conn[i * 3 + 2] = tri[i][0], tri[i][1], tri[i][2]
|
|
378
|
+
|
|
379
|
+
# Define offset of last vertex of each element
|
|
380
|
+
offset = np.zeros(tri.shape[0])
|
|
381
|
+
for i in range(tri.shape[0]):
|
|
382
|
+
offset[i] = (i + 1) * 3
|
|
383
|
+
|
|
384
|
+
# Define cell types
|
|
385
|
+
ctype = np.full(tri.shape[0], VtkTriangle.tid)
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
unstructuredGridToVTK(
|
|
389
|
+
file_name,
|
|
390
|
+
x,
|
|
391
|
+
y,
|
|
392
|
+
z,
|
|
393
|
+
connectivity=conn,
|
|
394
|
+
offsets=offset,
|
|
395
|
+
cell_types=ctype,
|
|
396
|
+
cellData=None,
|
|
397
|
+
pointData={data_label: val},
|
|
398
|
+
)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
logger.warning(f"Cannot export cuboid surface to VTK file {file_name}: {e}")
|
|
401
|
+
return False
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _write_vol_evtk(model, file_name, data_label, nsteps, real_coords=True):
|
|
406
|
+
"""
|
|
407
|
+
Writes out the model as a 3d volume grid in VTK points format
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
model : GeologicalModel object
|
|
412
|
+
Geological model to export
|
|
413
|
+
file_name : string
|
|
414
|
+
Name of file that model is exported to, including path, but without the file extension
|
|
415
|
+
data_label : string
|
|
416
|
+
A data label to insert into export file
|
|
417
|
+
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
|
|
418
|
+
3d array dimensions
|
|
419
|
+
|
|
420
|
+
Returns
|
|
421
|
+
-------
|
|
422
|
+
True if successful
|
|
423
|
+
|
|
424
|
+
"""
|
|
425
|
+
# Define grid spacing
|
|
426
|
+
xyz = model.bounding_box.regular_grid(nsteps)
|
|
427
|
+
vals = model.evaluate_model(xyz, scale=False)
|
|
428
|
+
if real_coords:
|
|
429
|
+
model.rescale(xyz)
|
|
430
|
+
|
|
431
|
+
# Define vertices - xyz.shape[0] is length of vector array
|
|
432
|
+
x = np.zeros(xyz.shape[0])
|
|
433
|
+
y = np.zeros(xyz.shape[0])
|
|
434
|
+
z = np.zeros(xyz.shape[0])
|
|
435
|
+
for i in range(xyz.shape[0]):
|
|
436
|
+
x[i], y[i], z[i] = xyz[i][0], xyz[i][1], xyz[i][2]
|
|
437
|
+
|
|
438
|
+
# Write to grid
|
|
439
|
+
try:
|
|
440
|
+
pointsToVTK(file_name, x, y, z, data={data_label: vals})
|
|
441
|
+
except Exception as e:
|
|
442
|
+
logger.warning(f"Cannot export volume to VTK file {file_name}: {e}")
|
|
443
|
+
return False
|
|
444
|
+
return True
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _write_vol_gocad(model, file_name, data_label, nsteps, real_coords=True):
|
|
448
|
+
"""
|
|
449
|
+
Writes out the model as a 3d volume grid in GOCAD VOXET object format
|
|
450
|
+
|
|
451
|
+
Parameters
|
|
452
|
+
----------
|
|
453
|
+
model : GeologicalModel object
|
|
454
|
+
Geological model to export
|
|
455
|
+
file_name : string
|
|
456
|
+
Name of file that model is exported to, including path, but without the file extension
|
|
457
|
+
data_label : string
|
|
458
|
+
A data label to insert into export file
|
|
459
|
+
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
|
|
460
|
+
3d array dimensions
|
|
461
|
+
|
|
462
|
+
Returns
|
|
463
|
+
-------
|
|
464
|
+
True if successful
|
|
465
|
+
|
|
466
|
+
"""
|
|
467
|
+
# Define grid spacing in model scale coords
|
|
468
|
+
xyz = model.bounding_box.regular_grid(nsteps)
|
|
469
|
+
|
|
470
|
+
vals = model.evaluate_model(xyz, scale=False)
|
|
471
|
+
# Use FORTRAN style indexing for GOCAD VOXET
|
|
472
|
+
vol_vals = np.reshape(vals, nsteps, order="F")
|
|
473
|
+
bbox = model.bounding_box[:]
|
|
474
|
+
|
|
475
|
+
# Convert bounding box to real world scale coords
|
|
476
|
+
if real_coords:
|
|
477
|
+
model.rescale(bbox)
|
|
478
|
+
|
|
479
|
+
# If integer values
|
|
480
|
+
if type(vals[0]) is np.int64:
|
|
481
|
+
d_type = np.int8
|
|
482
|
+
no_data_val = None
|
|
483
|
+
prop_esize = 1
|
|
484
|
+
prop_storage_type = "Octet"
|
|
485
|
+
|
|
486
|
+
# If float values
|
|
487
|
+
elif type(vals[0]) is np.float32:
|
|
488
|
+
d_type = np.dtype(">f4")
|
|
489
|
+
no_data_val = -999999.0
|
|
490
|
+
prop_esize = 4
|
|
491
|
+
prop_storage_type = "Float"
|
|
492
|
+
else:
|
|
493
|
+
logger.warning(
|
|
494
|
+
f"Cannot export volume to GOCAD VOXET file: Unsupported type {type(vals[0])}"
|
|
495
|
+
)
|
|
496
|
+
return False
|
|
497
|
+
|
|
498
|
+
# Write out VOXET file
|
|
499
|
+
vo_filename = file_name + ".vo"
|
|
500
|
+
data_filename = file_name + "@@"
|
|
501
|
+
try:
|
|
502
|
+
with open(vo_filename, "w") as fp:
|
|
503
|
+
fp.write(
|
|
504
|
+
f"""GOCAD Voxet 1
|
|
505
|
+
HEADER {{
|
|
506
|
+
name: {os.path.basename(file_name)}
|
|
507
|
+
}}
|
|
508
|
+
GOCAD_ORIGINAL_COORDINATE_SYSTEM
|
|
509
|
+
NAME Default
|
|
510
|
+
AXIS_NAME "X" "Y" "Z"
|
|
511
|
+
AXIS_UNIT "m" "m" "m"
|
|
512
|
+
ZPOSITIVE Elevation
|
|
513
|
+
END_ORIGINAL_COORDINATE_SYSTEM
|
|
514
|
+
AXIS_O 0.000000 0.000000 0.000000
|
|
515
|
+
AXIS_U 1.000000 0.000000 0.000000
|
|
516
|
+
AXIS_V 0.000000 1.000000 0.000000
|
|
517
|
+
AXIS_W 0.000000 0.000000 1.000000
|
|
518
|
+
AXIS_MIN {bbox[0, 0]} {bbox[0, 1]} {bbox[0, 2]}
|
|
519
|
+
AXIS_MAX {bbox[1, 0]} {bbox[1, 1]} {bbox[1, 2]}
|
|
520
|
+
AXIS_N {nsteps[0]} {nsteps[1]} {nsteps[2]}
|
|
521
|
+
AXIS_NAME "X" "Y" "Z"
|
|
522
|
+
AXIS_UNIT "m" "m" "m"
|
|
523
|
+
AXIS_TYPE even even even
|
|
524
|
+
PROPERTY 1 {data_label}
|
|
525
|
+
PROPERTY_CLASS 1 {data_label}
|
|
526
|
+
PROP_UNIT 1 {data_label}
|
|
527
|
+
PROPERTY_CLASS_HEADER 1 {data_label} {{
|
|
528
|
+
}}
|
|
529
|
+
PROPERTY_SUBCLASS 1 QUANTITY {prop_storage_type}
|
|
530
|
+
"""
|
|
531
|
+
)
|
|
532
|
+
if no_data_val is not None:
|
|
533
|
+
fp.write(f"PROP_NO_DATA_VALUE 1 {no_data_val}\n")
|
|
534
|
+
fp.write(
|
|
535
|
+
f"""PROP_ETYPE 1 IEEE
|
|
536
|
+
PROP_FORMAT 1 RAW
|
|
537
|
+
PROP_ESIZE 1 {prop_esize}
|
|
538
|
+
PROP_OFFSET 1 0
|
|
539
|
+
PROP_FILE 1 {data_filename}
|
|
540
|
+
END\n"""
|
|
541
|
+
)
|
|
542
|
+
except IOError as exc:
|
|
543
|
+
logger.warning(f"Cannot export volume to GOCAD VOXET file {vo_filename}: {exc}")
|
|
544
|
+
return False
|
|
545
|
+
|
|
546
|
+
# Write out accompanying binary data file
|
|
547
|
+
export_vals = np.array(vol_vals, dtype=d_type)
|
|
548
|
+
try:
|
|
549
|
+
with open(data_filename, "wb") as fp:
|
|
550
|
+
export_vals.tofile(fp)
|
|
551
|
+
except IOError as exc:
|
|
552
|
+
logger.warning(f"Cannot export volume to GOCAD VOXET data file {data_filename}: {exc}")
|
|
553
|
+
return False
|
|
554
|
+
return True
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import geoh5py
|
|
2
|
+
import geoh5py.workspace
|
|
3
|
+
import numpy as np
|
|
4
|
+
from LoopStructural.datatypes import ValuePoints, VectorPoints
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def add_surface_to_geoh5(filename, surface, overwrite=True, groupname="Loop"):
|
|
8
|
+
with geoh5py.workspace.Workspace(filename) as workspace:
|
|
9
|
+
group = workspace.get_entity(groupname)[0]
|
|
10
|
+
if not group:
|
|
11
|
+
group = geoh5py.groups.ContainerGroup.create(
|
|
12
|
+
workspace, name=groupname, allow_delete=True
|
|
13
|
+
)
|
|
14
|
+
if surface.name in workspace.list_entities_name.values():
|
|
15
|
+
existing_surf = workspace.get_entity(surface.name)
|
|
16
|
+
existing_surf[0].allow_delete = True
|
|
17
|
+
if overwrite:
|
|
18
|
+
workspace.remove_entity(existing_surf[0])
|
|
19
|
+
data = {}
|
|
20
|
+
if surface.properties is not None:
|
|
21
|
+
for k, v in surface.properties.items():
|
|
22
|
+
data[k] = {'association': "VERTEX", "values": v}
|
|
23
|
+
surface = geoh5py.objects.Surface.create(
|
|
24
|
+
workspace,
|
|
25
|
+
name=surface.name,
|
|
26
|
+
vertices=surface.vertices,
|
|
27
|
+
cells=surface.triangles,
|
|
28
|
+
parent=group,
|
|
29
|
+
)
|
|
30
|
+
surface.add_data(data)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def add_points_to_geoh5(filename, point, overwrite=True, groupname="Loop"):
|
|
34
|
+
with geoh5py.workspace.Workspace(filename) as workspace:
|
|
35
|
+
group = workspace.get_entity(groupname)[0]
|
|
36
|
+
if not group:
|
|
37
|
+
group = geoh5py.groups.ContainerGroup.create(
|
|
38
|
+
workspace, name=groupname, allow_delete=True
|
|
39
|
+
)
|
|
40
|
+
if point.name in workspace.list_entities_name.values():
|
|
41
|
+
existing_point = workspace.get_entity(point.name)
|
|
42
|
+
existing_point[0].allow_delete = True
|
|
43
|
+
if overwrite:
|
|
44
|
+
workspace.remove_entity(existing_point[0])
|
|
45
|
+
data = {}
|
|
46
|
+
if point.properties is not None:
|
|
47
|
+
for k, v in point.properties.items():
|
|
48
|
+
data[k] = {'association': "VERTEX", "values": v}
|
|
49
|
+
if isinstance(point, VectorPoints):
|
|
50
|
+
data['vx'] = {'association': "VERTEX", "values": point.vectors[:, 0]}
|
|
51
|
+
data['vy'] = {'association': "VERTEX", "values": point.vectors[:, 1]}
|
|
52
|
+
data['vz'] = {'association': "VERTEX", "values": point.vectors[:, 2]}
|
|
53
|
+
|
|
54
|
+
if isinstance(point, ValuePoints):
|
|
55
|
+
data['val'] = {'association': "VERTEX", "values": point.values}
|
|
56
|
+
point = geoh5py.objects.Points.create(
|
|
57
|
+
workspace,
|
|
58
|
+
name=point.name,
|
|
59
|
+
vertices=point.locations,
|
|
60
|
+
parent=group,
|
|
61
|
+
)
|
|
62
|
+
point.add_data(data)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def add_structured_grid_to_geoh5(filename, structured_grid, overwrite=True, groupname="Loop"):
|
|
66
|
+
with geoh5py.workspace.Workspace(filename) as workspace:
|
|
67
|
+
group = workspace.get_entity(groupname)[0]
|
|
68
|
+
if not group:
|
|
69
|
+
group = geoh5py.groups.ContainerGroup.create(
|
|
70
|
+
workspace, name=groupname, allow_delete=True
|
|
71
|
+
)
|
|
72
|
+
if structured_grid.name in workspace.list_entities_name.values():
|
|
73
|
+
existing_block = workspace.get_entity(structured_grid.name)
|
|
74
|
+
existing_block[0].allow_delete = True
|
|
75
|
+
if overwrite:
|
|
76
|
+
workspace.remove_entity(existing_block[0])
|
|
77
|
+
data = {}
|
|
78
|
+
if structured_grid.cell_properties is not None:
|
|
79
|
+
for k, v in structured_grid.cell_properties.items():
|
|
80
|
+
data[k] = {
|
|
81
|
+
'association': "CELL",
|
|
82
|
+
"values": np.rot90(v.reshape(structured_grid.nsteps - 1, order="F")).flatten(),
|
|
83
|
+
}
|
|
84
|
+
block = geoh5py.objects.BlockModel.create(
|
|
85
|
+
workspace,
|
|
86
|
+
name=structured_grid.name,
|
|
87
|
+
origin=structured_grid.origin,
|
|
88
|
+
u_cell_delimiters=np.cumsum(
|
|
89
|
+
np.ones(structured_grid.nsteps[0]) * structured_grid.step_vector[0]
|
|
90
|
+
), # Offsets along u
|
|
91
|
+
v_cell_delimiters=np.cumsum(
|
|
92
|
+
np.ones(structured_grid.nsteps[1]) * structured_grid.step_vector[1]
|
|
93
|
+
), # Offsets along v
|
|
94
|
+
z_cell_delimiters=np.cumsum(
|
|
95
|
+
np.ones(structured_grid.nsteps[2]) * structured_grid.step_vector[2]
|
|
96
|
+
), # Offsets along z (down)
|
|
97
|
+
rotation=0.0,
|
|
98
|
+
parent=group,
|
|
99
|
+
)
|
|
100
|
+
block.add_data(data)
|