Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PySEP v0.6.0 #143

Merged
merged 31 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
150aabd
FEATURE: improves FORCESOLUTION read function to deal with SPECFEM3D_…
bch0w Aug 17, 2023
8fc5397
utils.io.read_forcesolution changed input variable 'default_time' -> …
bch0w Aug 18, 2023
0eeba9c
REFORMAT read_forcesolution to return an ObsPy Event object rather th…
bch0w Aug 18, 2023
9302fb7
added function 'read_events_plus' (originally defined in Pyatoa) whic…
bch0w Aug 23, 2023
bab4b5a
removed Source class from io.mt which was an old holdover for reading…
bch0w Aug 23, 2023
7891703
General Improvements: improve recsec log, single-source version, bugf…
bch0w Aug 28, 2023
4580670
updates RTD Yaml file to comply with build.image deprecation
bch0w Aug 29, 2023
09a8ddf
attempt fix failing RTD build by swapping Python for Mamba
bch0w Aug 29, 2023
22ab388
removed 'read_sem' logger warning about not providing an origin time …
bch0w Sep 18, 2023
0643113
added read functions to main package imports (read_sem, read_stations…
bch0w Sep 18, 2023
23d5256
Bugfix: incorrect RecSec xlim_s indexing (#123)
bch0w Sep 21, 2023
431fd54
Update RecordSection data reading (#124)
bch0w Sep 24, 2023
573da99
BUGFIX: RecSec was unable to read different source file formats. New …
bch0w Oct 13, 2023
7e53a82
BUGFIX: function 'read_specfem3d_cmtsolution_cartesian' can is now le…
bch0w Oct 13, 2023
a2b9006
removing debug statement left in read forcesolution function
bch0w Oct 16, 2023
b0daad7
#126 updating example config files to fix inconsistencies
bch0w Nov 1, 2023
8b4144c
BUGFIX: RecordSection was incorrectly using indices in units of time,…
bch0w Nov 6, 2023
8e5eed9
Update recsec.rst
bch0w Nov 8, 2023
9dab52e
pysep internal variable _legacy_naming -> legacy_naming so that it ca…
bch0w Nov 17, 2023
d6b6b4c
recsec #127 added parameters 'obs_wildcard' and 'syn_wildcard' to all…
bch0w Nov 17, 2023
7a11b63
change feature so that 'syn_wildcard' defaults to 'wildcard' unless e…
bch0w Nov 17, 2023
8428d7d
removed parameters 'log_level' and 'legacy_naming' from the default c…
bch0w Nov 20, 2023
f775e1e
update changelog
bch0w Nov 20, 2023
788186d
Merge branch 'master' into devel
bch0w Nov 22, 2023
8a47656
added read function for asdfdatasets and allow RecSec to plot windows…
bch0w Feb 29, 2024
73462a2
added test to check zero division error, (#137)
bch0w Feb 29, 2024
f6b02ab
Improve SAC header append functions (#138)
bch0w Feb 29, 2024
f7f6d2d
Improve RecSec preprocessing architecture (#139)
bch0w Feb 29, 2024
01bd274
bump version 0.6.0 and bump copyright year in docs
bch0w Apr 19, 2024
5498d4f
update changelog
bch0w Apr 19, 2024
3f5a925
slim down changelog to include only most recent
bch0w Apr 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
- Adds version release documentation
- Slightly modifies pysep-docs conda environment to accomodate converted nbooks


## Version 0.5.0
- Improves functions 'read_forcesolution' and 'read_source', which now return
`obspy.core.event.Event` objects, rather than the makeshift Source objects
Expand Down Expand Up @@ -109,6 +110,15 @@
- Removed unnused parameters 'legacy_naming' and 'log_level' from
`Pysep.write_config`


## Version 0.5.1

- Bugfix: RecSec subset streams, which checked that 'st' and 'st_syn' had the same stations, would not run for streams of the same length, leading to edge case where same length streams would plot out of order because they had not been sorted. removed the criteria and now subset streams runs at all times


## Version 0.6.0

- #136: New read function for ASDFDataSets for misfit window plotting
- #137: More control over RecSec kwargs and better warning messages
- #138: Improved SAC header creation for SPECFEM synthetics
- #139: Improved RecSec preprocessing setup, more manual control for the User
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def setup(app):
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = 'PySEP'
copyright = '2023, adjTomo Dev Team'
copyright = '2024, adjTomo Dev Team'
author = 'adjTomo Dev Team'
release = ''
# Grab version number from 'pyproject.toml'
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pysep-adjtomo"
version = "0.5.1"
version = "0.6.0"
description = "Python Seismogram Extraction and Processing"
readme = "README.md"
requires-python = ">=3.8"
Expand Down
280 changes: 192 additions & 88 deletions pysep/recsec.py

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions pysep/tests/test_recsec.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,24 @@ def test_recsec_calc_time_offset(recsec_w_synthetics):
recsec_w_synthetics.get_parameters()
for tr in recsec_w_synthetics.st:
assert(tr.stats.time_offset == -100)

def test_recsec_zero_amplitude(recsec):
"""
waveforms that have zero amplitude and are normalized should be able
to bypass normalizations which lead to weird plotting (see #131).

.. note::

This does not really test that the method is working correctly because
dividing a NumPy array by zero leads to NaNs in the array which just
won't plot. This is more of a visual test to make sure that the
zero amplitude is plotting correctly, look for green lines
"""
recsec.kwargs.scale_by = "normalize"
recsec.kwargs.obs_color = "green"
recsec.linewidth = 30
for tr in recsec.st:
tr.data *= 0
recsec.process_st()
recsec.get_parameters()
recsec.plot()
28 changes: 28 additions & 0 deletions pysep/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ def test_append_sac_headers(test_st, test_inv, test_event):
assert(st[0].stats.sac["evla"] == test_event.preferred_origin().latitude)


def test_append_sac_headers_cartesian(test_st, test_inv, test_event):
"""
Make sure we can write SAC headers correctly
"""
st = append_sac_headers(st=test_st, inv=test_inv, event=test_event)
assert(not hasattr(test_st[0].stats, "sac"))
assert(hasattr(st[0].stats, "sac"))
assert(st[0].stats.sac["evla"] == test_event.preferred_origin().latitude)


def test_event_tag_and_event_tag_legacy(test_event):
"""
Check that event tagging works as expected
Expand Down Expand Up @@ -166,6 +176,14 @@ def test_read_sem():
st += read_sem(fid=test_synthetic, source=test_cmtsolution,
stations=test_stations)
assert(st)

expected_headers = ["iztype", "b", "e", "evla", "evlo", "stla", "stlo",
"stel", "kevnm", "nzyear", "nzjday", "nzhour", "nzmin",
"nzsec", "nzmsec", "dist", "az", "baz", "gcarc",
"lpspol", "lcalda", "evdp", "mag"]
for expected_header in expected_headers:
assert(expected_header in st[0].stats.sac)

assert(st[0].stats.sac.evla == -40.5405)


Expand All @@ -182,7 +200,17 @@ def test_read_sem_cartesian():
st += read_sem(fid=test_synthetic, source=test_cmtsolution,
stations=test_stations)
assert(st)

expected_headers = ["iztype", "b", "e", "evla", "evlo", "stla", "stlo",
"kevnm", "nzyear", "nzjday", "nzhour", "nzmin",
"nzsec", "nzmsec", "dist", "az", "baz", "gcarc",
"lpspol", "lcalda", "evdp"]
for expected_header in expected_headers:
assert(expected_header in st[0].stats.sac)

assert(st[0].stats.sac.stla == 67000.0)
assert(st[0].stats.sac.evdp == 30.)
assert(st[0].stats.sac.b == -10.)

def test_estimate_prefilter_corners(test_st):
"""
Expand Down
31 changes: 23 additions & 8 deletions pysep/utils/cap_sac.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def _append_sac_headers_trace(tr, event, inv):

We explicitely set 'iztype, 'b' and 'e' in the SAC header to tell ObsPy
that the trace start is NOT the origin time. Otherwise all the relative
timing (e.g., picks) will be wrong.
timing (e.g., picks) in SAC will be wrong.

:type tr: obspy.core.trace.Trace
:param tr: Trace to append SAC header to
Expand Down Expand Up @@ -206,6 +206,7 @@ def _append_sac_headers_trace(tr, event, inv):
"lpspol": 0, # 1 if left-hand polarity (usually no in passive seis)
"lcalda": 1, # 1 if DIST, AZ, BAZ, GCARC to be calc'd from metadata
}

# Some Inventory objects will not go all the way to channel, only to station
try:
cha = sta[0]
Expand All @@ -227,6 +228,13 @@ def _append_sac_headers_trace(tr, event, inv):
except Exception: # NOQA
pass

# Warn User that the following SAC headers could not be found
_warn_about = []
for key in ["cmpinc", "cmpaz", "evdp", "mag"]:
if key not in sac_header:
_warn_about.append(key)
logger.warning(f"no SAC header values found for: {_warn_about}")

# Append SAC header and include back azimuth for rotation
tr.stats.sac = sac_header
tr.stats.back_azimuth = baz
Expand Down Expand Up @@ -297,24 +305,29 @@ def _append_sac_headers_cartesian_trace(tr, event, rcv_x, rcv_y):
:rtype: obspy.core.trace.Trace
:return: Trace with appended SAC header
"""
net_sta = f"{tr.stats.network}.{tr.stats.station}"

src_y = event.preferred_origin().latitude
src_x = event.preferred_origin().longitude
otime = event.preferred_origin().time
evdepth_km = event.preferred_origin().depth / 1E3 # units: m -> km

# Calculate Cartesian distance and azimuth/backazimuth
dist_m = np.sqrt(((rcv_x - src_x) ** 2) + ((rcv_y - src_y) ** 2))
dist_km = dist_m * 1E-3 # units: m -> km
dist_deg = kilometer2degrees(dist_km) # spherical earth approximation
azimuth = np.degrees(np.arctan2((rcv_x - src_x), (rcv_y - src_y))) % 360
backazimuth = (azimuth - 180) % 360
otime = event.preferred_origin().time

# Barebones SAC header, we only append values required by RecSec

sac_header = {
"iztype": 9, # Ref time equivalence, IB (9): Begin time
"b": tr.stats.starttime - otime, # begin time
"e": tr.stats.npts * tr.stats.delta, # end time
"stla": rcv_y,
"stlo": rcv_x,
"evla": src_y,
"evlo": src_x,
"dist": dist_m * 1E-3, # units: km
"evdp": evdepth_km,
"dist": dist_km,
"gcarc": dist_deg, # degrees
"az": azimuth,
"baz": backazimuth,
"kevnm": format_event_tag_legacy(event), # only take date code
Expand All @@ -324,7 +337,9 @@ def _append_sac_headers_cartesian_trace(tr, event, rcv_x, rcv_y):
"nzmin": otime.minute,
"nzsec": otime.second,
"nzmsec": otime.microsecond,
}
"lpspol": 0, # 1 if left-hand polarity (usually no in passive seis)
"lcalda": 1, # 1 if DIST, AZ, BAZ, GCARC to be calc'd from metadata
}

tr.stats.sac = sac_header

Expand Down
63 changes: 62 additions & 1 deletion pysep/utils/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from pysep import logger
from pysep.utils.mt import moment_magnitude, seismic_moment
from pysep.utils.fmt import format_event_tag_legacy, channel_code
from pysep.utils.fmt import channel_code
from pysep.utils.cap_sac import append_sac_headers, append_sac_headers_cartesian


Expand Down Expand Up @@ -498,6 +498,67 @@ def read_event_file(fid):
return list_out


def read_asdfdataset(path, evaluation):
"""
Read an ASDFDataSet created by a SeisFlows Pyaflowa inversion run.
The dataset should contain observed and synthetic waveforms, and
optionally contains misfit windows. Will return Streams with SAC headers

.. note::

This function makes assumptions about the PyASDF data structure which
is defined by the external package Pyatoa. If Pyatoa changes that
structure, this function will break.

:type path: str
:param path: Path to the ASDF dataset to be read
:type evaluation: str
:param evaluation: evaluation to take synthetics from. These are saved
following a format specified by Pyatoa, but usually follow the form
iteration/step_count, e.g., i01s00 gives iteration 1, step count 0.
Take a look at the waveform tags in `ASDFDataSet.waveforms[<station>]`
for tags following the 'synthetic_' prefix
"""
# PySEP, by default will not require PyASDF to be installed
try:
from pyasdf import ASDFDataSet # NOQA
except ImportError:
logger.critical("pyasdf is not installed. Please install pyasdf "
"to read ASDF datasets")
return None, None

with ASDFDataSet(path) as ds:
event = ds.events[0]
st_out = Stream()
st_syn_out = Stream()
for station in ds.waveforms.list():
inv = ds.waveforms[station].StationXML
st = ds.waveforms[station].observed
st_syn = ds.waveforms[station][f"synthetic_{evaluation}"]

st_out += append_sac_headers(st, event, inv)
st_syn_out += append_sac_headers(st_syn, event, inv)

# Read windows from the dataset
windows = {}
if hasattr(ds.auxiliary_data, "MisfitWindows"):
iter_ = evaluation[:3] # 'i01s00' -> 'i01'
step = evaluation[3:]
for station in ds.auxiliary_data.MisfitWindows[iter_][step].list():
parameters = ds.auxiliary_data.MisfitWindows[iter_][step][
station].parameters
trace_id = parameters["channel_id"]
starttime = parameters["relative_starttime"]
endtime = parameters["relative_endtime"]
# initialize empty window array
if trace_id not in windows:
windows[trace_id] = []

windows[trace_id].append((starttime, endtime))

return st_out, st_syn_out, windows


def write_sem(st, unit, path="./", time_offset=0):
"""
Write an ObsPy Stream in the two-column ASCII format that Specfem uses
Expand Down
Loading