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,460 @@
|
|
|
1
|
+
## Support Functions for intrusion network simulated as the shortest path, and for simulations in general
|
|
2
|
+
import numpy as np
|
|
3
|
+
from ...utils import getLogger
|
|
4
|
+
|
|
5
|
+
logger = getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def sort_2_arrays(main_array, array):
|
|
9
|
+
"""
|
|
10
|
+
Sort two arrays, considering values of only the main array
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
main array: numpy array, considered to sort secondary array
|
|
15
|
+
array: numpy aray, array to be sorted
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
sorted arrays
|
|
20
|
+
"""
|
|
21
|
+
# function to sort 2 arrays, considering values of only the main array
|
|
22
|
+
|
|
23
|
+
for i in range(len(main_array)):
|
|
24
|
+
swap = i + np.argmin(main_array[i:])
|
|
25
|
+
(main_array[i], main_array[swap]) = (main_array[swap], main_array[i])
|
|
26
|
+
(array[i], array[swap]) = (array[swap], array[i])
|
|
27
|
+
|
|
28
|
+
return main_array, array
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def findMinDiff(arr, n):
|
|
32
|
+
"""
|
|
33
|
+
Find the min diff by comparing difference of all possible pairs in given array
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
arr: numpy array with values to compare and fin the minimum difference
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
minimum difference between values in arr
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
# Initialize difference as infinite
|
|
45
|
+
diff = 10**20
|
|
46
|
+
|
|
47
|
+
for i in range(n - 1):
|
|
48
|
+
for j in range(i + 1, n):
|
|
49
|
+
if abs(arr[i] - arr[j]) < diff:
|
|
50
|
+
diff = abs(arr[i] - arr[j])
|
|
51
|
+
|
|
52
|
+
return diff
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def array_from_coords(df, section_axis, df_axis):
|
|
56
|
+
"""
|
|
57
|
+
Create numpy array representing a section of the model
|
|
58
|
+
from a dataframe containing coordinates and values
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
df: pandas dataframe, should have at least ['X', 'Y', 'Z', 'val_1'] columns
|
|
63
|
+
section_axis: string 'X' or 'Y', the cross section represented by the arrays is along the section_axis
|
|
64
|
+
df_axis: number of the column where the value on interest is
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
array: numpy array, contains values (e.g. velocities at each point, scalar field value at each point, etc)
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if section_axis == "X":
|
|
73
|
+
other_axis = "Y"
|
|
74
|
+
elif section_axis == "Y":
|
|
75
|
+
other_axis = "X"
|
|
76
|
+
|
|
77
|
+
col = len(df.columns)
|
|
78
|
+
if col < df_axis:
|
|
79
|
+
logger.error("Finding shortest path, dataframe axis out of range")
|
|
80
|
+
|
|
81
|
+
else:
|
|
82
|
+
df.sort_values([other_axis, "Z"], ascending=[True, False], inplace=True)
|
|
83
|
+
xys = df[other_axis].unique()
|
|
84
|
+
zs = df["Z"].unique()
|
|
85
|
+
rows = len(zs)
|
|
86
|
+
columns = len(xys)
|
|
87
|
+
array = np.zeros([rows, columns])
|
|
88
|
+
n = 0
|
|
89
|
+
for j in range(columns):
|
|
90
|
+
for i in range(rows):
|
|
91
|
+
array[i, j] = df.iloc[i + n, df_axis]
|
|
92
|
+
n = n + rows
|
|
93
|
+
return array
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def find_inout_points(velocity_field_array, velocity_parameters):
|
|
97
|
+
"""
|
|
98
|
+
Looks for the indexes of the inlet and outle in an array.
|
|
99
|
+
Velocity parameters of anisotropies are used to find the indexes of inlet and outlet.
|
|
100
|
+
It is assumed that velocity_parameter[0] correspond to the inlet anisotropy and
|
|
101
|
+
velocity_parameter[len(velocity_parameter)-1] corresponds to the outlet anisotropy
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
velocity_field_array: numpy array, containing values of the velocity field used to find the shortest path
|
|
106
|
+
velocity_parameters: list of numbers, each value correspond to a velocity assign to an anisotropy involved in intrusion emplacement
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
inlet: list of indexes, [row index in array, column index in array]
|
|
111
|
+
outlet: list of indexes, [row index in array, column index in array]
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
inlet_point = [0, 0]
|
|
115
|
+
outlet_point = [0, 0]
|
|
116
|
+
|
|
117
|
+
inlet_velocity = velocity_parameters[0] + 0.1
|
|
118
|
+
outlet_velocity = velocity_parameters[len(velocity_parameters) - 1] + 0.1
|
|
119
|
+
|
|
120
|
+
k = 0
|
|
121
|
+
for i in range(len(velocity_field_array[0])):
|
|
122
|
+
if k == 1:
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
where_inlet_i = np.where(velocity_field_array[:, i] == inlet_velocity)
|
|
126
|
+
|
|
127
|
+
if len(where_inlet_i[0]) > 0:
|
|
128
|
+
inlet_point[0] = where_inlet_i[0][len(where_inlet_i[0]) - 1]
|
|
129
|
+
inlet_point[1] = i
|
|
130
|
+
k = 1
|
|
131
|
+
else:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
k = 0
|
|
135
|
+
for i in range(len(velocity_field_array[0])):
|
|
136
|
+
i_ = len(velocity_field_array[0]) - 1 - i
|
|
137
|
+
if k == 1:
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
where_outlet_i = np.where(velocity_field_array[:, i_] == outlet_velocity)
|
|
141
|
+
if len(where_outlet_i[0]) > 0:
|
|
142
|
+
outlet_point[0] = where_outlet_i[0][0]
|
|
143
|
+
outlet_point[1] = i_
|
|
144
|
+
k = 1
|
|
145
|
+
else:
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
return inlet_point, outlet_point
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def shortest_path(inlet, outlet, time_map):
|
|
152
|
+
"""
|
|
153
|
+
Look for the shortest path between inlet and outlet, using a time map.
|
|
154
|
+
In practice, for a given point looks for the neighbour elements which is closer in time.
|
|
155
|
+
The search starts in the inlet, until it reaches the outlet.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
inlet: list of indexes (row and column) of inlet point.
|
|
160
|
+
outlet: list of indexes (row and column) of outlet point.
|
|
161
|
+
time_map: numpy array, contains values of time, computed using the fast-marching method.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
inet: (intrusion network) numpy array of same shape of time_map array.
|
|
166
|
+
0 where the intrusion network is found, 1 above it, and -1 below it
|
|
167
|
+
|
|
168
|
+
"""
|
|
169
|
+
inet = np.ones_like(time_map) # array to save shortest path with zeros
|
|
170
|
+
temp_inlet = inlet # temporary inlet
|
|
171
|
+
inet[temp_inlet[0], temp_inlet[1]] = 0
|
|
172
|
+
i = 0
|
|
173
|
+
|
|
174
|
+
while True:
|
|
175
|
+
i = i + 1
|
|
176
|
+
|
|
177
|
+
neighbors = element_neighbour(
|
|
178
|
+
temp_inlet, time_map, inet
|
|
179
|
+
) # identify neighbours elements of temporary outlet
|
|
180
|
+
direction = index_min(neighbors) # obtain the location (index min) of minimun difference
|
|
181
|
+
|
|
182
|
+
if direction == 10:
|
|
183
|
+
break
|
|
184
|
+
|
|
185
|
+
temp_inlet = new_inlet(temp_inlet, direction)
|
|
186
|
+
row = temp_inlet[0]
|
|
187
|
+
col = temp_inlet[1]
|
|
188
|
+
|
|
189
|
+
# if row >= n_rows or col >= n_cols:
|
|
190
|
+
# break
|
|
191
|
+
# else:
|
|
192
|
+
inet[row, col] = 0
|
|
193
|
+
|
|
194
|
+
if temp_inlet[0] == outlet[0] and temp_inlet[1] == outlet[1]:
|
|
195
|
+
break
|
|
196
|
+
else:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
# Assing -1 to points below intrusion network
|
|
200
|
+
for j in range(len(inet[0])): # columns
|
|
201
|
+
for h in range(len(inet)): # rows
|
|
202
|
+
if inet[h, j] == 0:
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
inet[(h + 1) :, j] = -1
|
|
206
|
+
|
|
207
|
+
return inet
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def element_neighbour(index, array, inet):
|
|
211
|
+
"""
|
|
212
|
+
Identify the value of neighbours elements for a given element.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
index: list of indexes (row and column) of elements.
|
|
217
|
+
array: numpy array, containing values
|
|
218
|
+
inet: numpy array, temporal intrusion network. If one of the elements is already inet=0, the assign -1.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
values: numpy array, 1x5 with the values of the neighbours of a particular element
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
rows = len(array) - 1 # max index of rows of time_map array
|
|
227
|
+
cols = len(array[0]) - 1 # max index of columns of time_map arrays
|
|
228
|
+
values = np.zeros(
|
|
229
|
+
8
|
|
230
|
+
) # 8 - array to save values (element above, element to the left, element to the right)
|
|
231
|
+
# values[8] = 10
|
|
232
|
+
index_row = index[0]
|
|
233
|
+
index_col = index[1]
|
|
234
|
+
|
|
235
|
+
if index_row == 0:
|
|
236
|
+
values[0] = -1
|
|
237
|
+
values[1] = -1
|
|
238
|
+
values[2] = -1
|
|
239
|
+
|
|
240
|
+
if index_row == rows:
|
|
241
|
+
values[5] = -1
|
|
242
|
+
values[6] = -1
|
|
243
|
+
values[7] = -1
|
|
244
|
+
|
|
245
|
+
if index_col == 0:
|
|
246
|
+
values[0] = -1
|
|
247
|
+
values[3] = -1
|
|
248
|
+
values[5] = -1
|
|
249
|
+
|
|
250
|
+
if index_col == cols:
|
|
251
|
+
values[2] = -1
|
|
252
|
+
values[4] = -1
|
|
253
|
+
values[7] = -1
|
|
254
|
+
|
|
255
|
+
for k in range(8):
|
|
256
|
+
if values[k] > -1:
|
|
257
|
+
if k == 0:
|
|
258
|
+
values[0] = array[index[0] - 1, index[1] - 1]
|
|
259
|
+
|
|
260
|
+
if k == 1:
|
|
261
|
+
values[1] = array[index[0] - 1, index[1]]
|
|
262
|
+
|
|
263
|
+
if k == 2:
|
|
264
|
+
values[2] = array[index[0] - 1, index[1] + 1]
|
|
265
|
+
|
|
266
|
+
if k == 3:
|
|
267
|
+
values[3] = array[index[0], index[1] - 1]
|
|
268
|
+
|
|
269
|
+
if k == 4:
|
|
270
|
+
values[4] = array[index[0], index[1] + 1]
|
|
271
|
+
|
|
272
|
+
if k == 5:
|
|
273
|
+
values[5] = array[index[0] + 1, index[1] - 1]
|
|
274
|
+
|
|
275
|
+
if k == 6:
|
|
276
|
+
values[6] = array[index[0] + 1, index[1]]
|
|
277
|
+
|
|
278
|
+
if k == 7:
|
|
279
|
+
values[7] = array[index[0] + 1, index[1] + 1]
|
|
280
|
+
|
|
281
|
+
else:
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
# check if some of the neighbours is already part of the intrusion network
|
|
285
|
+
for h in range(8):
|
|
286
|
+
if values[h] > -1:
|
|
287
|
+
if h == 0:
|
|
288
|
+
if inet[index[0] - 1, index[1] - 1] == 0:
|
|
289
|
+
values[0] = -2
|
|
290
|
+
if h == 1:
|
|
291
|
+
if inet[index[0] - 1, index[1]] == 0:
|
|
292
|
+
values[1] = -2
|
|
293
|
+
if h == 2:
|
|
294
|
+
if inet[index[0] - 1, index[1] + 1] == 0:
|
|
295
|
+
values[2] = -2
|
|
296
|
+
if h == 3:
|
|
297
|
+
if inet[index[0], index[1] - 1] == 0:
|
|
298
|
+
values[3] = -2
|
|
299
|
+
if h == 4:
|
|
300
|
+
if inet[index[0], index[1] + 1] == 0:
|
|
301
|
+
values[4] = -2
|
|
302
|
+
if h == 5:
|
|
303
|
+
if inet[index[0] + 1, index[1] - 1] == 0:
|
|
304
|
+
values[5] = -2
|
|
305
|
+
if h == 6:
|
|
306
|
+
if inet[index[0] + 1, index[1]] == 0:
|
|
307
|
+
values[6] = -2
|
|
308
|
+
if h == 7:
|
|
309
|
+
if inet[index[0] + 1, index[1] + 1] == 0:
|
|
310
|
+
values[7] = -2
|
|
311
|
+
else:
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
return values
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def index_min(array):
|
|
318
|
+
"""
|
|
319
|
+
Given an array of 1x8, dentify the index of the minimum value within the array.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
array: numpy array, 1x8
|
|
324
|
+
|
|
325
|
+
Returns
|
|
326
|
+
-------
|
|
327
|
+
index_min: integer, index of minimum value in array
|
|
328
|
+
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
# return the index value of the minimum value in an array of 1x8
|
|
332
|
+
# print(array)
|
|
333
|
+
index_array = {}
|
|
334
|
+
|
|
335
|
+
for i in range(
|
|
336
|
+
8
|
|
337
|
+
): # create a dictionary assining positions from 0 to 7 to the values in the array
|
|
338
|
+
if array[i] >= 0:
|
|
339
|
+
index_array.update({i: array[i]})
|
|
340
|
+
|
|
341
|
+
if len(index_array.values()) > 0:
|
|
342
|
+
|
|
343
|
+
minimum_val = min(index_array.values())
|
|
344
|
+
|
|
345
|
+
for key, value in index_array.items():
|
|
346
|
+
if value == minimum_val:
|
|
347
|
+
index_min = key
|
|
348
|
+
|
|
349
|
+
else:
|
|
350
|
+
index_min = 10
|
|
351
|
+
|
|
352
|
+
return index_min
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def new_inlet(inlet, direction):
|
|
356
|
+
"""
|
|
357
|
+
Determine new inlet indexes, given current inlet and direction of minimum difference in time map.
|
|
358
|
+
|
|
359
|
+
Parameters
|
|
360
|
+
----------
|
|
361
|
+
inlet: list of indexes [row, column]
|
|
362
|
+
direction: integers e[0,7] (0: above-left, 1: above, 2: above right, 3: left, 4: right, 5: below left, 6: below, 7:below right)
|
|
363
|
+
|
|
364
|
+
Returns
|
|
365
|
+
-------
|
|
366
|
+
new_inlet: list of indexes [row, column]
|
|
367
|
+
|
|
368
|
+
"""
|
|
369
|
+
pot_new_inlets = {}
|
|
370
|
+
|
|
371
|
+
pot_new_inlets.update({"0": np.array([inlet[0] - 1, inlet[1] - 1])})
|
|
372
|
+
pot_new_inlets.update({"1": np.array([inlet[0] - 1, inlet[1]])})
|
|
373
|
+
pot_new_inlets.update({"2": np.array([inlet[0] - 1, inlet[1] + 1])})
|
|
374
|
+
pot_new_inlets.update({"3": np.array([inlet[0], inlet[1] - 1])})
|
|
375
|
+
pot_new_inlets.update({"4": np.array([inlet[0], inlet[1] + 1])})
|
|
376
|
+
pot_new_inlets.update({"5": np.array([inlet[0] + 1, inlet[1] - 1])})
|
|
377
|
+
pot_new_inlets.update({"6": np.array([inlet[0] + 1, inlet[1]])})
|
|
378
|
+
pot_new_inlets.update({"7": np.array([inlet[0] + 1, inlet[1] + 1])})
|
|
379
|
+
new_inlet = np.zeros(2)
|
|
380
|
+
|
|
381
|
+
for key, value in pot_new_inlets.items():
|
|
382
|
+
if key == str(direction):
|
|
383
|
+
new_inlet = value
|
|
384
|
+
return new_inlet
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def grid_from_array(array, fixed_coord, lower_extent, upper_extent):
|
|
388
|
+
"""
|
|
389
|
+
Create an numpy matrix of [i,j,x,y,z,values in array], given an array of 2 dimensions (any combination between x, y an z)
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
array: numpy array, two dimension. Represents a cross section of the model, and its values could be any property
|
|
394
|
+
fixed_coord: list, containing coordinate and value,
|
|
395
|
+
ie, [0,2] means section is in x=2, or [1, .45] means sections is in y= 0.45
|
|
396
|
+
the cross section is along this coordinate
|
|
397
|
+
lower_extent: numpy array 1x3, lower extent of the model
|
|
398
|
+
upper_extent: numpy array 1x3, upper extent of the model
|
|
399
|
+
|
|
400
|
+
Returns
|
|
401
|
+
-------
|
|
402
|
+
values: numpy matrix of [i,j,x,y,z,values in array]
|
|
403
|
+
(i,j) indexed in array
|
|
404
|
+
(x,y,z) coordinates considering lower and upper extent of model
|
|
405
|
+
values, from array
|
|
406
|
+
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
spacing_i = len(array) # number of rows
|
|
410
|
+
spacing_j = len(array[0]) # number of columns
|
|
411
|
+
values = np.zeros([spacing_i * spacing_j, 6])
|
|
412
|
+
if fixed_coord[0] == "X":
|
|
413
|
+
y = np.linspace(lower_extent[1], upper_extent[1], spacing_j)
|
|
414
|
+
z = np.linspace(lower_extent[2], upper_extent[2], spacing_i)
|
|
415
|
+
l = 0
|
|
416
|
+
for j in range(spacing_j):
|
|
417
|
+
for i in range(spacing_i):
|
|
418
|
+
values[l] = [
|
|
419
|
+
i,
|
|
420
|
+
j,
|
|
421
|
+
fixed_coord[1],
|
|
422
|
+
y[j],
|
|
423
|
+
z[i],
|
|
424
|
+
array[spacing_i - 1 - i, j],
|
|
425
|
+
]
|
|
426
|
+
l = l + 1
|
|
427
|
+
|
|
428
|
+
if fixed_coord[0] == "Y":
|
|
429
|
+
x = np.linspace(lower_extent[0], upper_extent[0], spacing_j)
|
|
430
|
+
z = np.linspace(lower_extent[2], upper_extent[2], spacing_i)
|
|
431
|
+
l = 0
|
|
432
|
+
for j in range(spacing_j):
|
|
433
|
+
for i in range(spacing_i):
|
|
434
|
+
values[l] = [
|
|
435
|
+
i,
|
|
436
|
+
j,
|
|
437
|
+
x[j],
|
|
438
|
+
fixed_coord[1],
|
|
439
|
+
z[i],
|
|
440
|
+
array[spacing_i - 1 - i, j],
|
|
441
|
+
]
|
|
442
|
+
l = l + 1
|
|
443
|
+
|
|
444
|
+
if fixed_coord[0] == "Z":
|
|
445
|
+
x = np.linspace(lower_extent[0], upper_extent[0], spacing_j)
|
|
446
|
+
y = np.linspace(lower_extent[1], upper_extent[1], spacing_i)
|
|
447
|
+
l = 0
|
|
448
|
+
for j in range(spacing_j):
|
|
449
|
+
for i in range(spacing_i):
|
|
450
|
+
values[l] = [
|
|
451
|
+
spacing_i - 1 - i,
|
|
452
|
+
spacing_j - 1 - j,
|
|
453
|
+
x[j],
|
|
454
|
+
y[i],
|
|
455
|
+
fixed_coord[1],
|
|
456
|
+
array[spacing_i - 1 - i, j],
|
|
457
|
+
]
|
|
458
|
+
l = l + 1
|
|
459
|
+
|
|
460
|
+
return values
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utils
|
|
3
|
+
=====
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .logging import getLogger, log_to_file, log_to_console, get_levels
|
|
7
|
+
from .exceptions import (
|
|
8
|
+
LoopException,
|
|
9
|
+
LoopImportError,
|
|
10
|
+
InterpolatorError,
|
|
11
|
+
LoopTypeError,
|
|
12
|
+
LoopValueError,
|
|
13
|
+
)
|
|
14
|
+
from ._transformation import EuclideanTransformation
|
|
15
|
+
from .helper import (
|
|
16
|
+
get_data_bounding_box,
|
|
17
|
+
get_data_bounding_box_map,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# from ..datatypes._bounding_box import BoundingBox
|
|
21
|
+
from .maths import (
|
|
22
|
+
get_dip_vector,
|
|
23
|
+
get_strike_vector,
|
|
24
|
+
get_vectors,
|
|
25
|
+
strikedip2vector,
|
|
26
|
+
azimuthplunge2vector,
|
|
27
|
+
normal_vector_to_strike_and_dip,
|
|
28
|
+
rotate,
|
|
29
|
+
)
|
|
30
|
+
from .helper import create_surface, create_box
|
|
31
|
+
from .regions import RegionEverywhere, RegionFunction, NegativeRegion, PositiveRegion
|
|
32
|
+
|
|
33
|
+
from .json_encoder import LoopJSONEncoder
|
|
34
|
+
import numpy as np
|
|
35
|
+
|
|
36
|
+
rng = np.random.default_rng()
|
|
37
|
+
|
|
38
|
+
from ._surface import LoopIsosurfacer, surface_list
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Union, Callable, List
|
|
4
|
+
import numpy as np
|
|
5
|
+
import numpy.typing as npt
|
|
6
|
+
from LoopStructural.utils.logging import getLogger
|
|
7
|
+
|
|
8
|
+
logger = getLogger(__name__)
|
|
9
|
+
try:
|
|
10
|
+
from skimage.measure import marching_cubes
|
|
11
|
+
except ImportError:
|
|
12
|
+
logger.warning("Using deprecated version of scikit-image")
|
|
13
|
+
from skimage.measure import marching_cubes_lewiner as marching_cubes
|
|
14
|
+
|
|
15
|
+
# from LoopStructural.interpolators._geological_interpolator import GeologicalInterpolator
|
|
16
|
+
from LoopStructural.datatypes import Surface, BoundingBox
|
|
17
|
+
|
|
18
|
+
surface_list = dict[str, Surface]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LoopIsosurfacer:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
bounding_box: BoundingBox,
|
|
25
|
+
interpolator=None,
|
|
26
|
+
callable: Optional[Callable[[npt.ArrayLike], npt.ArrayLike]] = None,
|
|
27
|
+
):
|
|
28
|
+
"""Extract isosurfaces from a geological interpolator or a callable function.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
bounding_box : BoundingBox
|
|
34
|
+
_description_
|
|
35
|
+
interpolator : Optional[GeologicalInterpolator], optional
|
|
36
|
+
interpolator object, by default None
|
|
37
|
+
callable : Optional[Callable[[npt.ArrayLike], npt.ArrayLike]], optional
|
|
38
|
+
callable object, by default None
|
|
39
|
+
|
|
40
|
+
Raises
|
|
41
|
+
------
|
|
42
|
+
ValueError
|
|
43
|
+
_description_
|
|
44
|
+
ValueError
|
|
45
|
+
_description_
|
|
46
|
+
ValueError
|
|
47
|
+
_description_
|
|
48
|
+
"""
|
|
49
|
+
self.bounding_box = bounding_box
|
|
50
|
+
self.callable = callable
|
|
51
|
+
if interpolator is None and callable is None:
|
|
52
|
+
raise ValueError("Must specify either interpolator or callable")
|
|
53
|
+
if interpolator is not None and self.callable is not None:
|
|
54
|
+
raise ValueError("Must specify either interpolator or callable")
|
|
55
|
+
|
|
56
|
+
if interpolator is not None:
|
|
57
|
+
self.callable = interpolator.evaluate_value
|
|
58
|
+
if self.callable is None:
|
|
59
|
+
raise ValueError("Must specify either interpolator or callable")
|
|
60
|
+
|
|
61
|
+
def fit(
|
|
62
|
+
self,
|
|
63
|
+
values: Optional[Union[list, int, float]],
|
|
64
|
+
name: Optional[Union[List[str], str]] = None,
|
|
65
|
+
) -> surface_list:
|
|
66
|
+
"""Extract isosurfaces from the interpolator
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
values : Union[list, int, float]
|
|
71
|
+
Either a list of values to extract isosurfaces for, or a single value
|
|
72
|
+
to extract a single isosurface for, or an integer to extract that many
|
|
73
|
+
isosurfaces evenly spaced between the minimum and maximum values of the
|
|
74
|
+
interpolator.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
surface_list
|
|
79
|
+
a dictionary containing the extracted isosurfaces
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
if not callable(self.callable):
|
|
83
|
+
raise ValueError("No interpolator of callable function set")
|
|
84
|
+
|
|
85
|
+
surfaces = []
|
|
86
|
+
all_values = self.callable(self.bounding_box.regular_grid(local=False))
|
|
87
|
+
## set value to mean value if its not specified
|
|
88
|
+
if values is None:
|
|
89
|
+
values = [(np.nanmax(all_values) - np.nanmin(all_values)) / 2]
|
|
90
|
+
if isinstance(values, list):
|
|
91
|
+
isovalues = values
|
|
92
|
+
elif isinstance(values, float):
|
|
93
|
+
isovalues = [values]
|
|
94
|
+
elif isinstance(values, int) and values < 1:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"Number of isosurfaces must be greater than 1. Either use a positive integer or provide a list or float for a specific isovalue."
|
|
97
|
+
)
|
|
98
|
+
elif isinstance(values, int):
|
|
99
|
+
isovalues = np.linspace(
|
|
100
|
+
np.nanmin(all_values) + np.finfo(float).eps,
|
|
101
|
+
np.nanmax(all_values) - np.finfo(float).eps,
|
|
102
|
+
values,
|
|
103
|
+
)
|
|
104
|
+
logger.info(f'Isosurfacing at values: {isovalues}')
|
|
105
|
+
if name is None:
|
|
106
|
+
names = ["surface"] * len(isovalues)
|
|
107
|
+
if isinstance(name, str):
|
|
108
|
+
names = [name] * len(isovalues)
|
|
109
|
+
if isinstance(name, list):
|
|
110
|
+
names = name
|
|
111
|
+
for name, isovalue in zip(names, isovalues):
|
|
112
|
+
try:
|
|
113
|
+
step_vector = (self.bounding_box.maximum - self.bounding_box.origin) / (
|
|
114
|
+
np.array(self.bounding_box.nsteps) - 1
|
|
115
|
+
)
|
|
116
|
+
verts, faces, normals, values = marching_cubes(
|
|
117
|
+
# np.rot90(
|
|
118
|
+
all_values.reshape(self.bounding_box.nsteps, order="C"), # k=2, axes=(0, 1)
|
|
119
|
+
# ),
|
|
120
|
+
isovalue,
|
|
121
|
+
spacing=step_vector,
|
|
122
|
+
mask=~np.isnan(all_values.reshape(self.bounding_box.nsteps, order="C")),
|
|
123
|
+
)
|
|
124
|
+
except RuntimeError:
|
|
125
|
+
logger.warning(f"Failed to extract isosurface for {isovalue}")
|
|
126
|
+
continue
|
|
127
|
+
except ValueError:
|
|
128
|
+
logger.warning(f"Failed to extract isosurface for {isovalue}")
|
|
129
|
+
continue
|
|
130
|
+
values = np.zeros(verts.shape[0]) + isovalue
|
|
131
|
+
# need to add both global and local origin. If the bb is a buffer the local
|
|
132
|
+
# origin may not be 0
|
|
133
|
+
verts += self.bounding_box.global_origin
|
|
134
|
+
surfaces.append(
|
|
135
|
+
Surface(
|
|
136
|
+
vertices=verts,
|
|
137
|
+
triangles=faces,
|
|
138
|
+
normals=normals,
|
|
139
|
+
name=f"{name}_{isovalue}",
|
|
140
|
+
values=values,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
return surfaces
|