apsg 1.3.0__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.
- AUTHORS.md +9 -0
- CHANGELOG.md +304 -0
- CONTRIBUTING.md +91 -0
- apsg/__init__.py +104 -0
- apsg/config.py +214 -0
- apsg/database/__init__.py +23 -0
- apsg/database/_alchemy.py +609 -0
- apsg/database/_sdbread.py +284 -0
- apsg/decorator/__init__.py +5 -0
- apsg/decorator/_decorator.py +43 -0
- apsg/feature/__init__.py +79 -0
- apsg/feature/_container.py +1808 -0
- apsg/feature/_geodata.py +702 -0
- apsg/feature/_paleomag.py +425 -0
- apsg/feature/_statistics.py +430 -0
- apsg/feature/_tensor2.py +550 -0
- apsg/feature/_tensor3.py +1108 -0
- apsg/helpers/__init__.py +28 -0
- apsg/helpers/_helper.py +7 -0
- apsg/helpers/_math.py +46 -0
- apsg/helpers/_notation.py +119 -0
- apsg/math/__init__.py +6 -0
- apsg/math/_matrix.py +406 -0
- apsg/math/_vector.py +590 -0
- apsg/pandas/__init__.py +27 -0
- apsg/pandas/_pandas_api.py +507 -0
- apsg/plotting/__init__.py +25 -0
- apsg/plotting/_fabricplot.py +563 -0
- apsg/plotting/_paleomagplots.py +71 -0
- apsg/plotting/_plot_artists.py +551 -0
- apsg/plotting/_projection.py +326 -0
- apsg/plotting/_roseplot.py +360 -0
- apsg/plotting/_stereogrid.py +332 -0
- apsg/plotting/_stereonet.py +992 -0
- apsg/shell.py +35 -0
- apsg-1.3.0.dist-info/AUTHORS.md +9 -0
- apsg-1.3.0.dist-info/METADATA +141 -0
- apsg-1.3.0.dist-info/RECORD +40 -0
- apsg-1.3.0.dist-info/WHEEL +4 -0
- apsg-1.3.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from apsg.helpers._helper import eformat
|
|
11
|
+
from apsg.math._vector import Vector3
|
|
12
|
+
from apsg.feature._geodata import Lineation, Foliation, Pair
|
|
13
|
+
from apsg.feature._container import Vector3Set
|
|
14
|
+
from apsg.feature._tensor3 import DeformationGradient3
|
|
15
|
+
|
|
16
|
+
__all__ = ("Core",)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Core(object):
|
|
20
|
+
"""
|
|
21
|
+
``Core`` class to store palemomagnetic analysis data
|
|
22
|
+
|
|
23
|
+
Keyword Args:
|
|
24
|
+
info:
|
|
25
|
+
specimen:
|
|
26
|
+
filename:
|
|
27
|
+
alpha:
|
|
28
|
+
beta:
|
|
29
|
+
strike:
|
|
30
|
+
dip:
|
|
31
|
+
volume:
|
|
32
|
+
date:
|
|
33
|
+
steps:
|
|
34
|
+
a95:
|
|
35
|
+
comments:
|
|
36
|
+
vectors:
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
``Core`` object instance
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, **kwargs):
|
|
44
|
+
self.site = kwargs.get("site", "Default")
|
|
45
|
+
self.specimen = kwargs.get("specimen", "Default")
|
|
46
|
+
self.filename = kwargs.get("filename", None)
|
|
47
|
+
self.latitude = kwargs.get("latitude", None)
|
|
48
|
+
self.longitude = kwargs.get("longitude", None)
|
|
49
|
+
self.height = kwargs.get("height", None)
|
|
50
|
+
self.rock = kwargs.get("rock", None)
|
|
51
|
+
self.age = kwargs.get("age", None)
|
|
52
|
+
self.formation = kwargs.get("formation", None)
|
|
53
|
+
self.sref = kwargs.get("sref", Pair(180, 0, 180, 0))
|
|
54
|
+
self.gref = kwargs.get("gref", Pair(180, 0, 180, 0))
|
|
55
|
+
self.bedding = kwargs.get("bedding", Foliation(0, 0))
|
|
56
|
+
self.foldaxis = kwargs.get("foldaxis", Lineation(0, 0))
|
|
57
|
+
self.volume = kwargs.get("volume", 1.0)
|
|
58
|
+
self.date = kwargs.get("date", datetime.now())
|
|
59
|
+
self.steps = kwargs.get("steps", [])
|
|
60
|
+
self.a95 = kwargs.get("a95", [])
|
|
61
|
+
self.comments = kwargs.get("comments", [])
|
|
62
|
+
self._vectors = kwargs.get("vectors", [])
|
|
63
|
+
self.module_units = kwargs.get("module_units", "A/m")
|
|
64
|
+
self.susceptibility_units = kwargs.get("susceptibility_units", "e-06 SI")
|
|
65
|
+
self.demag_units = kwargs.get("demag_units", "°C")
|
|
66
|
+
|
|
67
|
+
def __repr__(self):
|
|
68
|
+
return f"Core {self.site} {self.specimen}"
|
|
69
|
+
|
|
70
|
+
def __getitem__(self, key):
|
|
71
|
+
if np.issubdtype(type(key), np.integer):
|
|
72
|
+
key = self.steps[key]
|
|
73
|
+
if isinstance(key, slice):
|
|
74
|
+
res = deepcopy(self)
|
|
75
|
+
if key.start:
|
|
76
|
+
start_ok = key.start
|
|
77
|
+
else:
|
|
78
|
+
start_ok = self.nsteps[0]
|
|
79
|
+
if key.stop:
|
|
80
|
+
stop_ok = key.stop
|
|
81
|
+
else:
|
|
82
|
+
stop_ok = self.nsteps[-1]
|
|
83
|
+
ix = (self.nsteps >= start_ok) & (self.nsteps <= stop_ok)
|
|
84
|
+
res.steps = [val for (val, ok) in zip(self.steps, ix) if ok]
|
|
85
|
+
res.a95 = [val for (val, ok) in zip(self.a95, ix) if ok]
|
|
86
|
+
res.comments = [val for (val, ok) in zip(self.comments, ix) if ok]
|
|
87
|
+
res._vectors = [val for (val, ok) in zip(self._vectors, ix) if ok]
|
|
88
|
+
res.name = self.specimen + "({}-{})".format(start_ok, stop_ok)
|
|
89
|
+
return res
|
|
90
|
+
if isinstance(key, str):
|
|
91
|
+
if key in self.steps:
|
|
92
|
+
ix = self.steps.index(key)
|
|
93
|
+
return dict(
|
|
94
|
+
step=key,
|
|
95
|
+
MAG=self.MAG[ix],
|
|
96
|
+
V=self._vectors[ix],
|
|
97
|
+
geo=self.geo[ix],
|
|
98
|
+
tilt=self.tilt[ix],
|
|
99
|
+
a95=self.a95[ix],
|
|
100
|
+
comment=self.comments[ix],
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
raise (Exception("Key {} not found.".format(key)))
|
|
104
|
+
else:
|
|
105
|
+
raise (Exception("Key of {} not supported.".format(type(key))))
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_pmd(cls, filename):
|
|
109
|
+
"""Return ``Core`` instance generated from PMD file.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
filename: PMD file
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
with open(filename, encoding="latin1") as f:
|
|
116
|
+
d = f.read().splitlines()
|
|
117
|
+
data = {}
|
|
118
|
+
fields = {
|
|
119
|
+
"Xc": slice(5, 14),
|
|
120
|
+
"Yc": slice(15, 24),
|
|
121
|
+
"Zc": slice(25, 34),
|
|
122
|
+
"a95": slice(69, 73),
|
|
123
|
+
}
|
|
124
|
+
data["info"] = d[0].strip()
|
|
125
|
+
vline = d[1].strip()
|
|
126
|
+
data["filename"] = filename
|
|
127
|
+
data["specimen"] = vline[:10].strip()
|
|
128
|
+
data["alpha"] = float(vline[10:20].strip().split("=")[1])
|
|
129
|
+
data["beta"] = float(vline[20:30].strip().split("=")[1])
|
|
130
|
+
data["strike"] = float(vline[30:40].strip().split("=")[1])
|
|
131
|
+
data["dip"] = float(vline[40:50].strip().split("=")[1])
|
|
132
|
+
data["volume"] = float(vline[50:63].strip().split("=")[1].strip("m3"))
|
|
133
|
+
data["date"] = datetime.strptime(vline[63:].strip(), "%m-%d-%Y %H:%M")
|
|
134
|
+
data["steps"] = [ln[:4].strip() for ln in d[3:-1]]
|
|
135
|
+
data["comments"] = [ln[73:].strip() for ln in d[3:-1]]
|
|
136
|
+
data["a95"] = [float(ln[fields["a95"]].strip()) for ln in d[3:-1]]
|
|
137
|
+
data["vectors"] = []
|
|
138
|
+
for ln in d[3:-1]:
|
|
139
|
+
x = float(ln[fields["Xc"]].strip())
|
|
140
|
+
y = float(ln[fields["Yc"]].strip())
|
|
141
|
+
z = float(ln[fields["Zc"]].strip())
|
|
142
|
+
data["vectors"].append(Vector3((x, y, z)))
|
|
143
|
+
return cls(**data)
|
|
144
|
+
|
|
145
|
+
def write_pmd(self, filename=None):
|
|
146
|
+
"""Save ``Core`` instance to PMD file.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
filename: PMD file
|
|
150
|
+
|
|
151
|
+
"""
|
|
152
|
+
if filename is None:
|
|
153
|
+
filename = self.filename
|
|
154
|
+
ff = os.path.splitext(os.path.basename(filename))[0][:8]
|
|
155
|
+
dt = self.date.strftime("%m-%d-%Y %H:%M")
|
|
156
|
+
infoln = "{:<8} a={:5.1f} b={:5.1f} s={:5.1f} d={:5.1f} v={}m3 {}"
|
|
157
|
+
ln0 = infoln.format(
|
|
158
|
+
ff,
|
|
159
|
+
self.gref.lin.geo[0],
|
|
160
|
+
self.gref.lin.geo[1],
|
|
161
|
+
self.bedding.geo[0],
|
|
162
|
+
self.bedding.geo[1],
|
|
163
|
+
eformat(self.volume, 2),
|
|
164
|
+
dt,
|
|
165
|
+
)
|
|
166
|
+
headln = (
|
|
167
|
+
"STEP Xc [Am2] Yc [Am2] Zc [Am2] MAG[A/m] Dg Ig Ds Is a95 "
|
|
168
|
+
)
|
|
169
|
+
with open(filename, "w") as pmdfile:
|
|
170
|
+
print("/".join([self.site, self.name]), file=pmdfile, end="\r\n")
|
|
171
|
+
print(ln0, file=pmdfile, end="\r\n")
|
|
172
|
+
print(headln, file=pmdfile, end="\r\n")
|
|
173
|
+
for ln in self.datatable:
|
|
174
|
+
print(ln, file=pmdfile, end="\r\n")
|
|
175
|
+
pmdfile.write(chr(26))
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def from_rs3(cls, filename, exclude=["C", "G"]):
|
|
179
|
+
"""Return ``Core`` instance generated from PMD file.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
filename: Remasoft rs3 file
|
|
183
|
+
|
|
184
|
+
Kwargs:
|
|
185
|
+
exclude: Labels to be excluded. Default ['C', 'G']
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
with open(filename, encoding="windows-1250") as f:
|
|
189
|
+
d = f.read().splitlines()
|
|
190
|
+
|
|
191
|
+
from io import StringIO
|
|
192
|
+
import pandas as pd
|
|
193
|
+
|
|
194
|
+
headspec = [
|
|
195
|
+
[0, 9],
|
|
196
|
+
[10, 19],
|
|
197
|
+
[20, 29],
|
|
198
|
+
[30, 40],
|
|
199
|
+
[41, 50],
|
|
200
|
+
[51, 65],
|
|
201
|
+
[66, 70],
|
|
202
|
+
[71, 73],
|
|
203
|
+
[74, 79],
|
|
204
|
+
[80, 85],
|
|
205
|
+
[86, 91],
|
|
206
|
+
[92, 97],
|
|
207
|
+
[98, 103],
|
|
208
|
+
[104, 109],
|
|
209
|
+
[110, 112],
|
|
210
|
+
[113, 115],
|
|
211
|
+
[116, 118],
|
|
212
|
+
[119, 121],
|
|
213
|
+
[122, 126],
|
|
214
|
+
]
|
|
215
|
+
bodyspec = [
|
|
216
|
+
[0, 2],
|
|
217
|
+
[3, 13],
|
|
218
|
+
[14, 27],
|
|
219
|
+
[28, 33],
|
|
220
|
+
[34, 39],
|
|
221
|
+
[40, 45],
|
|
222
|
+
[46, 51],
|
|
223
|
+
[52, 57],
|
|
224
|
+
[58, 63],
|
|
225
|
+
[64, 69],
|
|
226
|
+
[70, 75],
|
|
227
|
+
[76, 81],
|
|
228
|
+
[82, 95],
|
|
229
|
+
[96, 105],
|
|
230
|
+
[106, 115],
|
|
231
|
+
[116, 126],
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
head = pd.read_fwf(StringIO("\n".join(d[:2])), colspecs=headspec)
|
|
235
|
+
body = pd.read_fwf(StringIO("\n".join(d[2:])), colspecs=bodyspec)
|
|
236
|
+
|
|
237
|
+
data = {}
|
|
238
|
+
data["site"] = head["Site"][0] if not pd.isna(head["Site"][0]) else ""
|
|
239
|
+
data["filename"] = filename
|
|
240
|
+
data["specimen"] = head["Name"][0] if not pd.isna(head["Name"][0]) else ""
|
|
241
|
+
data["longitude"] = (
|
|
242
|
+
float(head["Longitude"][0]) if not pd.isna(head["Longitude"][0]) else None
|
|
243
|
+
)
|
|
244
|
+
data["latitude"] = (
|
|
245
|
+
float(head["Latitude"][0]) if not pd.isna(head["Latitude"][0]) else None
|
|
246
|
+
)
|
|
247
|
+
data["height"] = (
|
|
248
|
+
float(head["Height"][0]) if not pd.isna(head["Height"][0]) else None
|
|
249
|
+
)
|
|
250
|
+
data["rock"] = head["Rock"][0] if not pd.isna(head["Rock"][0]) else ""
|
|
251
|
+
data["age"] = head["Age"][0] if not pd.isna(head["Age"][0]) else ""
|
|
252
|
+
data["formation"] = head["Fm"][0] if not pd.isna(head["Fm"][0]) else ""
|
|
253
|
+
data["sref"] = Pair(180, 0, 180, 0)
|
|
254
|
+
data["gref"] = Pair(
|
|
255
|
+
float(head["SDec"][0]),
|
|
256
|
+
float(head["SInc"][0]),
|
|
257
|
+
float(head["SDec"][0]),
|
|
258
|
+
float(head["SInc"][0]),
|
|
259
|
+
)
|
|
260
|
+
data["bedding"] = (
|
|
261
|
+
Foliation(float(head["BDec"][0]), float(head["BInc"][0]))
|
|
262
|
+
if not pd.isna(head["BDec"][0]) and not pd.isna(head["BInc"][0])
|
|
263
|
+
else None
|
|
264
|
+
)
|
|
265
|
+
data["foldaxis"] = (
|
|
266
|
+
Lineation(float(head["FDec"][0]), float(head["FInc"][0]))
|
|
267
|
+
if not pd.isna(head["FDec"][0]) and not pd.isna(head["FInc"][0])
|
|
268
|
+
else None
|
|
269
|
+
)
|
|
270
|
+
data["date"] = datetime.now()
|
|
271
|
+
ix = body.iloc[:, 0].apply(lambda x: x not in exclude)
|
|
272
|
+
data["steps"] = body[ix].iloc[:, 1].astype(int).to_list()
|
|
273
|
+
data["comments"] = body[ix]["Note"].to_list()
|
|
274
|
+
data["a95"] = body[ix]["Prec"].to_list()
|
|
275
|
+
data["vectors"] = []
|
|
276
|
+
for n, r in body[ix].iterrows():
|
|
277
|
+
data["vectors"].append(r.iloc[2] * Vector3(r["Dsp"], r["Isp"]))
|
|
278
|
+
return cls(**data)
|
|
279
|
+
|
|
280
|
+
def write_rs3(self, filename=None):
|
|
281
|
+
"""Save ``Core`` instance to RS3 file.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
filename: RS3 file
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
if filename is None:
|
|
288
|
+
filename = self.filename
|
|
289
|
+
|
|
290
|
+
head = "Name Site Latitude Longitude Height Rock Age Fm SDec SInc BDec BInc FDec FInc P1 P2 P3 P4 Note"
|
|
291
|
+
step_lbl = f"Step[{self.demag_units}]"
|
|
292
|
+
module_lbl = f"M[{self.module_units}]"
|
|
293
|
+
susceptibility_lbl = f"K[{self.susceptibility_units}]"
|
|
294
|
+
subhead = f"ID {step_lbl:<10} {module_lbl:>12} Dsp Isp Dge Ige Dtc Itc Dfc Ifc Prec {susceptibility_lbl:>13} Limit1 Limit2 Note "
|
|
295
|
+
latitude = self.latitude if self.latitude is not None else ""
|
|
296
|
+
longitude = self.longitude if self.longitude is not None else ""
|
|
297
|
+
height = self.height if self.height is not None else ""
|
|
298
|
+
sdec, sinc = (round(self.gref.fol.geo[0]), round(self.gref.fol.geo[1]))
|
|
299
|
+
bdec, binc = (
|
|
300
|
+
(round(self.bedding.geo[0]), round(self.bedding.geo[1]))
|
|
301
|
+
if self.bedding is not None
|
|
302
|
+
else ("", "")
|
|
303
|
+
)
|
|
304
|
+
fdec, finc = (
|
|
305
|
+
(round(self.foldaxis.geo[0]), round(self.foldaxis.geo[1]))
|
|
306
|
+
if self.foldaxis is not None
|
|
307
|
+
else ("", "")
|
|
308
|
+
)
|
|
309
|
+
hline = f"{self.specimen:9} {self.site:9} {latitude:<9} {longitude:<10} {height:<9} {self.rock:14} {self.age:<7} {sdec:<5} {sinc:<5} {bdec:<5} {binc:<5} {fdec:<5} {finc:<5} 12 0 6 0 "
|
|
310
|
+
prefix = "T" if self.demag_units == "°C" else "M"
|
|
311
|
+
with open(filename, "w", encoding="windows-1250") as res3file:
|
|
312
|
+
print(head, file=res3file, end="\r\n")
|
|
313
|
+
print(hline, file=res3file, end="\r\n")
|
|
314
|
+
print(subhead, file=res3file, end="\r\n")
|
|
315
|
+
|
|
316
|
+
ids = ["N"] + (len(self.steps) - 1) * [prefix]
|
|
317
|
+
for id, step, MAG, V, geo, tilt, a95, comment in zip(
|
|
318
|
+
ids,
|
|
319
|
+
self.steps,
|
|
320
|
+
self.MAG,
|
|
321
|
+
self.V,
|
|
322
|
+
self.geo,
|
|
323
|
+
self.tilt,
|
|
324
|
+
self.a95,
|
|
325
|
+
self.comments,
|
|
326
|
+
):
|
|
327
|
+
ln = f"{id:2} {step:<10} {MAG:>13g} {V.geo[0]:>5.1f} {V.geo[1]:> 5.1f} {geo.geo[0]:>5.1f} {geo.geo[1]:> 5.1f} {tilt.geo[0]:>5.1f} {tilt.geo[1]:> 5.1f} {a95:>5.1f} {comment:10}"
|
|
328
|
+
print(ln, file=res3file, end="\r\n")
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def datatable(self):
|
|
332
|
+
"""Return data list of strings"""
|
|
333
|
+
tb = []
|
|
334
|
+
for step, MAG, V, geo, tilt, a95, comment in zip(
|
|
335
|
+
self.steps,
|
|
336
|
+
self.MAG,
|
|
337
|
+
self.V,
|
|
338
|
+
self.geo,
|
|
339
|
+
self.tilt,
|
|
340
|
+
self.a95,
|
|
341
|
+
self.comments,
|
|
342
|
+
):
|
|
343
|
+
ln = "{:<4} {: 9.2E} {: 9.2E} {: 9.2E} {: 9.2E} {:5.1f} {:5.1f} {:5.1f} {:5.1f} {:4.1f} {}".format(
|
|
344
|
+
step,
|
|
345
|
+
V.x,
|
|
346
|
+
V.y,
|
|
347
|
+
V.z,
|
|
348
|
+
MAG,
|
|
349
|
+
geo.geo[0],
|
|
350
|
+
geo.geo[1],
|
|
351
|
+
tilt.geo[0],
|
|
352
|
+
tilt.geo[1],
|
|
353
|
+
a95,
|
|
354
|
+
comment,
|
|
355
|
+
)
|
|
356
|
+
tb.append(ln)
|
|
357
|
+
return tb
|
|
358
|
+
|
|
359
|
+
def show(self):
|
|
360
|
+
"""Show data"""
|
|
361
|
+
print(
|
|
362
|
+
"site:{} specimen:{} file:{}\nbedding:{} volume:{}m3 {}".format(
|
|
363
|
+
self.site,
|
|
364
|
+
self.specimen,
|
|
365
|
+
os.path.basename(self.filename),
|
|
366
|
+
self.bedding,
|
|
367
|
+
eformat(self.volume, 2),
|
|
368
|
+
self.date.strftime("%m-%d-%Y %H:%M"),
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
print(
|
|
372
|
+
"STEP Xc [Am2] Yc [Am2] Zc [Am2] MAG[A/m] Dge Ige Dtc Itc a95 "
|
|
373
|
+
)
|
|
374
|
+
print("\n".join(self.datatable))
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def MAG(self):
|
|
378
|
+
"""Returns numpy array of MAG values"""
|
|
379
|
+
return np.array([abs(v) / self.volume for v in self._vectors])
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def nsteps(self):
|
|
383
|
+
"""Returns steps as numpy array of numbers"""
|
|
384
|
+
pp = [re.findall(r"\d+", str(s)) for s in self.steps]
|
|
385
|
+
return np.array([int(s[0]) if s else 0 for s in pp])
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def V(self):
|
|
389
|
+
"""Returns ``Vector3Set`` of vectors in sample (or core) coordinates system"""
|
|
390
|
+
return Vector3Set([v / self.volume for v in self._vectors], name=self.specimen)
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def geo(self):
|
|
394
|
+
"""Returns ``Vector3Set`` of vectors in in-situ coordinates system"""
|
|
395
|
+
H = DeformationGradient3.from_two_pairs(self.sref, self.gref)
|
|
396
|
+
return self.V.transform(H)
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def tilt(self):
|
|
400
|
+
"""Returns ``Vector3Set`` of vectors in tilt‐corrected coordinates system"""
|
|
401
|
+
return self.geo.rotate(
|
|
402
|
+
Lineation(self.bedding.geo[0] - 90, 0), -self.bedding.geo[1]
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
def pca(self, kind="geo", origin=False):
|
|
406
|
+
"""
|
|
407
|
+
PCA analysis to calculate principal component and MAD
|
|
408
|
+
|
|
409
|
+
Keyword Args:
|
|
410
|
+
kind (str): "V", "geo" or "tilt". Default "geo"
|
|
411
|
+
origin (bool): Whether to include origin. Default False
|
|
412
|
+
"""
|
|
413
|
+
data = getattr(self, kind)
|
|
414
|
+
if not origin:
|
|
415
|
+
r = data.R(mean=True) / len(data)
|
|
416
|
+
data = Vector3Set([v - r for v in data])
|
|
417
|
+
ot = data.ortensor()
|
|
418
|
+
if ot.shape > 1:
|
|
419
|
+
pca = ot.V1
|
|
420
|
+
if pca.angle(r) > 90:
|
|
421
|
+
pca = -pca
|
|
422
|
+
else:
|
|
423
|
+
pca = ot.V3
|
|
424
|
+
mad = ot.MAD()
|
|
425
|
+
return pca, mad
|