pyatoa 0.3.1__tar.gz → 0.4.2__tar.gz
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.
- {pyatoa-0.3.1 → pyatoa-0.4.2}/PKG-INFO +1 -1
- {pyatoa-0.3.1 → pyatoa-0.4.2}/docs/conf.py +1 -1
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/core/inspector.py +431 -33
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/core/manager.py +182 -125
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_asdf_utils.py +2 -2
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_inspector.py +2 -25
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_manager.py +17 -27
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_process_util.py +49 -9
- pyatoa-0.4.2/pyatoa/utils/asdf/add.py +239 -0
- pyatoa-0.4.2/pyatoa/utils/asdf/clean.py +168 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/asdf/load.py +24 -13
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/form.py +20 -10
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/process.py +52 -29
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/visuals/insp_plot.py +572 -204
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/visuals/wave_maker.py +11 -10
- pyatoa-0.4.2/pyatoa/visuals/wave_train.py +348 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa.egg-info/PKG-INFO +1 -1
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa.egg-info/SOURCES.txt +2 -3
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyproject.toml +1 -1
- pyatoa-0.3.1/pyatoa/utils/asdf/add.py +0 -121
- pyatoa-0.3.1/pyatoa/utils/asdf/clean.py +0 -101
- pyatoa-0.3.1/pyatoa/visuals/comp_wave.py +0 -586
- pyatoa-0.3.1/pyatoa/visuals/improve_wave.py +0 -486
- {pyatoa-0.3.1 → pyatoa-0.4.2}/LICENSE.txt +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/README.md +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/docs/__init__.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/__init__.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/core/__init__.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/core/config.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/core/executive.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/scripts/__init__.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/scripts/data_data_example.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/scripts/load_example_data.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/scripts/process_data_w_mpi.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/scripts/syn_syn_example.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/__init__.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_config.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_executive.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_mgmt_plot.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_utils.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/tests/test_wave_maker.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/__init__.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/adjoint.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/asdf/__init__.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/asdf/write.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/calculate.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/images.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/srcrcv.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/window.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/utils/write.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/visuals/__init__.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa/visuals/map_maker.py +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa.egg-info/dependency_links.txt +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa.egg-info/requires.txt +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/pyatoa.egg-info/top_level.txt +0 -0
- {pyatoa-0.3.1 → pyatoa-0.4.2}/setup.cfg +0 -0
|
@@ -6,6 +6,7 @@ using Pandas.
|
|
|
6
6
|
import os
|
|
7
7
|
import pyasdf
|
|
8
8
|
import traceback
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
9
10
|
import numpy as np
|
|
10
11
|
import pandas as pd
|
|
11
12
|
from glob import glob
|
|
@@ -26,7 +27,7 @@ class Inspector(InspectorPlotter):
|
|
|
26
27
|
Inherits plotting capabilities from InspectorPlotter class to reduce clutter
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
|
-
def __init__(self, tag="
|
|
30
|
+
def __init__(self, tag="inspector", verbose=False):
|
|
30
31
|
"""
|
|
31
32
|
Inspector will automatically search for relevant file names using the
|
|
32
33
|
tag attribute. If nothing is found, internal dataframes will be empty.
|
|
@@ -41,7 +42,6 @@ class Inspector(InspectorPlotter):
|
|
|
41
42
|
self.sources = pd.DataFrame()
|
|
42
43
|
self.receivers = pd.DataFrame()
|
|
43
44
|
self.tag = tag
|
|
44
|
-
self.verbose = verbose
|
|
45
45
|
|
|
46
46
|
# Placeholder attributes for getters
|
|
47
47
|
self._models = None
|
|
@@ -50,6 +50,11 @@ class Inspector(InspectorPlotter):
|
|
|
50
50
|
self._event_misfit = None
|
|
51
51
|
self._station_misfit = None
|
|
52
52
|
|
|
53
|
+
if verbose:
|
|
54
|
+
logger.setLevel("DEBUG")
|
|
55
|
+
else:
|
|
56
|
+
logger.setLevel("CRITICAL")
|
|
57
|
+
|
|
53
58
|
# Try to load an already created Inspector
|
|
54
59
|
try:
|
|
55
60
|
self.read(tag=self.tag)
|
|
@@ -65,7 +70,8 @@ class Inspector(InspectorPlotter):
|
|
|
65
70
|
str_out = (f"{len(self.events):<4} event(s)\n"
|
|
66
71
|
f"{len(self.stations):<4} station(s)\n"
|
|
67
72
|
f"{len(self.iterations):<4} iteration(s)\n"
|
|
68
|
-
f"{self.evaluations:<4} evaluation(s)"
|
|
73
|
+
f"{self.evaluations:<4} evaluation(s)"
|
|
74
|
+
)
|
|
69
75
|
|
|
70
76
|
except KeyError:
|
|
71
77
|
str_out = (f"{0:<4} event(s)\n"
|
|
@@ -236,6 +242,252 @@ class Inspector(InspectorPlotter):
|
|
|
236
242
|
"""Return a dictionary of event depths in units of meters"""
|
|
237
243
|
return self._try_print("depth_km")
|
|
238
244
|
|
|
245
|
+
def generate_report(self, path_report=None, iteration=None,
|
|
246
|
+
step_count=None, geographic=True, outliers=True,
|
|
247
|
+
scatter=True, summary=True, nstd=2,
|
|
248
|
+
dpi=200, **kwargs):
|
|
249
|
+
"""
|
|
250
|
+
An aggregate function that generates a "report" by creating a number
|
|
251
|
+
of figures that summarize the misfit of the inversion. Makes it easier
|
|
252
|
+
for the User as they don't have to remember each of the functions in
|
|
253
|
+
the Inspector's wheelhouse, all relevant figures will be generated
|
|
254
|
+
automatically.
|
|
255
|
+
|
|
256
|
+
:type path_report: str
|
|
257
|
+
:param path_report: The path where the report will be saved.
|
|
258
|
+
Defaults to "./report".
|
|
259
|
+
:type iteration: int
|
|
260
|
+
:param iteration: The iteration number.
|
|
261
|
+
:type step_count: int
|
|
262
|
+
:param step_count: The step count.
|
|
263
|
+
:type geographic: bool
|
|
264
|
+
:param geographic: If True, includes geographic data in the report.
|
|
265
|
+
Defaults to True.
|
|
266
|
+
:type outliers: bool
|
|
267
|
+
:param outliers: If True, includes outliers in the report.
|
|
268
|
+
Defaults to True.
|
|
269
|
+
:type scatter: bool
|
|
270
|
+
:param scatter: If True, includes a scatter plot in the report.
|
|
271
|
+
Defaults to True.
|
|
272
|
+
:type summary: bool
|
|
273
|
+
:param summary: If True, includes a summary in the report.
|
|
274
|
+
Defaults to True.
|
|
275
|
+
:type nstd: int
|
|
276
|
+
:param nstd: The number of standard deviations for outlier detection.
|
|
277
|
+
Defaults to 2.
|
|
278
|
+
:type dpi: int
|
|
279
|
+
:param dpi: The resolution in dots per inch for the figures in the
|
|
280
|
+
report. Defaults to 200.
|
|
281
|
+
:type kwargs: dict
|
|
282
|
+
:param kwargs: Additional keyword arguments.
|
|
283
|
+
"""
|
|
284
|
+
# By default we generate a report for the final model
|
|
285
|
+
iteration, step_count = self.validate_evaluation(iteration, step_count,
|
|
286
|
+
choice="final")
|
|
287
|
+
if path_report is None:
|
|
288
|
+
path_report = f"./report_{iteration}{step_count}"
|
|
289
|
+
if not os.path.exists(path_report):
|
|
290
|
+
os.makedirs(path_report)
|
|
291
|
+
|
|
292
|
+
# Generate some geographic information
|
|
293
|
+
if geographic:
|
|
294
|
+
geographic_plot_functions = ["map", "travel_times", "raypaths",
|
|
295
|
+
"event_depths"]
|
|
296
|
+
for plot_function in geographic_plot_functions:
|
|
297
|
+
save = os.path.join(path_report, f"{plot_function}.png")
|
|
298
|
+
if os.path.exists(save):
|
|
299
|
+
continue
|
|
300
|
+
getattr(self, plot_function)(iteration=iteration,
|
|
301
|
+
step_count=step_count,
|
|
302
|
+
show=False, dpi=dpi)
|
|
303
|
+
plt.close()
|
|
304
|
+
|
|
305
|
+
# Plot misfit spider plots of event misfit for events that are outside
|
|
306
|
+
# N standard deviations of the mean w.r.t misfit value
|
|
307
|
+
if outliers:
|
|
308
|
+
upper_outliers, lower_outliers, mean ,std = \
|
|
309
|
+
self.event_outliers(iteration, step_count, nstd=nstd)
|
|
310
|
+
|
|
311
|
+
if upper_outliers.empty and lower_outliers.empty:
|
|
312
|
+
logger.warning("No outliers found, skipping outlier plots, "
|
|
313
|
+
"reduce `nstd` to reevaluate for outliers")
|
|
314
|
+
else:
|
|
315
|
+
for outliers, tag in zip([upper_outliers, lower_outliers],
|
|
316
|
+
["upper_outlier", "lower_outlier"]):
|
|
317
|
+
for event_name in outliers.index.to_list():
|
|
318
|
+
self.event_station_misfit_map(
|
|
319
|
+
event=event_name, iteration=iteration,
|
|
320
|
+
step_count=step_count,
|
|
321
|
+
save=os.path.join(path_report,
|
|
322
|
+
f"{tag}_{event_name}.png"),
|
|
323
|
+
show=False, dpi=dpi
|
|
324
|
+
)
|
|
325
|
+
plt.close()
|
|
326
|
+
|
|
327
|
+
# Create a few scatterplots comparing some parameters
|
|
328
|
+
if scatter:
|
|
329
|
+
for xy in [
|
|
330
|
+
("distance_km", "cc_shift_in_seconds"),
|
|
331
|
+
("backazimuth", "cc_shift_in_seconds"),
|
|
332
|
+
("length_s", "cc_shift_in_seconds"),
|
|
333
|
+
]:
|
|
334
|
+
x, y = xy
|
|
335
|
+
self.scatter(x=x, y=y, show=False, dpi=dpi,
|
|
336
|
+
save=os.path.join(path_report, f"{x}_v_{y}.png")
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Plot summary figures that show the status of inversion holistically
|
|
340
|
+
if summary:
|
|
341
|
+
self.convergence(normalize=True, show=False, dpi=dpi,
|
|
342
|
+
save=os.path.join(path_report, "convergence.png")
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
summary_functions = ["event_station_hist2d", "event_comparison",
|
|
346
|
+
"window_stack", "histogram_summary"]
|
|
347
|
+
for plot_function in summary_functions:
|
|
348
|
+
save = os.path.join(path_report, f"{plot_function}.png")
|
|
349
|
+
if os.path.exists(save):
|
|
350
|
+
continue
|
|
351
|
+
# We want the histogram summary to be comparative
|
|
352
|
+
if plot_function == "histogram_summary":
|
|
353
|
+
getattr(self, plot_function)(
|
|
354
|
+
iteration="i01", step_count="s00",
|
|
355
|
+
iteration_comp=iteration, step_count_comp=step_count,
|
|
356
|
+
save=save, show=False, dpi=dpi
|
|
357
|
+
)
|
|
358
|
+
else:
|
|
359
|
+
getattr(self, plot_function)(
|
|
360
|
+
iteration=iteration, step_count=step_count, save=save,
|
|
361
|
+
show=False, dpi=dpi
|
|
362
|
+
)
|
|
363
|
+
plt.close()
|
|
364
|
+
|
|
365
|
+
plt.close("all")
|
|
366
|
+
|
|
367
|
+
self.generate_report_text(path_report, nstd)
|
|
368
|
+
|
|
369
|
+
def generate_report_text(self, path_report="./", nstd=1):
|
|
370
|
+
"""
|
|
371
|
+
Generate a text report highlighting good/bad performing events and
|
|
372
|
+
stations that will provide the User a quickly accessible summary of
|
|
373
|
+
their inversion and may motivate looking at some waveforms
|
|
374
|
+
"""
|
|
375
|
+
line_break = "\n" + "=" * 80 +"\n"
|
|
376
|
+
iter_end, step_end = self.validate_evaluation(
|
|
377
|
+
iteration=None, step_count=None, choice="final"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Get event mean and std for current evaluation
|
|
381
|
+
_, _, mean ,std = \
|
|
382
|
+
self.event_outliers(iter_end, step_end, nstd=nstd)
|
|
383
|
+
|
|
384
|
+
# Get an ascended list of misfit/windows per event
|
|
385
|
+
_windows = self.windows
|
|
386
|
+
self.windows = self.isolate(iteration=iter_end, step_count=step_end)
|
|
387
|
+
|
|
388
|
+
# Event specific window information
|
|
389
|
+
win_per_event = self.nwin(level="event")
|
|
390
|
+
avg_win_per_event = win_per_event.nwin.mean()
|
|
391
|
+
|
|
392
|
+
_tenwin = win_per_event.iloc[0].nwin
|
|
393
|
+
top_event_by_win = win_per_event.iloc[0].name[-1]
|
|
394
|
+
|
|
395
|
+
_benwin = win_per_event.iloc[-1].nwin
|
|
396
|
+
bot_event_by_win = win_per_event.iloc[-1].name[-1]
|
|
397
|
+
|
|
398
|
+
win_per_event_str = win_per_event.to_string()
|
|
399
|
+
|
|
400
|
+
# Station specific window information
|
|
401
|
+
win_per_station = self.nwin(level="station")
|
|
402
|
+
avg_win_per_station = win_per_station.nwin.mean()
|
|
403
|
+
|
|
404
|
+
_tsnwin = win_per_station.iloc[0].nwin
|
|
405
|
+
top_sta_by_win = win_per_station.iloc[0].name[-1]
|
|
406
|
+
|
|
407
|
+
_bsnwin = win_per_station.iloc[-1].nwin
|
|
408
|
+
bot_sta_by_win = win_per_station.iloc[-1].name[-1]
|
|
409
|
+
|
|
410
|
+
win_per_station_str = win_per_station.to_string()
|
|
411
|
+
|
|
412
|
+
# Event specific misfit information
|
|
413
|
+
misfit_per_event = self.misfit(level="event", reset=True)
|
|
414
|
+
|
|
415
|
+
_temsft = misfit_per_event.iloc[0].misfit
|
|
416
|
+
highest_misfit_event = misfit_per_event.iloc[0].name[-1]
|
|
417
|
+
|
|
418
|
+
_bemsft = misfit_per_event.iloc[-1].misfit
|
|
419
|
+
lowest_misfit_event = misfit_per_event.iloc[-1].name[-1]
|
|
420
|
+
|
|
421
|
+
misfit_per_event_str = misfit_per_event.sort_values(
|
|
422
|
+
"misfit", ascending=False).to_string()
|
|
423
|
+
|
|
424
|
+
# Station specific misfit information
|
|
425
|
+
misfit_per_sta = self.misfit(level="station", reset=True)
|
|
426
|
+
|
|
427
|
+
_tsmsft = misfit_per_sta.iloc[0].misfit
|
|
428
|
+
highest_misfit_sta = misfit_per_sta.iloc[0].name[-1]
|
|
429
|
+
|
|
430
|
+
_bsmsft = misfit_per_sta.iloc[-1].misfit
|
|
431
|
+
lowest_misfit_sta = misfit_per_sta.iloc[-1].name[-1]
|
|
432
|
+
|
|
433
|
+
misfit_per_sta_str = misfit_per_sta.sort_values(
|
|
434
|
+
"misfit", ascending=False).to_string()
|
|
435
|
+
|
|
436
|
+
# Get windows per component for the current evaluation
|
|
437
|
+
_windows_eval = self.windows
|
|
438
|
+
win_str = ""
|
|
439
|
+
for component in self.windows.component.unique():
|
|
440
|
+
self.windows = self.isolate(component=component)
|
|
441
|
+
# Sort of a hacky way of getting what we know is a single value
|
|
442
|
+
nwin_per_comp = self.nwin().nwin.to_list()[0]
|
|
443
|
+
win_str += f"- {component}: {nwin_per_comp}\n"
|
|
444
|
+
self.windows = _windows_eval
|
|
445
|
+
|
|
446
|
+
# Restore the original windows, just incase but likely not needed?
|
|
447
|
+
self.windows = _windows
|
|
448
|
+
|
|
449
|
+
# Compile all the above information into a nice text output to be writ
|
|
450
|
+
srcrcv_summary = (
|
|
451
|
+
f"Avg windows per event: {avg_win_per_event:.2f}\n"
|
|
452
|
+
f"- Evt w/ max win: {top_event_by_win} ({_tenwin:.0f})\n"
|
|
453
|
+
f"- Evt w/ min win: {bot_event_by_win} ({_benwin:.0f})\n"
|
|
454
|
+
f"- Evt w/ max msft: {highest_misfit_event} ({_temsft:.2f})\n"
|
|
455
|
+
f"- Evt w/ min msft: {lowest_misfit_event} ({_bemsft:.2f})\n"
|
|
456
|
+
"\n"
|
|
457
|
+
f"Avg windows per sta: {avg_win_per_station:.2f}\n"
|
|
458
|
+
f"- Sta w/ max win: {top_sta_by_win} ({_tsnwin:.0f})\n"
|
|
459
|
+
f"- Sta w/ min win: {bot_sta_by_win} ({_bsnwin:.0f})\n"
|
|
460
|
+
f"- Sta w/ max msft: {highest_misfit_sta} ({_tsmsft:.2f})\n"
|
|
461
|
+
f"- Sta w/ min msft: {lowest_misfit_sta} ({_bsmsft:.2f})\n"
|
|
462
|
+
"\n"
|
|
463
|
+
f"Windows per component:\n"
|
|
464
|
+
f"{win_str}"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Header contains general information for understanding inversion
|
|
468
|
+
report = [
|
|
469
|
+
f"{'INSPECTOR REPORT':^80}",
|
|
470
|
+
f"{'SUMMARY':^80}",
|
|
471
|
+
f"{self._get_str()}",
|
|
472
|
+
f"{f'SRCRCV SUMMARY [{iter_end}{step_end}]':^80}",
|
|
473
|
+
f"{srcrcv_summary}",
|
|
474
|
+
f"{'TOTAL WINDOWS':^80}",
|
|
475
|
+
f"{self.nwin().to_string()}",
|
|
476
|
+
f"{'TOTAL MISFIT':^80}",
|
|
477
|
+
f"{self.misfit().to_string()}",
|
|
478
|
+
f"{'WINDOWS PER EVENT':^80}",
|
|
479
|
+
f"{win_per_event_str}",
|
|
480
|
+
f"{f'MISFIT PER EVENT (MEAN={mean:.2f}, {nstd}STD={std:.2f})':^80}",
|
|
481
|
+
f"{misfit_per_event_str}",
|
|
482
|
+
f"{'WINDOWS PER STATION':^80}",
|
|
483
|
+
f"{win_per_station_str}",
|
|
484
|
+
f"{f'MISFIT PER STATION':^80}",
|
|
485
|
+
f"{misfit_per_sta_str}",
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
with open(os.path.join(path_report, "inspector_report.txt"), "w") as f:
|
|
489
|
+
f.writelines(f"{line_break}".join(report))
|
|
490
|
+
|
|
239
491
|
def _get_srcrcv_from_dataset(self, ds):
|
|
240
492
|
"""
|
|
241
493
|
Get source and receiver information from dataset, this includes
|
|
@@ -336,12 +588,13 @@ class Inspector(InspectorPlotter):
|
|
|
336
588
|
# where no step count is given (e.g., iteration == 'default')
|
|
337
589
|
iters, steps = [], []
|
|
338
590
|
for iter_ in misfit_windows.list():
|
|
339
|
-
iters.append(iter_)
|
|
340
591
|
for step in misfit_windows[iter_].list():
|
|
341
592
|
# Ensure that step counts are formatted like: 's00'
|
|
342
593
|
# if not then we DONT have step counts in the dataset
|
|
343
594
|
if not step.startswith("s") and not len(step) == 3:
|
|
344
595
|
step = ""
|
|
596
|
+
|
|
597
|
+
iters.append(iter_)
|
|
345
598
|
steps.append(step)
|
|
346
599
|
|
|
347
600
|
# Pulling out important information from the windows and adj src.
|
|
@@ -377,8 +630,7 @@ class Inspector(InspectorPlotter):
|
|
|
377
630
|
window["misfit"].append(adjoint_source_eval[
|
|
378
631
|
adj_tag].parameters["misfit"])
|
|
379
632
|
except IndexError:
|
|
380
|
-
|
|
381
|
-
print(f"No matching adjoint source for {cha_id}")
|
|
633
|
+
logger.warning(f"No matching adjoint source for {cha_id}")
|
|
382
634
|
window["misfit"].append(np.nan)
|
|
383
635
|
|
|
384
636
|
# winfo keys match the keys of the Pyflex Window objects
|
|
@@ -407,35 +659,51 @@ class Inspector(InspectorPlotter):
|
|
|
407
659
|
self.windows = pd.concat([self.windows, pd.DataFrame(window)],
|
|
408
660
|
ignore_index=True)
|
|
409
661
|
|
|
410
|
-
def
|
|
662
|
+
def validate_evaluation(self, iteration, step_count, choice="final"):
|
|
411
663
|
"""
|
|
664
|
+
Provide acceptable values for 'iteration' and 'step_count' to underlying
|
|
665
|
+
functions that require it.
|
|
412
666
|
Whenever a user does not choose an iteration or step count, e.g., in
|
|
413
667
|
plotting functions, this function defines default values based on the
|
|
414
668
|
initial model (if neither given), or the last step count for a given
|
|
415
|
-
iteration (if only iteration is given). Only step count is not allowed
|
|
669
|
+
iteration (if only iteration is given). Only step count is not allowed.
|
|
670
|
+
If both iteration and step count are provided, just check that these
|
|
671
|
+
are acceptable values
|
|
416
672
|
|
|
417
673
|
:type iteration: str
|
|
418
674
|
:param iteration: chosen iteration, formatted as e.g., 'i01'
|
|
419
675
|
:type step_count: str
|
|
420
676
|
:param step_count: chosen step count, formatted as e.g., 's00'
|
|
677
|
+
:type choice: str
|
|
678
|
+
:param choice: 'initial' or 'final' to set the default behavior of
|
|
679
|
+
NoneType iteration and step_count returning either the initial
|
|
680
|
+
model or the final model evaluation
|
|
421
681
|
:rtype: tuple of str
|
|
422
682
|
:return: (iteration, step_count) default values for the iteration
|
|
423
683
|
and step_count
|
|
424
684
|
"""
|
|
425
685
|
# Default iteration and step count if None are given
|
|
426
686
|
if iteration is None and step_count is None:
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
687
|
+
if choice == "initial":
|
|
688
|
+
iteration, step_count = self.initial_model
|
|
689
|
+
elif choice == "final":
|
|
690
|
+
iteration, step_count = self.final_model
|
|
691
|
+
logger.debug(f"No iteration or step count given, defaulting to "
|
|
692
|
+
f"{choice} model: {iteration}{step_count}")
|
|
430
693
|
elif iteration and (step_count is None):
|
|
431
694
|
step_count = self.steps[iteration][-1]
|
|
432
|
-
|
|
433
|
-
|
|
695
|
+
|
|
696
|
+
logger.debug(f"No step count given, defaulting to final step count "
|
|
697
|
+
f"within given iteration: {iteration}{step_count}")
|
|
434
698
|
elif (iteration is None) and (step_count is not None):
|
|
435
699
|
raise ValueError("'step_count' cannot be provided by itself, you "
|
|
436
700
|
"must also set the variable: 'iteration'")
|
|
701
|
+
else:
|
|
702
|
+
assert (iteration in self.iterations and
|
|
703
|
+
step_count in self.steps[iteration]), \
|
|
704
|
+
f"{iteration}{step_count} does not exist in Inspector"
|
|
437
705
|
return iteration, step_count
|
|
438
|
-
|
|
706
|
+
|
|
439
707
|
def discover(self, path="./", ignore_symlinks=True):
|
|
440
708
|
"""
|
|
441
709
|
Allow the Inspector to scour through a path and find relevant files,
|
|
@@ -452,18 +720,17 @@ class Inspector(InspectorPlotter):
|
|
|
452
720
|
if ignore_symlinks:
|
|
453
721
|
dsfids = [_ for _ in dsfids if not os.path.islink(_)]
|
|
454
722
|
for i, dsfid in enumerate(dsfids):
|
|
455
|
-
|
|
456
|
-
print(f"{os.path.basename(dsfid):<25} "
|
|
457
|
-
f"{i+1:0>3}/{len(dsfids):0>3}", end="..."
|
|
458
|
-
)
|
|
723
|
+
|
|
459
724
|
try:
|
|
460
725
|
self.append(dsfid)
|
|
461
|
-
|
|
462
|
-
|
|
726
|
+
logger.info(f"{os.path.basename(dsfid):<25} "
|
|
727
|
+
f"{i + 1:0>3}/{len(dsfids):0>3}: done",
|
|
728
|
+
)
|
|
463
729
|
except KeyError as e:
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
730
|
+
logger.info(f"{os.path.basename(dsfid):<25} "
|
|
731
|
+
f"{i + 1:0>3}/{len(dsfids):0>3}: error {e}",
|
|
732
|
+
)
|
|
733
|
+
traceback.print_exc()
|
|
467
734
|
continue
|
|
468
735
|
|
|
469
736
|
return self
|
|
@@ -489,13 +756,11 @@ class Inspector(InspectorPlotter):
|
|
|
489
756
|
try:
|
|
490
757
|
self._get_windows_from_dataset(ds)
|
|
491
758
|
except AttributeError as e:
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
"missing auxiliary data")
|
|
759
|
+
logger.warning("error reading dataset: missing "
|
|
760
|
+
"auxiliary data")
|
|
495
761
|
return
|
|
496
762
|
except OSError:
|
|
497
|
-
|
|
498
|
-
print(f"error reading dataset: already open")
|
|
763
|
+
logger.warning(f"error reading dataset: already open")
|
|
499
764
|
return
|
|
500
765
|
|
|
501
766
|
def extend(self, windows):
|
|
@@ -653,7 +918,7 @@ class Inspector(InspectorPlotter):
|
|
|
653
918
|
self.sources = pd.DataFrame()
|
|
654
919
|
self.receivers = pd.DataFrame()
|
|
655
920
|
|
|
656
|
-
def isolate(self, iteration=None, step_count=None,
|
|
921
|
+
def isolate(self, iteration=None, step_count=None, event=None,
|
|
657
922
|
network=None, station=None, channel=None, component=None,
|
|
658
923
|
keys=None, exclude=None, unique_key=None):
|
|
659
924
|
"""
|
|
@@ -793,8 +1058,8 @@ class Inspector(InspectorPlotter):
|
|
|
793
1058
|
return self._event_misfit
|
|
794
1059
|
|
|
795
1060
|
# Various levels to sort the misfit by
|
|
796
|
-
group_list = ["iteration", "step", "event", "
|
|
797
|
-
"misfit"]
|
|
1061
|
+
group_list = ["iteration", "step", "event", "network", "station",
|
|
1062
|
+
"component", "misfit"]
|
|
798
1063
|
misfits = self.windows.loc[:, tuple(group_list)]
|
|
799
1064
|
|
|
800
1065
|
# Count the number of windows on a per station basis
|
|
@@ -927,8 +1192,8 @@ class Inspector(InspectorPlotter):
|
|
|
927
1192
|
|
|
928
1193
|
return minmax_dict
|
|
929
1194
|
|
|
930
|
-
def
|
|
931
|
-
|
|
1195
|
+
def compare_events(self, iteration_a=None, step_count_a=None,
|
|
1196
|
+
iteration_b=None, step_count_b=None):
|
|
932
1197
|
"""
|
|
933
1198
|
Compare the misfit and number of windows on an event by event basis
|
|
934
1199
|
between two evaluations. Provides absolute values as well as
|
|
@@ -1049,7 +1314,70 @@ class Inspector(InspectorPlotter):
|
|
|
1049
1314
|
df[f"diff_{val}"] = df[f"{val}_{final}"] - df[f"{val}_{initial}"]
|
|
1050
1315
|
|
|
1051
1316
|
return df
|
|
1317
|
+
|
|
1318
|
+
def compare_misfits(self, iteration_a=None, step_count_a=None,
|
|
1319
|
+
iteration_b=None, step_count_b=None):
|
|
1320
|
+
"""
|
|
1321
|
+
Compare the misfit values between the final and initial model for each
|
|
1322
|
+
source receiver pair. Returns differences of unscaled misfit and
|
|
1323
|
+
difference in number of windows between two evaluations.
|
|
1324
|
+
|
|
1325
|
+
.. note::
|
|
1052
1326
|
|
|
1327
|
+
See also compare_events() for a similar function but for events only
|
|
1328
|
+
|
|
1329
|
+
:type iteration_a: str
|
|
1330
|
+
:param iteration_a: initial iteration to use in comparison
|
|
1331
|
+
:type step_count_a: str
|
|
1332
|
+
:param step_count_a: initial step count to use in comparison
|
|
1333
|
+
:type iteration_b: str
|
|
1334
|
+
:param iteration_b: final iteration to use in comparison
|
|
1335
|
+
:type step_count_b: str
|
|
1336
|
+
:param step_count_b: final step count to use in comparison
|
|
1337
|
+
:rtype: pandas.core.data_frame.DataFrame
|
|
1338
|
+
:return: a data frame containing differences of misfit and number of
|
|
1339
|
+
windows between final and initial models
|
|
1340
|
+
"""
|
|
1341
|
+
iter_start, step_start = self.validate_evaluation(
|
|
1342
|
+
iteration=iteration_a, step_count=step_count_a, choice="initial"
|
|
1343
|
+
)
|
|
1344
|
+
iter_end, step_end = self.validate_evaluation(
|
|
1345
|
+
iteration=iteration_b, step_count=step_count_b, choice="final"
|
|
1346
|
+
)
|
|
1347
|
+
|
|
1348
|
+
_windows = self.windows
|
|
1349
|
+
self.windows = self.isolate(iteration=iter_start, step_count=step_start)
|
|
1350
|
+
misfit_start = self.misfit(level="station", reset=True)
|
|
1351
|
+
|
|
1352
|
+
self.windows = _windows
|
|
1353
|
+
self.windows = self.isolate(iteration=iter_end, step_count=step_end)
|
|
1354
|
+
misfit_end = self.misfit(level="station", reset=True)
|
|
1355
|
+
|
|
1356
|
+
# Merge the two making sure they match event and station
|
|
1357
|
+
sfx_start = f"{iter_start}{step_start}"
|
|
1358
|
+
sfx_end = f"{iter_end}{step_end}"
|
|
1359
|
+
df = pd.merge(misfit_start, misfit_end,
|
|
1360
|
+
on=["event", "network", "station"],
|
|
1361
|
+
suffixes=(f"_{sfx_start}", f"_{sfx_end}")
|
|
1362
|
+
)
|
|
1363
|
+
|
|
1364
|
+
# Calculate the difference between the two misfits
|
|
1365
|
+
df["diff_misfit"] = \
|
|
1366
|
+
df[f"unscaled_misfit_{sfx_end}"] - df[f"unscaled_misfit_{sfx_start}"]
|
|
1367
|
+
df["diff_nwin"] = df[f"nwin_{sfx_end}"] - df[f"nwin_{sfx_start}"]
|
|
1368
|
+
|
|
1369
|
+
# Drop the original values
|
|
1370
|
+
df.drop([f"unscaled_misfit_{sfx_end}", f"unscaled_misfit_{sfx_start}",
|
|
1371
|
+
f"misfit_{sfx_end}", f"misfit_{sfx_start}",
|
|
1372
|
+
f"nwin_{sfx_end}", f"nwin_{sfx_start}"],
|
|
1373
|
+
axis=1, inplace=True)
|
|
1374
|
+
|
|
1375
|
+
# Reset windows so User can still use the Inspector as advertised
|
|
1376
|
+
self.windows = _windows
|
|
1377
|
+
|
|
1378
|
+
# Isolate the largest misfit offenders
|
|
1379
|
+
return df.sort_values(by="diff_misfit")
|
|
1380
|
+
|
|
1053
1381
|
def filter_sources(self, lat_min=None, lat_max=None, lon_min=None,
|
|
1054
1382
|
lon_max=None, depth_min=None, depth_max=None,
|
|
1055
1383
|
mag_min=None, mag_max=None, min_start=None,
|
|
@@ -1109,6 +1437,48 @@ class Inspector(InspectorPlotter):
|
|
|
1109
1437
|
|
|
1110
1438
|
return sources
|
|
1111
1439
|
|
|
1440
|
+
def event_outliers(self, iteration=None, step_count=None, choice="misfit",
|
|
1441
|
+
nstd=1):
|
|
1442
|
+
"""
|
|
1443
|
+
Returns outliers for a given misfit measure (misfit or window number)
|
|
1444
|
+
by calculating mean and standard deviation and finding events that
|
|
1445
|
+
fall outside some integer multiple of standard deviations from the mean.
|
|
1446
|
+
Used for plotting in `event_comparison` but also useful for quickly
|
|
1447
|
+
assessing which events have anomalously low or high misfit values
|
|
1448
|
+
|
|
1449
|
+
:type iteration: str
|
|
1450
|
+
:param iteration: iteration to choose for misfit
|
|
1451
|
+
:type step_count: str
|
|
1452
|
+
:param step_count: step count to query, e.g. 's00'
|
|
1453
|
+
:type choice: str
|
|
1454
|
+
:param choice: choice of misfit value, either 'misfit' or 'nwin' or
|
|
1455
|
+
'unscaled_misfit'
|
|
1456
|
+
:type nstd: int
|
|
1457
|
+
:param nstd: number of standard deviations to set upper and lower
|
|
1458
|
+
thresholds. Defaults to 1
|
|
1459
|
+
:rtype: (Pandas.series, Pandas.series, float, float)
|
|
1460
|
+
:return: (events above upper threshold, events below lower threshold,
|
|
1461
|
+
mean, standard deviation)
|
|
1462
|
+
"""
|
|
1463
|
+
iteration, step_count = self.validate_evaluation(iteration, step_count)
|
|
1464
|
+
|
|
1465
|
+
arr = self.misfit(
|
|
1466
|
+
level="event")[choice][iteration][step_count].to_numpy()
|
|
1467
|
+
index = self.misfit(level="event")[choice][iteration][step_count]
|
|
1468
|
+
mean = np.mean(arr)
|
|
1469
|
+
std = np.std(arr)
|
|
1470
|
+
|
|
1471
|
+
upper_thresh = mean + (nstd * std)
|
|
1472
|
+
lower_thresh = mean - (nstd * std)
|
|
1473
|
+
|
|
1474
|
+
idx_upper_outliers = np.where(arr >= upper_thresh)
|
|
1475
|
+
idx_lower_outliers = np.where(arr <= lower_thresh)
|
|
1476
|
+
|
|
1477
|
+
upper_outliers = index.iloc[idx_upper_outliers]
|
|
1478
|
+
lower_outliers = index.iloc[idx_lower_outliers]
|
|
1479
|
+
|
|
1480
|
+
return upper_outliers, lower_outliers, mean, std
|
|
1481
|
+
|
|
1112
1482
|
def get_models(self):
|
|
1113
1483
|
"""
|
|
1114
1484
|
Return a sorted list of misfits which correspond to accepted models,
|
|
@@ -1222,3 +1592,31 @@ class Inspector(InspectorPlotter):
|
|
|
1222
1592
|
models.reset_index(drop=True, inplace=True)
|
|
1223
1593
|
|
|
1224
1594
|
return models
|
|
1595
|
+
|
|
1596
|
+
|
|
1597
|
+
if __name__ == "__main__":
|
|
1598
|
+
|
|
1599
|
+
|
|
1600
|
+
"""
|
|
1601
|
+
Here we define a simple command-line tool for using the Inspector. Useful
|
|
1602
|
+
for Users who have already generated the inspector, and want to quickly
|
|
1603
|
+
make figures to explore the misfit of their inversion. Must be run in the
|
|
1604
|
+
directory containing your '.csv' Inspector files. Kwargs can be passed as
|
|
1605
|
+
later arguments in the format 'key=val'
|
|
1606
|
+
|
|
1607
|
+
.. rubric::
|
|
1608
|
+
|
|
1609
|
+
$ python inspector.py <function_name> <kwarg_key=kwarg_val> ...
|
|
1610
|
+
e.g.,
|
|
1611
|
+
$ python inspector.py event_station_hist2d iteration=1 step_count=2
|
|
1612
|
+
"""
|
|
1613
|
+
import sys
|
|
1614
|
+
insp = Inspector()
|
|
1615
|
+
kwargs = {}
|
|
1616
|
+
if len(sys.argv) > 2:
|
|
1617
|
+
for arg in sys.argv[2:]:
|
|
1618
|
+
key, val = arg.split("=")
|
|
1619
|
+
kwargs[key] = val
|
|
1620
|
+
getattr(insp, sys.argv[1])(**kwargs)
|
|
1621
|
+
|
|
1622
|
+
|