LoopStructural 1.6.14__py3-none-any.whl → 1.6.16__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 +30 -12
- LoopStructural/datatypes/_bounding_box.py +22 -13
- LoopStructural/datatypes/_point.py +0 -1
- LoopStructural/export/exporters.py +2 -2
- LoopStructural/interpolators/__init__.py +33 -30
- LoopStructural/interpolators/_constant_norm.py +205 -0
- LoopStructural/interpolators/_discrete_interpolator.py +15 -14
- LoopStructural/interpolators/_finite_difference_interpolator.py +10 -10
- LoopStructural/interpolators/_geological_interpolator.py +9 -3
- LoopStructural/interpolators/_interpolatortype.py +22 -0
- LoopStructural/interpolators/_p1interpolator.py +6 -2
- LoopStructural/interpolators/_surfe_wrapper.py +4 -1
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +1 -1
- LoopStructural/interpolators/supports/_2d_structured_grid.py +16 -0
- LoopStructural/interpolators/supports/_3d_base_structured.py +16 -0
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +7 -3
- LoopStructural/modelling/core/geological_model.py +250 -312
- LoopStructural/modelling/core/stratigraphic_column.py +473 -0
- LoopStructural/modelling/features/_base_geological_feature.py +38 -2
- LoopStructural/modelling/features/builders/_fault_builder.py +1 -0
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +1 -1
- LoopStructural/modelling/features/fault/_fault_segment.py +1 -1
- LoopStructural/modelling/intrusions/intrusion_builder.py +1 -1
- LoopStructural/modelling/intrusions/intrusion_frame_builder.py +1 -1
- LoopStructural/version.py +1 -1
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.dist-info}/METADATA +2 -2
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.dist-info}/RECORD +30 -27
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.dist-info}/WHEEL +0 -0
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.dist-info}/licenses/LICENSE +0 -0
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from typing import Dict
|
|
3
|
+
import numpy as np
|
|
4
|
+
from LoopStructural.utils import rng, getLogger
|
|
5
|
+
|
|
6
|
+
logger = getLogger(__name__)
|
|
7
|
+
logger.info("Imported LoopStructural Stratigraphic Column module")
|
|
8
|
+
class UnconformityType(enum.Enum):
|
|
9
|
+
"""
|
|
10
|
+
An enumeration for different types of unconformities in a stratigraphic column.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
ERODE = 'erode'
|
|
14
|
+
ONLAP = 'onlap'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StratigraphicColumnElementType(enum.Enum):
|
|
18
|
+
"""
|
|
19
|
+
An enumeration for different types of elements in a stratigraphic column.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
UNIT = 'unit'
|
|
23
|
+
UNCONFORMITY = 'unconformity'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StratigraphicColumnElement:
|
|
27
|
+
"""
|
|
28
|
+
A class to represent an element in a stratigraphic column, which can be a unit or a topological object
|
|
29
|
+
for example unconformity.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, uuid=None):
|
|
33
|
+
"""
|
|
34
|
+
Initializes the StratigraphicColumnElement with a uuid.
|
|
35
|
+
"""
|
|
36
|
+
if uuid is None:
|
|
37
|
+
import uuid as uuid_module
|
|
38
|
+
|
|
39
|
+
uuid = str(uuid_module.uuid4())
|
|
40
|
+
self.uuid = uuid
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class StratigraphicUnit(StratigraphicColumnElement):
|
|
44
|
+
"""
|
|
45
|
+
A class to represent a stratigraphic unit.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, *, uuid=None, name=None, colour=None, thickness=None, data=None):
|
|
49
|
+
"""
|
|
50
|
+
Initializes the StratigraphicUnit with a name and an optional description.
|
|
51
|
+
"""
|
|
52
|
+
super().__init__(uuid)
|
|
53
|
+
self.name = name
|
|
54
|
+
if colour is None:
|
|
55
|
+
colour = rng.random(3)
|
|
56
|
+
self.colour = colour
|
|
57
|
+
self.thickness = thickness
|
|
58
|
+
self.data = data
|
|
59
|
+
self.element_type = StratigraphicColumnElementType.UNIT
|
|
60
|
+
|
|
61
|
+
def to_dict(self):
|
|
62
|
+
"""
|
|
63
|
+
Converts the stratigraphic unit to a dictionary representation.
|
|
64
|
+
"""
|
|
65
|
+
colour = self.colour
|
|
66
|
+
if isinstance(colour, np.ndarray):
|
|
67
|
+
colour = colour.astype(float).tolist()
|
|
68
|
+
return {"name": self.name, "colour": colour, "thickness": self.thickness, 'uuid': self.uuid}
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_dict(cls, data):
|
|
72
|
+
"""
|
|
73
|
+
Creates a StratigraphicUnit from a dictionary representation.
|
|
74
|
+
"""
|
|
75
|
+
if not isinstance(data, dict):
|
|
76
|
+
raise TypeError("Data must be a dictionary")
|
|
77
|
+
name = data.get("name")
|
|
78
|
+
colour = data.get("colour")
|
|
79
|
+
thickness = data.get("thickness", None)
|
|
80
|
+
uuid = data.get("uuid", None)
|
|
81
|
+
return cls(uuid=uuid, name=name, colour=colour, thickness=thickness)
|
|
82
|
+
|
|
83
|
+
def __str__(self):
|
|
84
|
+
"""
|
|
85
|
+
Returns a string representation of the stratigraphic unit.
|
|
86
|
+
"""
|
|
87
|
+
return (
|
|
88
|
+
f"StratigraphicUnit(name={self.name}, colour={self.colour}, thickness={self.thickness})"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class StratigraphicUnconformity(StratigraphicColumnElement):
|
|
93
|
+
"""
|
|
94
|
+
A class to represent a stratigraphic unconformity, which is a surface of discontinuity in the stratigraphic record.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self, *, uuid=None, name=None, unconformity_type: UnconformityType = UnconformityType.ERODE
|
|
99
|
+
):
|
|
100
|
+
"""
|
|
101
|
+
Initializes the StratigraphicUnconformity with a name and an optional description.
|
|
102
|
+
"""
|
|
103
|
+
super().__init__(uuid)
|
|
104
|
+
|
|
105
|
+
self.name = name
|
|
106
|
+
if unconformity_type not in [UnconformityType.ERODE, UnconformityType.ONLAP]:
|
|
107
|
+
raise ValueError("Invalid unconformity type")
|
|
108
|
+
self.unconformity_type = unconformity_type
|
|
109
|
+
self.element_type = StratigraphicColumnElementType.UNCONFORMITY
|
|
110
|
+
|
|
111
|
+
def to_dict(self):
|
|
112
|
+
"""
|
|
113
|
+
Converts the stratigraphic unconformity to a dictionary representation.
|
|
114
|
+
"""
|
|
115
|
+
return {
|
|
116
|
+
"uuid": self.uuid,
|
|
117
|
+
"name": self.name,
|
|
118
|
+
"unconformity_type": self.unconformity_type.value,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
def __str__(self):
|
|
122
|
+
"""
|
|
123
|
+
Returns a string representation of the stratigraphic unconformity.
|
|
124
|
+
"""
|
|
125
|
+
return (
|
|
126
|
+
f"StratigraphicUnconformity(name={self.name}, "
|
|
127
|
+
f"unconformity_type={self.unconformity_type.value})"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def from_dict(cls, data):
|
|
132
|
+
"""
|
|
133
|
+
Creates a StratigraphicUnconformity from a dictionary representation.
|
|
134
|
+
"""
|
|
135
|
+
if not isinstance(data, dict):
|
|
136
|
+
raise TypeError("Data must be a dictionary")
|
|
137
|
+
name = data.get("name")
|
|
138
|
+
unconformity_type = UnconformityType(
|
|
139
|
+
data.get("unconformity_type", UnconformityType.ERODE.value)
|
|
140
|
+
)
|
|
141
|
+
uuid = data.get("uuid", None)
|
|
142
|
+
return cls(uuid=uuid, name=name, unconformity_type=unconformity_type)
|
|
143
|
+
class StratigraphicGroup:
|
|
144
|
+
"""
|
|
145
|
+
A class to represent a group of stratigraphic units.
|
|
146
|
+
This class is not fully implemented and serves as a placeholder for future development.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, name=None, units=None):
|
|
150
|
+
"""
|
|
151
|
+
Initializes the StratigraphicGroup with a name and an optional list of units.
|
|
152
|
+
"""
|
|
153
|
+
self.name = name
|
|
154
|
+
self.units = units if units is not None else []
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class StratigraphicColumn:
|
|
158
|
+
"""
|
|
159
|
+
A class to represent a stratigraphic column, which is a vertical section of the Earth's crust
|
|
160
|
+
showing the sequence of rock layers and their relationships.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(self):
|
|
164
|
+
"""
|
|
165
|
+
Initializes the StratigraphicColumn with a name and a list of layers.
|
|
166
|
+
"""
|
|
167
|
+
self.order = [StratigraphicUnit(name='Basement', colour='grey', thickness=np.inf),StratigraphicUnconformity(name='Base Unconformity', unconformity_type=UnconformityType.ERODE)]
|
|
168
|
+
self.group_mapping = {}
|
|
169
|
+
def clear(self,basement=True):
|
|
170
|
+
"""
|
|
171
|
+
Clears the stratigraphic column, removing all elements.
|
|
172
|
+
"""
|
|
173
|
+
if basement:
|
|
174
|
+
self.order = [StratigraphicUnit(name='Basement', colour='grey', thickness=np.inf),StratigraphicUnconformity(name='Base Unconformity', unconformity_type=UnconformityType.ERODE)]
|
|
175
|
+
else:
|
|
176
|
+
self.order = []
|
|
177
|
+
self.group_mapping = {}
|
|
178
|
+
|
|
179
|
+
def add_unit(self, name,*, colour=None, thickness=None, where='top'):
|
|
180
|
+
unit = StratigraphicUnit(name=name, colour=colour, thickness=thickness)
|
|
181
|
+
|
|
182
|
+
if where == 'top':
|
|
183
|
+
self.order.append(unit)
|
|
184
|
+
elif where == 'bottom':
|
|
185
|
+
self.order.insert(0, unit)
|
|
186
|
+
else:
|
|
187
|
+
raise ValueError("Invalid 'where' argument. Use 'top' or 'bottom'.")
|
|
188
|
+
|
|
189
|
+
return unit
|
|
190
|
+
|
|
191
|
+
def remove_unit(self, uuid):
|
|
192
|
+
"""
|
|
193
|
+
Removes a unit or unconformity from the stratigraphic column by its uuid.
|
|
194
|
+
"""
|
|
195
|
+
for i, element in enumerate(self.order):
|
|
196
|
+
if element.uuid == uuid:
|
|
197
|
+
del self.order[i]
|
|
198
|
+
return True
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
def add_unconformity(self, name, *, unconformity_type=UnconformityType.ERODE, where='top' ):
|
|
202
|
+
unconformity = StratigraphicUnconformity(
|
|
203
|
+
uuid=None, name=name, unconformity_type=unconformity_type
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if where == 'top':
|
|
207
|
+
self.order.append(unconformity)
|
|
208
|
+
elif where == 'bottom':
|
|
209
|
+
self.order.insert(0, unconformity)
|
|
210
|
+
else:
|
|
211
|
+
raise ValueError("Invalid 'where' argument. Use 'top' or 'bottom'.")
|
|
212
|
+
return unconformity
|
|
213
|
+
|
|
214
|
+
def get_element_by_index(self, index):
|
|
215
|
+
"""
|
|
216
|
+
Retrieves an element by its index from the stratigraphic column.
|
|
217
|
+
"""
|
|
218
|
+
if index < 0 or index >= len(self.order):
|
|
219
|
+
raise IndexError("Index out of range")
|
|
220
|
+
return self.order[index]
|
|
221
|
+
|
|
222
|
+
def get_unit_by_name(self, name):
|
|
223
|
+
"""
|
|
224
|
+
Retrieves a unit by its name from the stratigraphic column.
|
|
225
|
+
"""
|
|
226
|
+
for unit in self.order:
|
|
227
|
+
if isinstance(unit, StratigraphicUnit) and unit.name == name:
|
|
228
|
+
return unit
|
|
229
|
+
|
|
230
|
+
return None
|
|
231
|
+
def get_unconformity_by_name(self, name):
|
|
232
|
+
"""
|
|
233
|
+
Retrieves an unconformity by its name from the stratigraphic column.
|
|
234
|
+
"""
|
|
235
|
+
for unconformity in self.order:
|
|
236
|
+
if isinstance(unconformity, StratigraphicUnconformity) and unconformity.name == name:
|
|
237
|
+
return unconformity
|
|
238
|
+
|
|
239
|
+
return None
|
|
240
|
+
def get_element_by_uuid(self, uuid):
|
|
241
|
+
"""
|
|
242
|
+
Retrieves an element by its uuid from the stratigraphic column.
|
|
243
|
+
"""
|
|
244
|
+
for element in self.order:
|
|
245
|
+
if element.uuid == uuid:
|
|
246
|
+
return element
|
|
247
|
+
raise KeyError(f"No element found with uuid: {uuid}")
|
|
248
|
+
def add_element(self, element):
|
|
249
|
+
"""
|
|
250
|
+
Adds a StratigraphicColumnElement to the stratigraphic column.
|
|
251
|
+
"""
|
|
252
|
+
if isinstance(element, StratigraphicColumnElement):
|
|
253
|
+
self.order.append(element)
|
|
254
|
+
else:
|
|
255
|
+
raise TypeError("Element must be an instance of StratigraphicColumnElement")
|
|
256
|
+
|
|
257
|
+
def get_elements(self):
|
|
258
|
+
"""
|
|
259
|
+
Returns a list of all elements in the stratigraphic column.
|
|
260
|
+
"""
|
|
261
|
+
return self.order
|
|
262
|
+
|
|
263
|
+
def get_groups(self):
|
|
264
|
+
groups = []
|
|
265
|
+
i=0
|
|
266
|
+
group = StratigraphicGroup(
|
|
267
|
+
name=(
|
|
268
|
+
f'Group_{i}'
|
|
269
|
+
if f'Group_{i}' not in self.group_mapping
|
|
270
|
+
else self.group_mapping[f'Group_{i}']
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
for e in reversed(self.order):
|
|
274
|
+
if isinstance(e, StratigraphicUnit):
|
|
275
|
+
group.units.append(e)
|
|
276
|
+
else:
|
|
277
|
+
if group.units:
|
|
278
|
+
groups.append(group)
|
|
279
|
+
i+=1
|
|
280
|
+
group = StratigraphicGroup(
|
|
281
|
+
name=(
|
|
282
|
+
f'Group_{i}'
|
|
283
|
+
if f'Group_{i}' not in self.group_mapping
|
|
284
|
+
else self.group_mapping[f'Group_{i}']
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
if group:
|
|
288
|
+
groups.append(group)
|
|
289
|
+
return groups
|
|
290
|
+
|
|
291
|
+
def get_unitname_groups(self):
|
|
292
|
+
groups = self.get_groups()
|
|
293
|
+
groups_list = []
|
|
294
|
+
group = []
|
|
295
|
+
for g in groups:
|
|
296
|
+
group = [u.name for u in g.units if isinstance(u, StratigraphicUnit)]
|
|
297
|
+
groups_list.append(group)
|
|
298
|
+
return groups_list
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def __getitem__(self, uuid):
|
|
302
|
+
"""
|
|
303
|
+
Retrieves an element by its uuid from the stratigraphic column.
|
|
304
|
+
"""
|
|
305
|
+
for element in self.order:
|
|
306
|
+
if element.uuid == uuid:
|
|
307
|
+
return element
|
|
308
|
+
raise KeyError(f"No element found with uuid: {uuid}")
|
|
309
|
+
|
|
310
|
+
def update_order(self, new_order):
|
|
311
|
+
"""
|
|
312
|
+
Updates the order of elements in the stratigraphic column based on a new order list.
|
|
313
|
+
"""
|
|
314
|
+
if not isinstance(new_order, list):
|
|
315
|
+
raise TypeError("New order must be a list")
|
|
316
|
+
self.order = [
|
|
317
|
+
self.__getitem__(uuid) for uuid in new_order if self.__getitem__(uuid) is not None
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
def update_element(self, unit_data: Dict):
|
|
321
|
+
"""
|
|
322
|
+
Updates an existing element in the stratigraphic column with new data.
|
|
323
|
+
:param unit_data: A dictionary containing the updated data for the element.
|
|
324
|
+
"""
|
|
325
|
+
if not isinstance(unit_data, dict):
|
|
326
|
+
raise TypeError("unit_data must be a dictionary")
|
|
327
|
+
element = self.__getitem__(unit_data['uuid'])
|
|
328
|
+
if isinstance(element, StratigraphicUnit):
|
|
329
|
+
element.name = unit_data.get('name', element.name)
|
|
330
|
+
element.colour = unit_data.get('colour', element.colour)
|
|
331
|
+
element.thickness = unit_data.get('thickness', element.thickness)
|
|
332
|
+
elif isinstance(element, StratigraphicUnconformity):
|
|
333
|
+
element.name = unit_data.get('name', element.name)
|
|
334
|
+
element.unconformity_type = UnconformityType(
|
|
335
|
+
unit_data.get('unconformity_type', element.unconformity_type.value)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def __str__(self):
|
|
339
|
+
"""
|
|
340
|
+
Returns a string representation of the stratigraphic column, listing all elements.
|
|
341
|
+
"""
|
|
342
|
+
return "\n".join([f"{i+1}. {element}" for i, element in enumerate(self.order)])
|
|
343
|
+
|
|
344
|
+
def to_dict(self):
|
|
345
|
+
"""
|
|
346
|
+
Converts the stratigraphic column to a dictionary representation.
|
|
347
|
+
"""
|
|
348
|
+
return {
|
|
349
|
+
"elements": [element.to_dict() for element in self.order],
|
|
350
|
+
}
|
|
351
|
+
def update_from_dict(self, data):
|
|
352
|
+
"""
|
|
353
|
+
Updates the stratigraphic column from a dictionary representation.
|
|
354
|
+
"""
|
|
355
|
+
if not isinstance(data, dict):
|
|
356
|
+
raise TypeError("Data must be a dictionary")
|
|
357
|
+
self.clear(basement=False)
|
|
358
|
+
elements_data = data.get("elements", [])
|
|
359
|
+
for element_data in elements_data:
|
|
360
|
+
if "unconformity_type" in element_data:
|
|
361
|
+
element = StratigraphicUnconformity.from_dict(element_data)
|
|
362
|
+
else:
|
|
363
|
+
element = StratigraphicUnit.from_dict(element_data)
|
|
364
|
+
self.add_element(element)
|
|
365
|
+
@classmethod
|
|
366
|
+
def from_dict(cls, data):
|
|
367
|
+
"""
|
|
368
|
+
Creates a StratigraphicColumn from a dictionary representation.
|
|
369
|
+
"""
|
|
370
|
+
if not isinstance(data, dict):
|
|
371
|
+
raise TypeError("Data must be a dictionary")
|
|
372
|
+
column = cls()
|
|
373
|
+
column.clear(basement=False)
|
|
374
|
+
elements_data = data.get("elements", [])
|
|
375
|
+
for element_data in elements_data:
|
|
376
|
+
if "unconformity_type" in element_data:
|
|
377
|
+
element = StratigraphicUnconformity.from_dict(element_data)
|
|
378
|
+
else:
|
|
379
|
+
element = StratigraphicUnit.from_dict(element_data)
|
|
380
|
+
column.add_element(element)
|
|
381
|
+
return column
|
|
382
|
+
|
|
383
|
+
def get_isovalues(self) -> Dict[str, float]:
|
|
384
|
+
"""
|
|
385
|
+
Returns a dictionary of isovalues for the stratigraphic units in the column.
|
|
386
|
+
"""
|
|
387
|
+
surface_values = {}
|
|
388
|
+
for g in reversed(self.get_groups()):
|
|
389
|
+
v = 0
|
|
390
|
+
for u in g.units:
|
|
391
|
+
surface_values[u.name] = {'value':v,'group':g.name,'colour':u.colour}
|
|
392
|
+
v += u.thickness
|
|
393
|
+
return surface_values
|
|
394
|
+
|
|
395
|
+
def plot(self,*, ax=None, **kwargs):
|
|
396
|
+
import matplotlib.pyplot as plt
|
|
397
|
+
from matplotlib import cm
|
|
398
|
+
from matplotlib.patches import Polygon
|
|
399
|
+
from matplotlib.collections import PatchCollection
|
|
400
|
+
n_units = 0 # count how many discrete colours (number of stratigraphic units)
|
|
401
|
+
xmin = 0
|
|
402
|
+
ymin = 0
|
|
403
|
+
ymax = 1
|
|
404
|
+
xmax = 1
|
|
405
|
+
fig = None
|
|
406
|
+
if ax is None:
|
|
407
|
+
fig, ax = plt.subplots(figsize=(2, 10))
|
|
408
|
+
patches = [] # stores the individual stratigraphic unit polygons
|
|
409
|
+
|
|
410
|
+
total_height = 0
|
|
411
|
+
prev_coords = [0, 0]
|
|
412
|
+
|
|
413
|
+
# iterate through groups, skipping faults
|
|
414
|
+
for u in reversed(self.order):
|
|
415
|
+
if u.element_type == StratigraphicColumnElementType.UNCONFORMITY:
|
|
416
|
+
logger.info(f"Plotting unconformity {u.name} of type {u.unconformity_type.value}")
|
|
417
|
+
ax.axhline(y=total_height, linestyle='--', color='black')
|
|
418
|
+
ax.annotate(
|
|
419
|
+
getattr(u, 'name', 'Unconformity'),
|
|
420
|
+
xy=(xmin, total_height),
|
|
421
|
+
fontsize=8,
|
|
422
|
+
ha='left',
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
total_height -= 0.05 # Adjust height slightly for visual separation
|
|
426
|
+
continue
|
|
427
|
+
|
|
428
|
+
if u.element_type == StratigraphicColumnElementType.UNIT:
|
|
429
|
+
logger.info(f"Plotting unit {u.name} of type {u.element_type}")
|
|
430
|
+
|
|
431
|
+
n_units += 1
|
|
432
|
+
|
|
433
|
+
ymax = total_height
|
|
434
|
+
ymin = ymax - (getattr(u, 'thickness', np.nan) if not np.isinf(getattr(u, 'thickness', np.nan)) else np.nanmean([getattr(e, 'thickness', np.nan) for e in self.order if not np.isinf(getattr(e, 'thickness', np.nan))]))
|
|
435
|
+
|
|
436
|
+
if not np.isfinite(ymin):
|
|
437
|
+
ymin = prev_coords[1] - (prev_coords[1] - prev_coords[0]) * (1 + rng.random())
|
|
438
|
+
|
|
439
|
+
total_height = ymin
|
|
440
|
+
|
|
441
|
+
prev_coords = (ymin, ymax)
|
|
442
|
+
|
|
443
|
+
polygon_points = np.array([[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]])
|
|
444
|
+
patches.append(Polygon(polygon_points))
|
|
445
|
+
ax.annotate(getattr(u, 'name', 'Unknown'), xy=(xmin+(xmax-xmin)/2, (ymax-ymin)/2+ymin), fontsize=8, ha='left')
|
|
446
|
+
|
|
447
|
+
if 'cmap' not in kwargs:
|
|
448
|
+
import matplotlib.colors as colors
|
|
449
|
+
|
|
450
|
+
colours = []
|
|
451
|
+
boundaries = []
|
|
452
|
+
data = []
|
|
453
|
+
for i, u in enumerate(self.order):
|
|
454
|
+
if u.element_type != StratigraphicColumnElementType.UNIT:
|
|
455
|
+
continue
|
|
456
|
+
data.append((i, u.colour))
|
|
457
|
+
colours.append(u.colour)
|
|
458
|
+
boundaries.append(i) # print(u,v)
|
|
459
|
+
cmap = colors.ListedColormap(colours)
|
|
460
|
+
else:
|
|
461
|
+
cmap = cm.get_cmap(kwargs['cmap'], n_units - 1)
|
|
462
|
+
p = PatchCollection(patches, cmap=cmap)
|
|
463
|
+
|
|
464
|
+
colors = np.arange(len(patches))
|
|
465
|
+
p.set_array(np.array(colors))
|
|
466
|
+
|
|
467
|
+
ax.add_collection(p)
|
|
468
|
+
|
|
469
|
+
ax.set_ylim(total_height - (total_height - prev_coords[0]) * 0.1, 0)
|
|
470
|
+
|
|
471
|
+
ax.axis("off")
|
|
472
|
+
|
|
473
|
+
return fig
|
|
@@ -244,7 +244,7 @@ class BaseFeature(metaclass=ABCMeta):
|
|
|
244
244
|
if self.model is None:
|
|
245
245
|
return 0
|
|
246
246
|
|
|
247
|
-
return np.nanmin(self.evaluate_value(self.model.regular_grid((10, 10, 10))))
|
|
247
|
+
return np.nanmin(self.evaluate_value(self.model.regular_grid(nsteps=(10, 10, 10))))
|
|
248
248
|
|
|
249
249
|
def max(self):
|
|
250
250
|
"""Calculate the maximum value of the geological feature
|
|
@@ -257,7 +257,7 @@ class BaseFeature(metaclass=ABCMeta):
|
|
|
257
257
|
"""
|
|
258
258
|
if self.model is None:
|
|
259
259
|
return 0
|
|
260
|
-
return np.nanmax(self.evaluate_value(self.model.regular_grid((10, 10, 10))))
|
|
260
|
+
return np.nanmax(self.evaluate_value(self.model.regular_grid(nsteps=(10, 10, 10))))
|
|
261
261
|
|
|
262
262
|
def __tojson__(self):
|
|
263
263
|
regions = [r.name for r in self.regions]
|
|
@@ -347,6 +347,42 @@ class BaseFeature(metaclass=ABCMeta):
|
|
|
347
347
|
grid.cell_properties[self.name] = value
|
|
348
348
|
return grid
|
|
349
349
|
|
|
350
|
+
def gradient_norm_scalar_field(self, bounding_box=None):
|
|
351
|
+
"""Create a scalar field for the gradient norm of the feature
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
bounding_box : Optional[BoundingBox], optional
|
|
356
|
+
bounding box to evaluate the scalar field in, by default None
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
np.ndarray
|
|
361
|
+
scalar field of the gradient norm
|
|
362
|
+
"""
|
|
363
|
+
if bounding_box is None:
|
|
364
|
+
if self.model is None:
|
|
365
|
+
raise ValueError("Must specify bounding box")
|
|
366
|
+
bounding_box = self.model.bounding_box
|
|
367
|
+
grid = bounding_box.structured_grid(name=self.name)
|
|
368
|
+
value = np.linalg.norm(
|
|
369
|
+
self.evaluate_gradient(bounding_box.regular_grid(local=False, order='F')),
|
|
370
|
+
axis=1,
|
|
371
|
+
)
|
|
372
|
+
if self.model is not None:
|
|
373
|
+
value = np.linalg.norm(
|
|
374
|
+
self.evaluate_gradient(
|
|
375
|
+
self.model.scale(bounding_box.regular_grid(local=False, order='F'))
|
|
376
|
+
),
|
|
377
|
+
axis=1,
|
|
378
|
+
)
|
|
379
|
+
grid.properties[self.name] = value
|
|
380
|
+
|
|
381
|
+
value = np.linalg.norm(
|
|
382
|
+
self.evaluate_gradient(bounding_box.cell_centres(order='F')), axis=1
|
|
383
|
+
)
|
|
384
|
+
grid.cell_properties[self.name] = value
|
|
385
|
+
return grid
|
|
350
386
|
def vector_field(self, bounding_box=None, tolerance=0.05, scale=1.0):
|
|
351
387
|
"""Create a vector field for the feature
|
|
352
388
|
|
|
@@ -150,6 +150,7 @@ class FaultBuilder(StructuralFrameBuilder):
|
|
|
150
150
|
np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["val"] == 0),
|
|
151
151
|
["X", "Y"],
|
|
152
152
|
].to_numpy()
|
|
153
|
+
self.fault_dip = fault_dip
|
|
153
154
|
if fault_normal_vector is None:
|
|
154
155
|
if fault_frame_data.loc[
|
|
155
156
|
np.logical_and(fault_frame_data["coord"] == 0, fault_frame_data["nx"].notna())].shape[0]>0:
|
|
@@ -452,7 +452,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
452
452
|
self.interpolator.support.rotation_xy = rotation
|
|
453
453
|
self._up_to_date = False
|
|
454
454
|
|
|
455
|
-
while self.interpolator.
|
|
455
|
+
while self.interpolator.dof < 100:
|
|
456
456
|
self.interpolator.support.step_vector = self.interpolator.support.step_vector * 0.9
|
|
457
457
|
|
|
458
458
|
def check_interpolation_geometry(self, data, buffer=0.3):
|
|
@@ -82,7 +82,7 @@ class IntrusionBuilder(BaseBuilder):
|
|
|
82
82
|
if spacing is None:
|
|
83
83
|
spacing = self.model.nsteps
|
|
84
84
|
|
|
85
|
-
grid_points = self.model.regular_grid(spacing, shuffle=False)
|
|
85
|
+
grid_points = self.model.regular_grid(nsteps=spacing, shuffle=False)
|
|
86
86
|
|
|
87
87
|
grid_points_coord0 = self.intrusion_frame[0].evaluate_value(grid_points)
|
|
88
88
|
|
|
@@ -168,7 +168,7 @@ class IntrusionFrameBuilder(StructuralFrameBuilder):
|
|
|
168
168
|
if spacing is None:
|
|
169
169
|
spacing = self.model.nsteps
|
|
170
170
|
|
|
171
|
-
grid_points = self.model.regular_grid(spacing, shuffle=False)
|
|
171
|
+
grid_points = self.model.regular_grid(nsteps=spacing, shuffle=False)
|
|
172
172
|
|
|
173
173
|
self.grid_to_evaluate_ifx = grid_points
|
|
174
174
|
|
LoopStructural/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.6.
|
|
1
|
+
__version__ = "1.6.16"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: LoopStructural
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.16
|
|
4
4
|
Summary: 3D geological modelling
|
|
5
5
|
Author-email: Lachlan Grose <lachlan.grose@monash.edu>
|
|
6
6
|
License: MIT
|
|
@@ -34,7 +34,7 @@ Requires-Dist: tqdm; extra == "all"
|
|
|
34
34
|
Provides-Extra: visualisation
|
|
35
35
|
Requires-Dist: matplotlib; extra == "visualisation"
|
|
36
36
|
Requires-Dist: pyvista; extra == "visualisation"
|
|
37
|
-
Requires-Dist:
|
|
37
|
+
Requires-Dist: loopstructuralvisualisation>=0.1.14; extra == "visualisation"
|
|
38
38
|
Provides-Extra: export
|
|
39
39
|
Requires-Dist: geoh5py; extra == "export"
|
|
40
40
|
Requires-Dist: pyevtk; extra == "export"
|