Skip to content

Commit 7ed0355

Browse files
authored
Merge pull request #47 from vjf/export-2
Ability to export GOCAD VOXET object files
2 parents 2897c44 + f920d08 commit 7ed0355

File tree

2 files changed

+134
-16
lines changed

2 files changed

+134
-16
lines changed

LoopStructural/export/exporters.py

Lines changed: 129 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
Routines to export geological model data to file in a variety of formats
33
"""
44
import logging
5+
import os
56
from pyevtk.hl import unstructuredGridToVTK, pointsToVTK
67
from pyevtk.vtk import VtkTriangle
78
import numpy as np
89

910
from LoopStructural.utils.helper import create_box
1011
from LoopStructural.export.file_formats import FileFormat
1112

12-
13+
1314
from LoopStructural.utils import getLogger
1415
logger = getLogger(__name__)
1516

@@ -29,7 +30,7 @@ def write_cubeface(model, file_name, data_label, nsteps, file_format):
2930
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
3031
3d array dimensions
3132
file_format: export.fileformats.FileFormat object
32-
Desired format of exported file
33+
Desired format of exported file. Supports VTK
3334
3435
Returns
3536
-------
@@ -58,7 +59,7 @@ def write_vol(model, file_name, data_label, nsteps, file_format):
5859
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
5960
3d array dimensions
6061
file_format: export.fileformats.FileFormat object
61-
Desired format of exported file
62+
Desired format of exported file. Supports VTK and GOCAD
6263
6364
Returns
6465
-------
@@ -67,14 +68,16 @@ def write_vol(model, file_name, data_label, nsteps, file_format):
6768
"""
6869
if file_format == FileFormat.VTK:
6970
return _write_vol_evtk(model, file_name, data_label, nsteps)
71+
if file_format == FileFormat.GOCAD:
72+
return _write_vol_gocad(model, file_name, data_label, nsteps)
7073

7174
logger.warning("Cannot export to file - format {} not supported yet".format(str(file_format)))
7275
return False
7376

7477

7578
def _write_cubeface_evtk(model, file_name, data_label, nsteps, real_coords=True):
7679
"""
77-
Writes out the model as a cuboid with six rectangular surfaces
80+
Writes out the model as a cuboid with six rectangular surfaces in VTK unstructured grid format
7881
7982
Parameters
8083
----------
@@ -119,16 +122,17 @@ def _write_cubeface_evtk(model, file_name, data_label, nsteps, real_coords=True)
119122
ctype = np.full(tri.shape[0], VtkTriangle.tid)
120123

121124
try:
122-
unstructuredGridToVTK(file_name, x, y, z, connectivity = conn, offsets = offset, cell_types = ctype, cellData = None, pointData = {data_label: val})
125+
unstructuredGridToVTK(file_name, x, y, z, connectivity=conn, offsets=offset, cell_types=ctype,
126+
cellData=None, pointData={data_label: val})
123127
except Exception as e:
124-
logger.warning("Cannot export cuboid surface to file {}: {}".format(file_name, str(e)))
128+
logger.warning("Cannot export cuboid surface to VTK file {}: {}".format(file_name, str(e)))
125129
return False
126-
return True
130+
return True
127131

128132

129133
def _write_vol_evtk(model, file_name, data_label, nsteps, real_coords=True):
130134
"""
131-
Writes out the model as a 3d volume grid
135+
Writes out the model as a 3d volume grid in VTK points format
132136
133137
Parameters
134138
----------
@@ -153,12 +157,13 @@ def _write_vol_evtk(model, file_name, data_label, nsteps, real_coords=True):
153157

154158
# Generate model values in 3d grid
155159
xx, yy, zz = np.meshgrid(loop_X, loop_Y, loop_Z, indexing='ij')
160+
# xyz is N x 3 vector array
156161
xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
157162
vals = model.evaluate_model(xyz, scale=False)
158163
if real_coords:
159164
model.rescale(xyz)
160165

161-
# Define vertices
166+
# Define vertices - xyz.shape[0] is length of vector array
162167
x = np.zeros(xyz.shape[0])
163168
y = np.zeros(xyz.shape[0])
164169
z = np.zeros(xyz.shape[0])
@@ -167,10 +172,122 @@ def _write_vol_evtk(model, file_name, data_label, nsteps, real_coords=True):
167172

168173
# Write to grid
169174
try:
170-
pointsToVTK(file_name, x, y, z, data= { data_label: vals})
175+
pointsToVTK(file_name, x, y, z, data={data_label: vals})
171176
except Exception as e:
172-
logger.warning("Cannot export volume to file {}: {}".format(file_name, str(e)))
177+
logger.warning("Cannot export volume to VTK file {}: {}".format(file_name, str(e)))
173178
return False
174-
return True
179+
return True
180+
175181

182+
def _write_vol_gocad(model, file_name, data_label, nsteps, real_coords=True):
183+
"""
184+
Writes out the model as a 3d volume grid in GOCAD VOXET object format
185+
186+
Parameters
187+
----------
188+
model : GeologicalModel object
189+
Geological model to export
190+
file_name : string
191+
Name of file that model is exported to, including path, but without the file extension
192+
data_label : string
193+
A data label to insert into export file
194+
nsteps : np.array([num-x-steps, num-y-steps, num-z-steps])
195+
3d array dimensions
176196
197+
Returns
198+
-------
199+
True if successful
200+
201+
"""
202+
# Define grid spacing in model scale coords
203+
loop_X = np.linspace(model.bounding_box[0, 0], model.bounding_box[1, 0], nsteps[0])
204+
loop_Y = np.linspace(model.bounding_box[0, 1], model.bounding_box[1, 1], nsteps[1])
205+
loop_Z = np.linspace(model.bounding_box[0, 2], model.bounding_box[1, 2], nsteps[2])
206+
207+
# Generate model values in 3d grid
208+
xx, yy, zz = np.meshgrid(loop_X, loop_Y, loop_Z, indexing='ij')
209+
# xyz is N x 3 vector array
210+
xyz = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
211+
vals = model.evaluate_model(xyz, scale=False)
212+
# Use FORTRAN style indexing for GOCAD VOXET
213+
vol_vals = np.reshape(vals, nsteps, order='F')
214+
bbox = model.bounding_box[:]
215+
216+
# Convert bounding box to real world scale coords
217+
if real_coords:
218+
model.rescale(bbox)
219+
220+
# If integer values
221+
if type(vals[0]) is np.int64:
222+
d_type = np.int8
223+
no_data_val = None
224+
prop_esize = 1
225+
prop_storage_type = "Octet"
226+
227+
# If float values
228+
elif type(vals[0]) is np.float32:
229+
d_type = np.dtype('>f4')
230+
no_data_val = -999999.0
231+
prop_esize = 4
232+
prop_storage_type = "Float"
233+
else:
234+
logger.warning("Cannot export volume to GOCAD VOXET file: Unsupported type {}".format(type(vals[0])))
235+
return False
236+
237+
# Write out VOXET file
238+
vo_filename = file_name + ".vo"
239+
data_filename = file_name + "@@"
240+
try:
241+
with open(vo_filename, "w") as fp:
242+
fp.write("""GOCAD Voxet 1
243+
HEADER {{
244+
name: {name}
245+
}}
246+
GOCAD_ORIGINAL_COORDINATE_SYSTEM
247+
NAME Default
248+
AXIS_NAME "X" "Y" "Z"
249+
AXIS_UNIT "m" "m" "m"
250+
ZPOSITIVE Elevation
251+
END_ORIGINAL_COORDINATE_SYSTEM
252+
AXIS_O 0.000000 0.000000 0.000000
253+
AXIS_U 1.000000 0.000000 0.000000
254+
AXIS_V 0.000000 1.000000 0.000000
255+
AXIS_W 0.000000 0.000000 1.000000
256+
AXIS_MIN {axismin1} {axismin2} {axismin3}
257+
AXIS_MAX {axismax1} {axismax2} {axismax3}
258+
AXIS_N {nsteps1} {nsteps2} {nsteps3}
259+
AXIS_NAME "X" "Y" "Z"
260+
AXIS_UNIT "m" "m" "m"
261+
AXIS_TYPE even even even
262+
PROPERTY 1 {propname}
263+
PROPERTY_CLASS 1 {propname}
264+
PROP_UNIT 1 {propname}
265+
PROPERTY_CLASS_HEADER 1 {propname} {{
266+
}}
267+
PROPERTY_SUBCLASS 1 QUANTITY {prop_storage_type}
268+
""".format(name=os.path.basename(file_name),
269+
nsteps1=nsteps[0], nsteps2=nsteps[1], nsteps3=nsteps[2],
270+
axismin1=bbox[0, 0], axismin2=bbox[0, 1], axismin3=bbox[0, 2],
271+
axismax1=bbox[1, 0], axismax2=bbox[1, 1], axismax3=bbox[1, 2],
272+
propname=data_label, prop_storage_type=prop_storage_type))
273+
if no_data_val is not None:
274+
fp.write("PROP_NO_DATA_VALUE 1 {no_data_val}\n".format(no_data_val=no_data_val))
275+
fp.write("""PROP_ETYPE 1 IEEE
276+
PROP_FORMAT 1 RAW
277+
PROP_ESIZE 1 {prop_esize}
278+
PROP_OFFSET 1 0
279+
PROP_FILE 1 {prop_file}
280+
END\n""".format(prop_file=data_filename, prop_esize=prop_esize))
281+
except IOError as exc:
282+
logger.warning("Cannot export volume to GOCAD VOXET file {}: {}".format(vo_filename, str(exc)))
283+
return False
284+
285+
# Write out accompanying binary data file
286+
export_vals = np.array(vol_vals, dtype=d_type)
287+
try:
288+
with open(data_filename, "wb") as fp:
289+
export_vals.tofile(fp)
290+
except IOError as exc:
291+
logger.warning("Cannot export volume to GOCAD VOXET data file {}: {}".format(data_filename, str(exc)))
292+
return False
293+
return True

LoopStructural/export/file_formats.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"""
44
from enum import Enum
55

6+
67
class FileFormat(Enum):
7-
""" Enumeration of file export formats
8+
""" Enumeration of file export formats
89
"""
9-
OBJ = 1
10+
OBJ = 1 # Not supported yet
1011
VTK = 2
11-
GZ = 3 # Not supported yet
12-
GLTF = 4 # Not supported yet
12+
GOCAD = 3
13+
GLTF = 4 # Not supported yet

0 commit comments

Comments
 (0)