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.

Files changed (129) hide show
  1. LoopStructural/__init__.py +52 -0
  2. LoopStructural/datasets/__init__.py +23 -0
  3. LoopStructural/datasets/_base.py +301 -0
  4. LoopStructural/datasets/_example_models.py +10 -0
  5. LoopStructural/datasets/data/claudius.csv +21049 -0
  6. LoopStructural/datasets/data/claudiusbb.txt +2 -0
  7. LoopStructural/datasets/data/duplex.csv +126 -0
  8. LoopStructural/datasets/data/duplexbb.txt +2 -0
  9. LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
  10. LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
  11. LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
  12. LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
  13. LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
  14. LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
  15. LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
  16. LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
  17. LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
  18. LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
  19. LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
  20. LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
  21. LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
  22. LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
  23. LoopStructural/datasets/data/intrusion.csv +1017 -0
  24. LoopStructural/datasets/data/intrusionbb.txt +2 -0
  25. LoopStructural/datasets/data/onefoldbb.txt +2 -0
  26. LoopStructural/datasets/data/onefolddata.csv +2226 -0
  27. LoopStructural/datasets/data/refolded_bb.txt +2 -0
  28. LoopStructural/datasets/data/refolded_fold.csv +205 -0
  29. LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
  30. LoopStructural/datatypes/__init__.py +4 -0
  31. LoopStructural/datatypes/_bounding_box.py +422 -0
  32. LoopStructural/datatypes/_point.py +166 -0
  33. LoopStructural/datatypes/_structured_grid.py +94 -0
  34. LoopStructural/datatypes/_surface.py +184 -0
  35. LoopStructural/export/exporters.py +554 -0
  36. LoopStructural/export/file_formats.py +15 -0
  37. LoopStructural/export/geoh5.py +100 -0
  38. LoopStructural/export/gocad.py +126 -0
  39. LoopStructural/export/omf_wrapper.py +88 -0
  40. LoopStructural/interpolators/__init__.py +105 -0
  41. LoopStructural/interpolators/_api.py +143 -0
  42. LoopStructural/interpolators/_builders.py +149 -0
  43. LoopStructural/interpolators/_cython/__init__.py +0 -0
  44. LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
  45. LoopStructural/interpolators/_discrete_interpolator.py +692 -0
  46. LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
  47. LoopStructural/interpolators/_geological_interpolator.py +380 -0
  48. LoopStructural/interpolators/_interpolator_factory.py +89 -0
  49. LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
  50. LoopStructural/interpolators/_operator.py +38 -0
  51. LoopStructural/interpolators/_p1interpolator.py +228 -0
  52. LoopStructural/interpolators/_p2interpolator.py +277 -0
  53. LoopStructural/interpolators/_surfe_wrapper.py +174 -0
  54. LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
  55. LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
  56. LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
  57. LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
  58. LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
  59. LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
  60. LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
  61. LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
  62. LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
  63. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
  64. LoopStructural/interpolators/supports/__init__.py +55 -0
  65. LoopStructural/interpolators/supports/_aabb.py +77 -0
  66. LoopStructural/interpolators/supports/_base_support.py +114 -0
  67. LoopStructural/interpolators/supports/_face_table.py +70 -0
  68. LoopStructural/interpolators/supports/_support_factory.py +32 -0
  69. LoopStructural/modelling/__init__.py +29 -0
  70. LoopStructural/modelling/core/__init__.py +0 -0
  71. LoopStructural/modelling/core/geological_model.py +1867 -0
  72. LoopStructural/modelling/features/__init__.py +32 -0
  73. LoopStructural/modelling/features/_analytical_feature.py +79 -0
  74. LoopStructural/modelling/features/_base_geological_feature.py +364 -0
  75. LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
  76. LoopStructural/modelling/features/_geological_feature.py +288 -0
  77. LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
  78. LoopStructural/modelling/features/_region.py +18 -0
  79. LoopStructural/modelling/features/_structural_frame.py +186 -0
  80. LoopStructural/modelling/features/_unconformity_feature.py +83 -0
  81. LoopStructural/modelling/features/builders/__init__.py +5 -0
  82. LoopStructural/modelling/features/builders/_base_builder.py +111 -0
  83. LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
  84. LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
  85. LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
  86. LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
  87. LoopStructural/modelling/features/fault/__init__.py +3 -0
  88. LoopStructural/modelling/features/fault/_fault_function.py +444 -0
  89. LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
  90. LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
  91. LoopStructural/modelling/features/fold/__init__.py +9 -0
  92. LoopStructural/modelling/features/fold/_fold.py +167 -0
  93. LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
  94. LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
  95. LoopStructural/modelling/features/fold/_foldframe.py +194 -0
  96. LoopStructural/modelling/features/fold/_svariogram.py +188 -0
  97. LoopStructural/modelling/input/__init__.py +2 -0
  98. LoopStructural/modelling/input/fault_network.py +80 -0
  99. LoopStructural/modelling/input/map2loop_processor.py +165 -0
  100. LoopStructural/modelling/input/process_data.py +650 -0
  101. LoopStructural/modelling/input/project_file.py +84 -0
  102. LoopStructural/modelling/intrusions/__init__.py +25 -0
  103. LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
  104. LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
  105. LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
  106. LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
  107. LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
  108. LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
  109. LoopStructural/utils/__init__.py +38 -0
  110. LoopStructural/utils/_surface.py +143 -0
  111. LoopStructural/utils/_transformation.py +76 -0
  112. LoopStructural/utils/config.py +18 -0
  113. LoopStructural/utils/dtm_creator.py +17 -0
  114. LoopStructural/utils/exceptions.py +31 -0
  115. LoopStructural/utils/helper.py +292 -0
  116. LoopStructural/utils/json_encoder.py +18 -0
  117. LoopStructural/utils/linalg.py +8 -0
  118. LoopStructural/utils/logging.py +79 -0
  119. LoopStructural/utils/maths.py +245 -0
  120. LoopStructural/utils/regions.py +103 -0
  121. LoopStructural/utils/typing.py +7 -0
  122. LoopStructural/utils/utils.py +68 -0
  123. LoopStructural/version.py +1 -0
  124. LoopStructural/visualisation/__init__.py +11 -0
  125. LoopStructural-1.6.1.dist-info/LICENSE +21 -0
  126. LoopStructural-1.6.1.dist-info/METADATA +81 -0
  127. LoopStructural-1.6.1.dist-info/RECORD +129 -0
  128. LoopStructural-1.6.1.dist-info/WHEEL +5 -0
  129. 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