LoopStructural 1.6.15__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/interpolators/_geological_interpolator.py +8 -2
- LoopStructural/modelling/core/geological_model.py +87 -102
- LoopStructural/modelling/core/stratigraphic_column.py +473 -0
- LoopStructural/modelling/features/builders/_fault_builder.py +1 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +1 -1
- LoopStructural/version.py +1 -1
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.16.dist-info}/METADATA +1 -1
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.16.dist-info}/RECORD +12 -11
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.16.dist-info}/WHEEL +0 -0
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.16.dist-info}/licenses/LICENSE +0 -0
- {loopstructural-1.6.15.dist-info → loopstructural-1.6.16.dist-info}/top_level.txt +0 -0
LoopStructural/__init__.py
CHANGED
|
@@ -19,6 +19,7 @@ ch.setFormatter(formatter)
|
|
|
19
19
|
ch.setLevel(logging.WARNING)
|
|
20
20
|
loggers = {}
|
|
21
21
|
from .modelling.core.geological_model import GeologicalModel
|
|
22
|
+
from .modelling.core.stratigraphic_column import StratigraphicColumn
|
|
22
23
|
from .interpolators._api import LoopInterpolator
|
|
23
24
|
from .interpolators import InterpolatorBuilder
|
|
24
25
|
from .datatypes import BoundingBox
|
|
@@ -28,26 +29,43 @@ logger = getLogger(__name__)
|
|
|
28
29
|
logger.info("Imported LoopStructural")
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
def setLogging(level="info"):
|
|
32
|
+
def setLogging(level="info", handler=None):
|
|
32
33
|
"""
|
|
33
|
-
Set the logging parameters for log file
|
|
34
|
+
Set the logging parameters for log file or custom handler
|
|
34
35
|
|
|
35
36
|
Parameters
|
|
36
37
|
----------
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
level : str
|
|
39
|
+
'info', 'warning', 'error', 'debug'
|
|
40
|
+
handler : logging.Handler, optional
|
|
41
|
+
A logging handler to use instead of the default StreamHandler
|
|
41
42
|
"""
|
|
42
43
|
import LoopStructural
|
|
43
44
|
|
|
44
|
-
logger = getLogger(__name__)
|
|
45
|
-
|
|
46
45
|
levels = get_levels()
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
level_value = levels.get(level, logging.WARNING)
|
|
47
|
+
|
|
48
|
+
# Create default handler if none provided
|
|
49
|
+
if handler is None:
|
|
50
|
+
handler = logging.StreamHandler()
|
|
51
|
+
|
|
52
|
+
formatter = logging.Formatter(
|
|
53
|
+
"%(levelname)s: %(asctime)s: %(filename)s:%(lineno)d -- %(message)s"
|
|
54
|
+
)
|
|
55
|
+
handler.setFormatter(formatter)
|
|
56
|
+
handler.setLevel(level_value)
|
|
49
57
|
|
|
58
|
+
# Replace handlers in all known loggers
|
|
50
59
|
for name in LoopStructural.loggers:
|
|
51
60
|
logger = logging.getLogger(name)
|
|
52
|
-
logger.
|
|
53
|
-
|
|
61
|
+
logger.handlers = []
|
|
62
|
+
logger.addHandler(handler)
|
|
63
|
+
logger.setLevel(level_value)
|
|
64
|
+
|
|
65
|
+
# Also apply to main module logger
|
|
66
|
+
main_logger = logging.getLogger(__name__)
|
|
67
|
+
main_logger.handlers = []
|
|
68
|
+
main_logger.addHandler(handler)
|
|
69
|
+
main_logger.setLevel(level_value)
|
|
70
|
+
|
|
71
|
+
main_logger.info(f"Set logging to {level}")
|
|
@@ -154,16 +154,22 @@ class GeologicalInterpolator(metaclass=ABCMeta):
|
|
|
154
154
|
----------
|
|
155
155
|
points : np.ndarray
|
|
156
156
|
array containing the value constraints usually 7-8 columns.
|
|
157
|
-
X,Y,Z,nx,ny,nz,weight
|
|
157
|
+
X,Y,Z,nx,ny,nz,(weight, default : 1 for each row)
|
|
158
158
|
|
|
159
159
|
Returns
|
|
160
160
|
-------
|
|
161
161
|
|
|
162
|
+
Notes
|
|
163
|
+
-------
|
|
164
|
+
If no weights are provided, w = 1 is assigned to each normal constraint.
|
|
165
|
+
|
|
162
166
|
"""
|
|
163
167
|
if points.shape[1] == self.dimensions * 2:
|
|
164
168
|
points = np.hstack([points, np.ones((points.shape[0], 1))])
|
|
169
|
+
logger.warning(f"No weight provided for normal constraints, all weights are set to 1")
|
|
170
|
+
raise Warning
|
|
165
171
|
if points.shape[1] < self.dimensions * 2 + 1:
|
|
166
|
-
raise ValueError("
|
|
172
|
+
raise ValueError("Normal constraints must at least have X,Y,Z,nx,ny,nz")
|
|
167
173
|
self.n_n = points.shape[0]
|
|
168
174
|
self.data["normal"] = points
|
|
169
175
|
self.up_to_date = False
|
|
@@ -37,7 +37,7 @@ from ...datatypes import BoundingBox
|
|
|
37
37
|
from ...modelling.intrusions import IntrusionBuilder
|
|
38
38
|
|
|
39
39
|
from ...modelling.intrusions import IntrusionFrameBuilder
|
|
40
|
-
|
|
40
|
+
from .stratigraphic_column import StratigraphicColumn
|
|
41
41
|
|
|
42
42
|
logger = getLogger(__name__)
|
|
43
43
|
|
|
@@ -61,14 +61,11 @@ class GeologicalModel:
|
|
|
61
61
|
the origin of the model box
|
|
62
62
|
parameters : dict
|
|
63
63
|
a dictionary tracking the parameters used to build the model
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
|
|
66
66
|
"""
|
|
67
67
|
|
|
68
|
-
def __init__(
|
|
69
|
-
self,
|
|
70
|
-
*args
|
|
71
|
-
):
|
|
68
|
+
def __init__(self, *args):
|
|
72
69
|
"""
|
|
73
70
|
Parameters
|
|
74
71
|
----------
|
|
@@ -78,7 +75,7 @@ class GeologicalModel:
|
|
|
78
75
|
the origin of the model
|
|
79
76
|
maximum : np.array(3,dtype=doubles)
|
|
80
77
|
the maximum of the model
|
|
81
|
-
|
|
78
|
+
|
|
82
79
|
Examples
|
|
83
80
|
--------
|
|
84
81
|
Demo data
|
|
@@ -126,7 +123,8 @@ class GeologicalModel:
|
|
|
126
123
|
self.feature_name_index = {}
|
|
127
124
|
self._data = pd.DataFrame() # None
|
|
128
125
|
|
|
129
|
-
self.stratigraphic_column =
|
|
126
|
+
self.stratigraphic_column = StratigraphicColumn()
|
|
127
|
+
|
|
130
128
|
|
|
131
129
|
self.tol = 1e-10 * np.max(self.bounding_box.maximum - self.bounding_box.origin)
|
|
132
130
|
self._dtm = None
|
|
@@ -148,29 +146,6 @@ class GeologicalModel:
|
|
|
148
146
|
# json["features"] = [f.to_json() for f in self.features]
|
|
149
147
|
return json
|
|
150
148
|
|
|
151
|
-
# @classmethod
|
|
152
|
-
# def from_json(cls,json):
|
|
153
|
-
# """
|
|
154
|
-
# Create a geological model from a json string
|
|
155
|
-
|
|
156
|
-
# Parameters
|
|
157
|
-
# ----------
|
|
158
|
-
# json : str
|
|
159
|
-
# json string of the geological model
|
|
160
|
-
|
|
161
|
-
# Returns
|
|
162
|
-
# -------
|
|
163
|
-
# model : GeologicalModel
|
|
164
|
-
# a geological model
|
|
165
|
-
# """
|
|
166
|
-
# model = cls(json["model"]["origin"],json["model"]["maximum"],data=None)
|
|
167
|
-
# model.stratigraphic_column = json["model"]["stratigraphic_column"]
|
|
168
|
-
# model.nsteps = json["model"]["nsteps"]
|
|
169
|
-
# model.data = pd.read_json(json["model"]["data"])
|
|
170
|
-
# model.features = []
|
|
171
|
-
# for feature in json["features"]:
|
|
172
|
-
# model.features.append(GeologicalFeature.from_json(feature,model))
|
|
173
|
-
# return model
|
|
174
149
|
def __str__(self):
|
|
175
150
|
return f"GeologicalModel with {len(self.features)} features"
|
|
176
151
|
|
|
@@ -181,6 +156,38 @@ class GeologicalModel:
|
|
|
181
156
|
data = data.copy()
|
|
182
157
|
data[['X', 'Y', 'Z']] = self.bounding_box.project(data[['X', 'Y', 'Z']].to_numpy())
|
|
183
158
|
|
|
159
|
+
if "type" in data:
|
|
160
|
+
logger.warning("'type' is deprecated replace with 'feature_name' \n")
|
|
161
|
+
data.rename(columns={"type": "feature_name"}, inplace=True)
|
|
162
|
+
if "feature_name" not in data:
|
|
163
|
+
logger.error("Data does not contain 'feature_name' column")
|
|
164
|
+
raise BaseException("Cannot load data")
|
|
165
|
+
for h in all_heading():
|
|
166
|
+
if h not in data:
|
|
167
|
+
data[h] = np.nan
|
|
168
|
+
if h == "w":
|
|
169
|
+
data[h] = 1.0
|
|
170
|
+
if h == "coord":
|
|
171
|
+
data[h] = 0
|
|
172
|
+
if h == "polarity":
|
|
173
|
+
data[h] = 1.0
|
|
174
|
+
# LS wants polarity as -1 or 1, change 0 to -1
|
|
175
|
+
data.loc[data["polarity"] == 0, "polarity"] = -1.0
|
|
176
|
+
data.loc[np.isnan(data["w"]), "w"] = 1.0
|
|
177
|
+
if "strike" in data and "dip" in data:
|
|
178
|
+
logger.info("Converting strike and dip to vectors")
|
|
179
|
+
mask = np.all(~np.isnan(data.loc[:, ["strike", "dip"]]), axis=1)
|
|
180
|
+
data.loc[mask, gradient_vec_names()] = (
|
|
181
|
+
strikedip2vector(data.loc[mask, "strike"], data.loc[mask, "dip"])
|
|
182
|
+
* data.loc[mask, "polarity"].to_numpy()[:, None]
|
|
183
|
+
)
|
|
184
|
+
data.drop(["strike", "dip"], axis=1, inplace=True)
|
|
185
|
+
data[['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']] = data[
|
|
186
|
+
['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']
|
|
187
|
+
].astype(float)
|
|
188
|
+
return data
|
|
189
|
+
|
|
190
|
+
|
|
184
191
|
if "type" in data:
|
|
185
192
|
logger.warning("'type' is deprecated replace with 'feature_name' \n")
|
|
186
193
|
data.rename(columns={"type": "feature_name"}, inplace=True)
|
|
@@ -402,12 +409,6 @@ class GeologicalModel:
|
|
|
402
409
|
"""
|
|
403
410
|
return [f.name for f in self.faults]
|
|
404
411
|
|
|
405
|
-
def check_inialisation(self):
|
|
406
|
-
if self.data is None:
|
|
407
|
-
logger.error("Data not associated with GeologicalModel. Run set_data")
|
|
408
|
-
return False
|
|
409
|
-
if self.data.shape[0] > 0:
|
|
410
|
-
return True
|
|
411
412
|
|
|
412
413
|
def to_file(self, file):
|
|
413
414
|
"""Save a model to a pickle file requires dill
|
|
@@ -506,7 +507,6 @@ class GeologicalModel:
|
|
|
506
507
|
# self._data[['X','Y','Z']] = self.bounding_box.project(self._data[['X','Y','Z']].to_numpy())
|
|
507
508
|
|
|
508
509
|
|
|
509
|
-
|
|
510
510
|
def set_model_data(self, data):
|
|
511
511
|
logger.warning("deprecated method. Model data can now be set using the data attribute")
|
|
512
512
|
self.data = data.copy()
|
|
@@ -532,28 +532,34 @@ class GeologicalModel:
|
|
|
532
532
|
}
|
|
533
533
|
|
|
534
534
|
"""
|
|
535
|
+
self.stratigraphic_column.clear()
|
|
535
536
|
# if the colour for a unit hasn't been specified we can just sample from
|
|
536
537
|
# a colour map e.g. tab20
|
|
537
538
|
logger.info("Adding stratigraphic column to model")
|
|
538
|
-
|
|
539
|
-
|
|
539
|
+
DeprecationWarning(
|
|
540
|
+
"set_stratigraphic_column is deprecated, use model.stratigraphic_column.add_units instead"
|
|
541
|
+
)
|
|
540
542
|
for g in stratigraphic_column.keys():
|
|
541
543
|
for u in stratigraphic_column[g].keys():
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
544
|
+
thickness = 0
|
|
545
|
+
if "min" in stratigraphic_column[g][u] and "max" in stratigraphic_column[g][u]:
|
|
546
|
+
min_val = stratigraphic_column[g][u]["min"]
|
|
547
|
+
max_val = stratigraphic_column[g][u].get("max", None)
|
|
548
|
+
thickness = max_val - min_val if max_val is not None else None
|
|
549
|
+
logger.warning(
|
|
550
|
+
f"""
|
|
551
|
+
model.stratigraphic_column.add_unit({u},
|
|
552
|
+
colour={stratigraphic_column[g][u].get("colour", None)},
|
|
553
|
+
thickness={thickness})"""
|
|
554
|
+
)
|
|
555
|
+
self.stratigraphic_column.add_unit(
|
|
556
|
+
u,
|
|
557
|
+
colour=stratigraphic_column[g][u].get("colour", None),
|
|
558
|
+
thickness=thickness,
|
|
559
|
+
)
|
|
560
|
+
self.stratigraphic_column.add_unconformity(
|
|
561
|
+
name=''.join([g, 'unconformity']),
|
|
562
|
+
)
|
|
557
563
|
|
|
558
564
|
def create_and_add_foliation(
|
|
559
565
|
self,
|
|
@@ -600,9 +606,7 @@ class GeologicalModel:
|
|
|
600
606
|
An interpolator will be chosen by calling :meth:`LoopStructural.GeologicalModel.get_interpolator`
|
|
601
607
|
|
|
602
608
|
"""
|
|
603
|
-
|
|
604
|
-
logger.warning(f"{series_surface_data} not added, model not initialised")
|
|
605
|
-
return
|
|
609
|
+
|
|
606
610
|
# if tol is not specified use the model default
|
|
607
611
|
if tol is None:
|
|
608
612
|
tol = self.tol
|
|
@@ -638,7 +642,7 @@ class GeologicalModel:
|
|
|
638
642
|
|
|
639
643
|
def create_and_add_fold_frame(
|
|
640
644
|
self,
|
|
641
|
-
fold_frame_name:str,
|
|
645
|
+
fold_frame_name: str,
|
|
642
646
|
*,
|
|
643
647
|
fold_frame_data=None,
|
|
644
648
|
interpolatortype="FDI",
|
|
@@ -667,15 +671,14 @@ class GeologicalModel:
|
|
|
667
671
|
:class:`LoopStructural.modelling.features.builders.StructuralFrameBuilder`
|
|
668
672
|
and :meth:`LoopStructural.modelling.features.builders.StructuralFrameBuilder.setup`
|
|
669
673
|
and the interpolator, such as `domain` or `tol`
|
|
670
|
-
|
|
674
|
+
|
|
671
675
|
|
|
672
676
|
Returns
|
|
673
677
|
-------
|
|
674
678
|
fold_frame : FoldFrame
|
|
675
679
|
the created fold frame
|
|
676
680
|
"""
|
|
677
|
-
|
|
678
|
-
return False
|
|
681
|
+
|
|
679
682
|
if tol is None:
|
|
680
683
|
tol = self.tol
|
|
681
684
|
|
|
@@ -751,8 +754,7 @@ class GeologicalModel:
|
|
|
751
754
|
:class:`LoopStructural.modelling.features.builders.FoldedFeatureBuilder`
|
|
752
755
|
|
|
753
756
|
"""
|
|
754
|
-
|
|
755
|
-
return False
|
|
757
|
+
|
|
756
758
|
if tol is None:
|
|
757
759
|
tol = self.tol
|
|
758
760
|
|
|
@@ -781,11 +783,8 @@ class GeologicalModel:
|
|
|
781
783
|
if foliation_data.shape[0] == 0:
|
|
782
784
|
logger.warning(f"No data for {foliation_name}, skipping")
|
|
783
785
|
return
|
|
784
|
-
series_builder.add_data_from_data_frame(
|
|
785
|
-
|
|
786
|
-
foliation_data
|
|
787
|
-
)
|
|
788
|
-
)
|
|
786
|
+
series_builder.add_data_from_data_frame(self.prepare_data(foliation_data))
|
|
787
|
+
|
|
789
788
|
self._add_faults(series_builder)
|
|
790
789
|
# series_builder.add_data_to_interpolator(True)
|
|
791
790
|
# build feature
|
|
@@ -852,8 +851,7 @@ class GeologicalModel:
|
|
|
852
851
|
see :class:`LoopStructural.modelling.features.fold.FoldEvent`,
|
|
853
852
|
:class:`LoopStructural.modelling.features.builders.FoldedFeatureBuilder`
|
|
854
853
|
"""
|
|
855
|
-
|
|
856
|
-
return False
|
|
854
|
+
|
|
857
855
|
if tol is None:
|
|
858
856
|
tol = self.tol
|
|
859
857
|
|
|
@@ -1180,7 +1178,7 @@ class GeologicalModel:
|
|
|
1180
1178
|
return uc_feature
|
|
1181
1179
|
|
|
1182
1180
|
def create_and_add_domain_fault(
|
|
1183
|
-
self, fault_surface_data
|
|
1181
|
+
self, fault_surface_data, *, nelements=10000, interpolatortype="FDI", **kwargs
|
|
1184
1182
|
):
|
|
1185
1183
|
"""
|
|
1186
1184
|
Parameters
|
|
@@ -1234,7 +1232,7 @@ class GeologicalModel:
|
|
|
1234
1232
|
fault_name: str,
|
|
1235
1233
|
displacement: float,
|
|
1236
1234
|
*,
|
|
1237
|
-
fault_data:Optional[pd.DataFrame] = None,
|
|
1235
|
+
fault_data: Optional[pd.DataFrame] = None,
|
|
1238
1236
|
interpolatortype="FDI",
|
|
1239
1237
|
tol=None,
|
|
1240
1238
|
fault_slip_vector=None,
|
|
@@ -1319,7 +1317,7 @@ class GeologicalModel:
|
|
|
1319
1317
|
if "data_region" in kwargs:
|
|
1320
1318
|
kwargs.pop("data_region")
|
|
1321
1319
|
logger.error("kwarg data_region currently not supported, disabling")
|
|
1322
|
-
displacement_scaled = displacement
|
|
1320
|
+
displacement_scaled = displacement
|
|
1323
1321
|
fault_frame_builder = FaultBuilder(
|
|
1324
1322
|
interpolatortype,
|
|
1325
1323
|
bounding_box=self.bounding_box,
|
|
@@ -1340,11 +1338,11 @@ class GeologicalModel:
|
|
|
1340
1338
|
if fault_center is not None and ~np.isnan(fault_center).any():
|
|
1341
1339
|
fault_center = self.scale(fault_center, inplace=False)
|
|
1342
1340
|
if minor_axis:
|
|
1343
|
-
minor_axis = minor_axis
|
|
1341
|
+
minor_axis = minor_axis
|
|
1344
1342
|
if major_axis:
|
|
1345
|
-
major_axis = major_axis
|
|
1343
|
+
major_axis = major_axis
|
|
1346
1344
|
if intermediate_axis:
|
|
1347
|
-
intermediate_axis = intermediate_axis
|
|
1345
|
+
intermediate_axis = intermediate_axis
|
|
1348
1346
|
fault_frame_builder.create_data_from_geometry(
|
|
1349
1347
|
fault_frame_data=self.prepare_data(fault_data),
|
|
1350
1348
|
fault_center=fault_center,
|
|
@@ -1400,7 +1398,8 @@ class GeologicalModel:
|
|
|
1400
1398
|
|
|
1401
1399
|
"""
|
|
1402
1400
|
|
|
1403
|
-
return self.bounding_box.reproject(points,inplace=inplace)
|
|
1401
|
+
return self.bounding_box.reproject(points, inplace=inplace)
|
|
1402
|
+
|
|
1404
1403
|
|
|
1405
1404
|
# TODO move scale to bounding box/transformer
|
|
1406
1405
|
def scale(self, points: np.ndarray, *, inplace: bool = False) -> np.ndarray:
|
|
@@ -1418,7 +1417,8 @@ class GeologicalModel:
|
|
|
1418
1417
|
points : np.a::rray((N,3),dtype=double)
|
|
1419
1418
|
|
|
1420
1419
|
"""
|
|
1421
|
-
return self.bounding_box.project(np.array(points).astype(float),inplace=inplace)
|
|
1420
|
+
return self.bounding_box.project(np.array(points).astype(float), inplace=inplace)
|
|
1421
|
+
|
|
1422
1422
|
|
|
1423
1423
|
def regular_grid(self, *, nsteps=None, shuffle=True, rescale=False, order="C"):
|
|
1424
1424
|
"""
|
|
@@ -1567,7 +1567,7 @@ class GeologicalModel:
|
|
|
1567
1567
|
if f.type == FeatureType.FAULT:
|
|
1568
1568
|
disp = f.displacementfeature.evaluate_value(points)
|
|
1569
1569
|
vals[~np.isnan(disp)] += disp[~np.isnan(disp)]
|
|
1570
|
-
return vals
|
|
1570
|
+
return vals # convert from restoration magnutude to displacement
|
|
1571
1571
|
|
|
1572
1572
|
def get_feature_by_name(self, feature_name) -> GeologicalFeature:
|
|
1573
1573
|
"""Returns a feature from the mode given a name
|
|
@@ -1736,30 +1736,15 @@ class GeologicalModel:
|
|
|
1736
1736
|
units = []
|
|
1737
1737
|
if self.stratigraphic_column is None:
|
|
1738
1738
|
return []
|
|
1739
|
-
|
|
1740
|
-
|
|
1739
|
+
units = self.stratigraphic_column.get_isovalues()
|
|
1740
|
+
for name, u in units.items():
|
|
1741
|
+
if u['group'] not in self:
|
|
1742
|
+
logger.warning(f"Group {u['group']} not found in model")
|
|
1741
1743
|
continue
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
units.append(series)
|
|
1745
|
-
unit_table = pd.DataFrame(units)
|
|
1746
|
-
for u in unit_table['feature_name'].unique():
|
|
1747
|
-
|
|
1748
|
-
values = unit_table.loc[unit_table['feature_name'] == u, 'min' if bottoms else 'max']
|
|
1749
|
-
if 'name' not in unit_table.columns:
|
|
1750
|
-
unit_table['name'] = unit_table['feature_name']
|
|
1751
|
-
|
|
1752
|
-
names = unit_table[unit_table['feature_name'] == u]['name']
|
|
1753
|
-
values = values.loc[~np.logical_or(values == np.inf, values == -np.inf)]
|
|
1744
|
+
feature = self.get_feature_by_name(u['group'])
|
|
1745
|
+
|
|
1754
1746
|
surfaces.extend(
|
|
1755
|
-
|
|
1756
|
-
values.to_list(),
|
|
1757
|
-
self.bounding_box,
|
|
1758
|
-
name=names.loc[values.index].to_list(),
|
|
1759
|
-
colours=unit_table.loc[unit_table['feature_name'] == u, 'colour'].tolist()[
|
|
1760
|
-
1:
|
|
1761
|
-
], # we don't isosurface basement, no value
|
|
1762
|
-
)
|
|
1747
|
+
feature.surfaces([u['value']], self.bounding_box, name=name, colours=[u['colour']])
|
|
1763
1748
|
)
|
|
1764
1749
|
|
|
1765
1750
|
return surfaces
|
|
@@ -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
|
|
@@ -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:
|
LoopStructural/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.6.
|
|
1
|
+
__version__ = "1.6.16"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
LoopStructural/__init__.py,sha256=
|
|
2
|
-
LoopStructural/version.py,sha256=
|
|
1
|
+
LoopStructural/__init__.py,sha256=3yLT09qS3rOrQtyxsECOTh98B3sKv2IrE_DIfTSu930,2022
|
|
2
|
+
LoopStructural/version.py,sha256=DGM49nQ783oxeemzhU9PZifWC3GA5fiS9MnhU4zNfso,23
|
|
3
3
|
LoopStructural/datasets/__init__.py,sha256=ylb7fzJU_DyQ73LlwQos7VamqkDSGITbbnoKg7KAOmE,677
|
|
4
4
|
LoopStructural/datasets/_base.py,sha256=FB_D5ybBYHoaNbycdkpZcRffzjrrL1xp9X0k-pyob9Y,7618
|
|
5
5
|
LoopStructural/datasets/_example_models.py,sha256=Zg33IeUyh4C-lC0DRMLqCDP2IrX8L-gNV1WxJwBGjzM,113
|
|
@@ -45,7 +45,7 @@ LoopStructural/interpolators/_constant_norm.py,sha256=gGaDGDoEzfnL4b6386YwInCxIA
|
|
|
45
45
|
LoopStructural/interpolators/_discrete_fold_interpolator.py,sha256=eDe0R1lcQ0AuMcv7zlpu5c-soCv7AybIqQAuN2vFE3M,6542
|
|
46
46
|
LoopStructural/interpolators/_discrete_interpolator.py,sha256=bPGJ1CrvLmz3m86JkXAiw7WbfbGEeGXR5cklDX54PQU,26083
|
|
47
47
|
LoopStructural/interpolators/_finite_difference_interpolator.py,sha256=qc7zpqJka16I7yv-GigjQxF0hWRRHyWpHm8dHersy_8,18712
|
|
48
|
-
LoopStructural/interpolators/_geological_interpolator.py,sha256=
|
|
48
|
+
LoopStructural/interpolators/_geological_interpolator.py,sha256=hdi8mFwHp4G3Tv3lXDvQey8QrRIHuk3KRbJcKtBh918,11460
|
|
49
49
|
LoopStructural/interpolators/_interpolator_builder.py,sha256=Z8bhmco5aSQX19A8It2SB_rG61wnlyshWfp3ivm8rU0,4586
|
|
50
50
|
LoopStructural/interpolators/_interpolator_factory.py,sha256=fbjebXSe5IgTol1tnBlnsw9gD426v-TGkX3gquIg7LI,2782
|
|
51
51
|
LoopStructural/interpolators/_interpolatortype.py,sha256=q8U9JGyFpO2FBA9XsMI5ojv3TV1LYqyvYHzLAbHcj9A,593
|
|
@@ -71,7 +71,8 @@ LoopStructural/interpolators/supports/_face_table.py,sha256=Hyj4Io63NkPRN8ab9uDH
|
|
|
71
71
|
LoopStructural/interpolators/supports/_support_factory.py,sha256=XNAxnr-JS3KEhdsoZeJ-VaLTJwlvxgBuRMCqYrCDW18,1485
|
|
72
72
|
LoopStructural/modelling/__init__.py,sha256=a-bq2gDhyUlcky5l9kl_IP3ExMdohkgYjQz2V8madQE,902
|
|
73
73
|
LoopStructural/modelling/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
|
-
LoopStructural/modelling/core/geological_model.py,sha256=
|
|
74
|
+
LoopStructural/modelling/core/geological_model.py,sha256=asAyjVkO-uILX8QQgM8oqdkBQQfCjFDpuXyy2wn8lwc,65150
|
|
75
|
+
LoopStructural/modelling/core/stratigraphic_column.py,sha256=ZHquRb6S97KwB8K8pqEGRSDah0-FjU2uOqdor8NMuY4,16828
|
|
75
76
|
LoopStructural/modelling/features/__init__.py,sha256=Vf-qd5EDBtJ1DpuXXyCcw2-wf6LWPRW5wzxDEO3vOc8,939
|
|
76
77
|
LoopStructural/modelling/features/_analytical_feature.py,sha256=U_g86LgQhYY2359rdsDqpvziYwqrWkc5EdvhJARiUWo,3597
|
|
77
78
|
LoopStructural/modelling/features/_base_geological_feature.py,sha256=kGyrbb8nNzfi-M8WSrVMEQYKtxThdcBxaji5HKXtAqw,13483
|
|
@@ -84,14 +85,14 @@ LoopStructural/modelling/features/_structural_frame.py,sha256=e3QmNHLwuZc5PX3rLa
|
|
|
84
85
|
LoopStructural/modelling/features/_unconformity_feature.py,sha256=2Bx0BI38YLdcNvDWuP9E1pKFN4orEUq9aC8b5xG1UVk,2362
|
|
85
86
|
LoopStructural/modelling/features/builders/__init__.py,sha256=Gqld1C-PcaXfJ8vpkWMDCmehmd3hZNYQk1knPtl59Bk,266
|
|
86
87
|
LoopStructural/modelling/features/builders/_base_builder.py,sha256=N3txGC98V08A8-k2TLdoIWgWLfblZ91kaTvciPq_QVM,3750
|
|
87
|
-
LoopStructural/modelling/features/builders/_fault_builder.py,sha256=
|
|
88
|
+
LoopStructural/modelling/features/builders/_fault_builder.py,sha256=_DZ0Hy_-jjm2fFU-5lY60zGisixdUWbAjsOQzMFKigY,25359
|
|
88
89
|
LoopStructural/modelling/features/builders/_folded_feature_builder.py,sha256=1_0BVTzcvmFl6K3_lX-jF0tiMFPmS8j6vPeSLn9MbrE,6607
|
|
89
90
|
LoopStructural/modelling/features/builders/_geological_feature_builder.py,sha256=tQJJol1U5wH6V0Rw3OgigCFPssv8uOPQ5jdwdLFg3cc,22015
|
|
90
91
|
LoopStructural/modelling/features/builders/_structural_frame_builder.py,sha256=ms3-fuFpDEarjzYU5W499TquOIlTwHPUibVxIypfmWY,8019
|
|
91
92
|
LoopStructural/modelling/features/fault/__init__.py,sha256=4u0KfYzmoO-ddFGo9qd9ov0gBoLqBiPAUsaw5zhEOAQ,189
|
|
92
93
|
LoopStructural/modelling/features/fault/_fault_function.py,sha256=QEPh2jIvgD68hEJc5SM5xuMzZw-93V1me1ZbK9G2TB0,12655
|
|
93
94
|
LoopStructural/modelling/features/fault/_fault_function_feature.py,sha256=4m0jVNx7ewrVI0pECI1wNciv8Cy8FzhZrYDjKJ_e2GU,2558
|
|
94
|
-
LoopStructural/modelling/features/fault/_fault_segment.py,sha256=
|
|
95
|
+
LoopStructural/modelling/features/fault/_fault_segment.py,sha256=BEIVAY_-iQYYuoyIj1doq_cDLgmMpY0PDYBiuBXOjN8,18309
|
|
95
96
|
LoopStructural/modelling/features/fold/__init__.py,sha256=pOv20yQvshZozvmO_YFw2E7Prp9DExlm855N-0SnxbQ,175
|
|
96
97
|
LoopStructural/modelling/features/fold/_fold.py,sha256=bPnnLUSiF4uoMRg8aHoOSTPRgaM0JyLoRQPu5_A-J3w,5448
|
|
97
98
|
LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py,sha256=CXLbFRQ3CrTMAcHmfdbKcmSvvLs9_6TLe0Wqi1pK2tg,892
|
|
@@ -131,8 +132,8 @@ LoopStructural/utils/regions.py,sha256=SjCC40GI7_n03G4mlcmvyrBgJFbxnvB3leBzXWco3
|
|
|
131
132
|
LoopStructural/utils/typing.py,sha256=29uVSTZdzXXH-jdlaYyBWZ1gQ2-nlZ2-XoVgG_PXNFY,157
|
|
132
133
|
LoopStructural/utils/utils.py,sha256=2Z4zVE6G752-SPmM29zebk82bROJxEwi_YiiJjcVED4,2438
|
|
133
134
|
LoopStructural/visualisation/__init__.py,sha256=5BDgKor8-ae6DrS7IZybJ3Wq_pTnCchxuY4EgzA7v1M,318
|
|
134
|
-
loopstructural-1.6.
|
|
135
|
-
loopstructural-1.6.
|
|
136
|
-
loopstructural-1.6.
|
|
137
|
-
loopstructural-1.6.
|
|
138
|
-
loopstructural-1.6.
|
|
135
|
+
loopstructural-1.6.16.dist-info/licenses/LICENSE,sha256=ZqGeNFOgmYevj7Ld7Q-kR4lAxWXuBRUdUmPC6XM_py8,1071
|
|
136
|
+
loopstructural-1.6.16.dist-info/METADATA,sha256=uvSdqdLD2MRCeE972hyitE5L5UfIne1o5uVRXzIPIPU,6453
|
|
137
|
+
loopstructural-1.6.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
138
|
+
loopstructural-1.6.16.dist-info/top_level.txt,sha256=QtQErKzYHfg6ddxTQ1NyaTxXBVM6qAqrM_vxEPyXZLg,15
|
|
139
|
+
loopstructural-1.6.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|