Skip to content

Commit 9d5e608

Browse files
authored
Merge pull request #29 from Loop3D/map2loop
Map2loop
2 parents b6526a2 + 73f5ce7 commit 9d5e608

7 files changed

Lines changed: 311 additions & 143 deletions

File tree

.github/workflows/python-publish.yml

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
types: [created]
66

77
jobs:
8-
deploy:
8+
flake8:
99
runs-on: ubuntu-latest
1010
steps:
1111
- uses: actions/checkout@v2
@@ -16,12 +16,25 @@ jobs:
1616
- name: Install dependencies
1717
run: |
1818
python -m pip install --upgrade pip
19-
pip install twine flake8
19+
pip install flake8
2020
- name: Lint with flake8 for syntax errors
2121
run: |
2222
pip install flake8
2323
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
2424
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
25+
manylinux:
26+
runs-on: ubuntu-latest
27+
needs: flake8
28+
steps:
29+
- uses: actions/checkout@v2
30+
- name: Set up Python
31+
uses: actions/setup-python@v1
32+
with:
33+
python-version: 3.8
34+
- name: Install dependencies
35+
run: |
36+
python -m pip install --upgrade pip
37+
pip install twine
2538
- name: Build manylinux Python wheels
2639
uses: RalfG/python-wheels-manylinux-build@v0.2.2-manylinux2010_x86_64
2740
with:
@@ -36,3 +49,37 @@ jobs:
3649
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
3750
run: |
3851
twine upload wheelhouse/*-manylinux*.whl
52+
53+
build-windows:
54+
runs-on: windows-latest
55+
needs: flake8
56+
strategy:
57+
matrix:
58+
python: ['3.6','3.7','3.8']
59+
steps:
60+
- uses: actions/checkout@v2
61+
- uses: goanpeca/action-setup-conda@v1
62+
with:
63+
python-version: ${{ matrix.python }}
64+
activate-environment: loop
65+
- name: Installing dependencies
66+
shell: bash -l {0}
67+
run: |
68+
python --version
69+
pip install -r requirements.txt
70+
conda info
71+
conda list
72+
- name: Building Loop wheel and installing
73+
shell: bash -l {0}
74+
run: |
75+
python setup.py bdist_wheel
76+
python setup.py bdist
77+
- name: Publish wheels to PyPI
78+
env:
79+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
80+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
81+
shell: bash -l {0}
82+
run : |
83+
pip install twine
84+
twine upload dist/*
85+

LoopStructural/modelling/core/geological_model.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,31 @@ def from_map2loop_directory(cls, m2l_directory,**kwargs):
128128
m2l_data = process_map2loop(m2l_directory)
129129
return build_model(m2l_data,**kwargs), m2l_data
130130

131+
@classmethod
132+
def from_file(cls, file):
133+
try:
134+
import dill as pickle
135+
except ImportError:
136+
logger.error("Cannot import from file, dill not installed")
137+
return None
138+
model = pickle.load(open(file,'rb'))
139+
if type(model) == GeologicalModel:
140+
return model
141+
else:
142+
logger.error('{} does not contain a geological model'.format(file))
143+
return None
144+
145+
def to_file(self, file):
146+
try:
147+
import dill as pickle
148+
except ImportError:
149+
logger.error("Cannot write to file, dill not installed")
150+
return
151+
try:
152+
pickle.dump(self,open(file,'wb'))
153+
except pickle.PicklingError:
154+
logger.error('Error saving file')
155+
131156
def _add_feature(self, feature):
132157
"""
133158
Add a feature to the model stack
@@ -262,7 +287,7 @@ def create_from_feature_list(self, features):
262287
if featuretype == 'folded_strati':
263288
self.create_and_add_folded_foliation(f)
264289

265-
def get_interpolator(self, interpolatortype='PLI', nelements=5e5,
290+
def get_interpolator(self, interpolatortype='PLI', nelements=1e5,
266291
buffer=0.2, **kwargs):
267292
"""
268293
Returns an interpolator given the arguments, also constructs a
@@ -622,6 +647,8 @@ def _add_domain_fault_above(self, feature):
622647
623648
"""
624649
for f in reversed(self.features):
650+
if f.name == feature.name:
651+
continue
625652
if f.type == 'domain_fault':
626653
feature.add_region(lambda pos: f.evaluate_value(pos) < 0)
627654
break
@@ -642,6 +669,8 @@ def _add_domain_fault_below(self, domain_fault):
642669
643670
"""
644671
for f in reversed(self.features):
672+
if f.name == domain_fault.name:
673+
continue
645674
f.add_region(lambda pos: domain_fault.evaluate_value(pos) > 0)
646675
if f.type == 'unconformity':
647676
break
@@ -818,11 +847,14 @@ def create_and_add_domain_fault(self, fault_surface_data, **kwargs):
818847
# build feature
819848
domain_fault = domain_fault_feature_builder.build(**kwargs)
820849
domain_fault.type = 'domain_fault'
850+
self._add_feature(domain_fault)
851+
self._add_domain_fault_below(domain_fault)
852+
821853
# uc_feature = UnconformityFeature(uc_feature_base,0)
822854
# iterate over existing features and add the unconformity as a
823855
# region so the feature is only
824856
# evaluated where the unconformity is positive
825-
return self.add_unconformity(domain_fault, 0)
857+
return domain_fault
826858

827859
def create_and_add_fault(self, fault_surface_data, displacement, **kwargs):
828860
"""
@@ -988,7 +1020,7 @@ def voxet(self, nsteps=(50, 50, 25)):
9881020
"""
9891021
return {'bounding_box': self.bounding_box, 'nsteps': nsteps}
9901022

991-
def regular_grid(self, nsteps=(50, 50, 25), shuffle = True):
1023+
def regular_grid(self, nsteps=(50, 50, 25), shuffle = True, rescale=True):
9921024
"""
9931025
Return a regular grid within the model bounding box
9941026
@@ -1012,6 +1044,8 @@ def regular_grid(self, nsteps=(50, 50, 25), shuffle = True):
10121044
locs = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
10131045
if shuffle:
10141046
np.random.shuffle(locs)
1047+
if rescale:
1048+
locs = self.rescale(locs)
10151049
return locs
10161050

10171051
def evaluate_model(self, xyz, rescale=True):

LoopStructural/utils/map2loop.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ def process_map2loop(m2l_directory, flags={}):
4343
except:
4444
for g in groups['group'].unique():
4545
supergroups[g] = g
46+
supergroups.pop('\n')
47+
4648

4749

4850
bb = pd.read_csv(m2l_directory+'/tmp/bbox.csv')
@@ -85,6 +87,23 @@ def process_map2loop(m2l_directory, flags={}):
8587
unit_id += 1
8688
strat_val[c] = val[g]
8789
val[g] += thickness[c]
90+
group_name = None
91+
for g, i in stratigraphic_column.items():
92+
if len(i) ==0:
93+
for gr, sg in supergroups.items():
94+
if sg == g:
95+
group_name = gr
96+
break
97+
try:
98+
if group_name is None:
99+
continue
100+
c=groups.loc[groups['group']==group_name,'code'].to_numpy()[0]
101+
strat_val[c] = 0
102+
stratigraphic_column[g] = {c:{'min':0,'max':9999,'id':unit_id}}
103+
unit_id+=1
104+
group_name = None
105+
except:
106+
print('Couldnt process {}'.format(g))
88107
contacts['val'] = np.nan
89108
for o in strat_val:
90109
contacts.loc[contacts['formation'] == o, 'val'] = strat_val[o]
@@ -140,7 +159,7 @@ def process_map2loop(m2l_directory, flags={}):
140159
'bounding_box':bb,
141160
'strat_va':strat_val}
142161

143-
def build_model(m2l_data, skip_faults = False, fault_params = None, foliation_params=None):
162+
def build_model(m2l_data, skip_faults = False, unconformities=False, fault_params = None, foliation_params=None):
144163
"""[summary]
145164
146165
[extended_summary]
@@ -199,12 +218,12 @@ def build_model(m2l_data, skip_faults = False, fault_params = None, foliation_pa
199218

200219
## loop through all of the groups and add them to the model in youngest to oldest.
201220
group_features = []
202-
for i in m2l_data['groups']['group number'].unique():
221+
for i in np.sort(m2l_data['groups']['group number'].unique()):
203222
g = m2l_data['groups'].loc[m2l_data['groups']['group number'] == i, 'group'].unique()[0]
204223
group_features.append(model.create_and_add_foliation(g,
205224
**foliation_params))
206225
# if the group was successfully added (not null) then lets add the base (0 to be unconformity)
207-
# if group_features[-1]:
208-
# model.add_unconformity(group_features[-1], 0)
226+
if group_features[-1] and unconformities:
227+
model.add_unconformity(group_features[-1], 0)
209228
model.set_stratigraphic_column(m2l_data['stratigraphic_column'])
210229
return model

LoopStructural/visualisation/model_visualisation.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ def add_section(self, geological_feature=None, axis='x', value=None, **kwargs):
149149
geological_feature.name, geological_feature.min(), geological_feature.max()))
150150
surf.colourmap(cmap, range=[geological_feature.min(), geological_feature.max()])
151151

152-
def add_isosurface(self, geological_feature, value = None, isovalue=None, paint_with=None,
153-
slices=None, colour='red', nslices=None, cmap=None, **kwargs):
152+
def add_isosurface(self, geological_feature, value = None, isovalue=None,
153+
paint_with=None, slices=None, colour='red', nslices=None,
154+
cmap=None, filename=None, **kwargs):
154155
""" Plot the surface of a geological feature
155156
156157
[extended_summary]
@@ -173,6 +174,8 @@ def add_isosurface(self, geological_feature, value = None, isovalue=None, paint_
173174
[description], by default None
174175
cmap : [type], optional
175176
[description], by default None
177+
filename: string, optional
178+
filename for exporting
176179
177180
Returns
178181
-------
@@ -188,7 +191,7 @@ def add_isosurface(self, geological_feature, value = None, isovalue=None, paint_
188191
# do isosurfacing of support using marching tetras/cubes
189192
x = np.linspace(self.bounding_box[0, 0], self.bounding_box[1, 0], self.nsteps[0])
190193
y = np.linspace(self.bounding_box[0, 1], self.bounding_box[1, 1], self.nsteps[1])
191-
z = np.linspace(self.bounding_box[1, 2], self.bounding_box[0, 2], self.nsteps[2])
194+
z = np.linspace(self.bounding_box[0, 2], self.bounding_box[1, 2], self.nsteps[2])
192195
xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
193196
points = np.array([xx.flatten(), yy.flatten(), zz.flatten()]).T
194197
val = geological_feature.evaluate_value(points)
@@ -240,9 +243,20 @@ def add_isosurface(self, geological_feature, value = None, isovalue=None, paint_
240243
except ValueError:
241244
logger.warning("no surface to mesh, skipping")
242245
continue
246+
247+
243248
name = geological_feature.name
244249
name = kwargs.get('name', name)
245250
name += '_iso_%f' % isovalue
251+
if filename is not None:
252+
try:
253+
import meshio
254+
except ImportError:
255+
logger.error("Could not save surfaces, meshio is not installed")
256+
meshio.write_points_cells(filename.format(name),
257+
self.model.rescale(verts),
258+
[("triangle", faces)]
259+
)
246260
surf = self.lv.triangles(name)
247261
surf.vertices(verts)
248262
surf.indices(faces)
@@ -353,12 +367,12 @@ def add_model_surfaces(self, faults = True, cmap='tab20', **kwargs):
353367
if g in self.model.feature_name_index:
354368
feature = self.model.features[self.model.feature_name_index[g]]
355369
for u, vals in self.model.stratigraphic_column[g].items():
356-
self.add_isosurface(feature, isovalue=vals['max'],name=u,colour=tab.colors[ci,:])
370+
self.add_isosurface(feature, isovalue=vals['max'],name=u,colour=tab.colors[ci,:],**kwargs)
357371
ci+=1
358372
if faults:
359373
for f in self.model.features:
360374
if f.type == 'fault':
361-
self.add_isosurface(f,isovalue=0)
375+
self.add_isosurface(f,isovalue=0,**kwargs)
362376

363377

364378
def add_vector_field(self, geological_feature, **kwargs):

examples/1_basic/plot_1_data_prepration.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@
123123
# Geological feature can be evaluated:
124124
# * for the scalar field value at a location
125125
# * for the gradient of the scalar field at a location
126+
# To evaluate a model feature (scalar value or gradient) use the:
127+
# :code:`model.evaluate_feature_value(feature_name, locations)` or
128+
# :code:`model.evaluate_feature_gradient(feature_name, locations)`
129+
# Where the feature_name is the string naming the feature and locations is a numpy array of
130+
# xyz coordinates.
131+
#
132+
# In the following example we will use matplotlib to visualise these results however, the
133+
# next tutorial will show how to use the lavavu visualisation model.
126134

127135

128136
import matplotlib.pyplot as plt
@@ -135,10 +143,10 @@
135143
xx = np.zeros_like(yy)
136144
xx[:] = 5
137145

138-
vals = conformable_feature.evaluate_value(model.scale(np.array([xx.flatten(),yy.flatten(),zz.flatten()]).T))
146+
vals = model.evaluate_feature_value('conformable',np.array([xx.flatten(),yy.flatten(),zz.flatten()]).T)
139147
fig, ax = plt.subplots(1,2,figsize=(20,10))
140-
ax[0].contourf(vals.reshape((100,100)))
141-
ax[0].contour(vals.reshape((100,100)),[0,1])
148+
ax[0].contourf(vals.reshape((100,100)),extent=(0,10,0,10))
149+
ax[0].contour(vals.reshape((100,100)),[0,1],extent=(0,10,0,10))
142150

143151
# Y section
144152
x = np.linspace(0,10,100)
@@ -148,8 +156,8 @@
148156
yy = np.zeros_like(xx)
149157
yy[:] = 5
150158

151-
vals = conformable_feature.evaluate_value(model.scale(np.array([xx.flatten(),yy.flatten(),zz.flatten()]).T))
152-
ax[1].contourf(vals.reshape((100,100)))
153-
ax[1].contour(vals.reshape((100,100)),[0,1])
159+
vals = model.evaluate_feature_value('conformable',np.array([xx.flatten(),yy.flatten(),zz.flatten()]).T)
160+
ax[1].contourf(vals.reshape((100,100)),extent=(0,10,0,10))
161+
ax[1].contour(vals.reshape((100,100)),[0,1],extent=(0,10,0,10))
154162

155163
plt.show()

0 commit comments

Comments
 (0)