Skip to content

Commit 31b67e3

Browse files
authored
Merge pull request #60 from xumi1993/dev
1.3.4
2 parents bd66f9b + a35bea6 commit 31b67e3

29 files changed

Lines changed: 337 additions & 192 deletions

.github/workflows/doc_build.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ on:
88
branches: [ docs ]
99
pull_request:
1010
branches: [ master ]
11+
workflow_call:
12+
inputs:
13+
config-path:
14+
required: true
15+
type: string
16+
secrets:
17+
token:
18+
required: true
1119

1220

1321
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
@@ -49,4 +57,4 @@ jobs:
4957
force_orphan: true
5058
cname: seispy.xumijian.me
5159
user_name: 'github-actions[bot]'
52-
user_email: 'github-actions[bot]@users.noreply.github.com'
60+
user_email: 'github-actions[bot]@users.noreply.github.com'

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,20 @@
2828

2929
Seispy is a Python module for processing seismological data and calculating Receiver Functions. The advanced functions are available to improve the Obspy.
3030

31+
## Acknowledgements
32+
33+
For the use of the Seispy package, please cite as:
34+
35+
- Xu, M. & He, J. (2023). Seispy: Python Module for Batch Calculation and Postprocessing of Receiver Functions. Seismological Research Letters, 94 (2A): 935–943. doi: https://doi.org/10.1785/0220220288
36+
37+
For 3D time-difference correction, please also consider citing:
38+
39+
- Xu, M., Huang, H., Huang, Z., Wang, P., Wang, L., Xu, M., ... & Yuan, X. (2018). Insight into the subducted Indian slab and origin of the Tengchong volcano in SE Tibet from receiver function analysis. Earth and Planetary Science Letters, 482, 567-579. doi: https://doi.org/10.1016/j.epsl.2017.11.048
40+
41+
For 2D and 3D CCP stacking, please also consider citing:
42+
43+
- Xu, M., Huang, Z., Wang, L., Xu, M., Zhang, Y., Mi, N., ... & Yuan, X. (2020). Sharp lateral Moho variations across the SE Tibetan margin and their implications for plateau growth. Journal of Geophysical Research: Solid Earth, 125(5), e2019JB018117. doi: https://doi.org/10.1029/2019JB018117
3144

32-
## Dependencies
33-
* [Python]() >= 3.8
34-
* [ObsPy](http://docs.obspy.org) >= 1.2.0
35-
* [NumPy](http://www.numpy.org/) >= 1.16
36-
* [SciPy](http://www.scipy.org/) >= 1.2.0
37-
* [matplotlib](https://matplotlib.org/) >= 3.6.0
38-
* [PySide6](https://www.riverbankcomputing.com/software/pyqt/) >= 6.3.0
39-
* [scikits.bootstrap](https://github.com/cgevans/scikits-bootstrap) >= 1.0.0
40-
4145
## Installation
4246

4347
See [Seispy documentation](https://seispy.xumijian.me/installation.html) in detail.

seispy/catalog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ def write_catalog(query, fname, format):
2525
def read_catalog_file(fname):
2626
col = ['date', 'evla', 'evlo', 'evdp', 'mag']
2727
colcata = ['year', 'month', 'day', 'hour', 'minute', 'second']
28-
data_cata = pd.read_table(fname, header=None, sep=' |\t', engine='python')
28+
data_cata = pd.read_table(fname, header=None, sep='\s+')
2929
date_cols = data_cata.loc[:, [0,1,2,4,5,6]]
3030
date_cols.columns = colcata
31-
dates = pd.to_datetime(date_cols)
31+
dates = pd.DataFrame(pd.to_datetime(date_cols))[0].apply(UTCDateTime)
3232
eq_lst = pd.concat([dates, data_cata.loc[:, 7:]], axis=1)
3333
eq_lst.columns = col
3434
return eq_lst

seispy/ccp3d.py

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import numpy as np
2-
from seispy.geo import km2deg, latlon_from, cosd, extrema, skm2srad, rad2deg
2+
from seispy.geo import km2deg, extrema, skm2srad, rad2deg
33
from seispy import distaz
44
from seispy.rfcorrect import DepModel
55
from seispy.setuplog import setuplog
@@ -8,50 +8,48 @@
88
from seispy.signal import smooth
99
from seispy.utils import check_stack_val, read_rfdep
1010
from scipy.interpolate import interp1d
11+
import pyproj
1112
import warnings
1213
import sys
1314

1415

1516
def gen_center_bin(center_lat, center_lon, len_lat, len_lon, val):
1617
"""
17-
Create spaced grid point with coordinates of the center point in the area in spherical coordinates.
18+
Create spaced grid point with coordinates of the center point in the area in spherical coordinates.
1819
19-
:param center_lat: Latitude of the center point.
20-
:type center_lat: float
21-
:param center_lon: Longitude of the center point.
22-
:type center_lon: float
23-
:param len_lat: Half length in degree along latitude axis.
24-
:type len_lat: float
25-
:param len_lon: Half length in degree along longitude axis.
26-
:type len_lon: float
27-
:param val: Interval in degree between adjacent grid point.
28-
:type val: float
29-
:return: Coordinates of Grid points.
30-
:rtype: 2-D ndarray of floats with shape (n, 2), where n is the number of grid points.
20+
:param center_lat: Latitude of the center point.
21+
:type center_lat: float
22+
:param center_lon: Longitude of the center point.
23+
:type center_lon: float
24+
:param len_lat: Half length in degree along latitude axis.
25+
:type len_lat: float
26+
:param len_lon: Half length in degree along longitude axis.
27+
:type len_lon: float
28+
:param val: Interval in degree between adjacent grid point.
29+
:type val: float
30+
:return: Coordinates of Grid points.
31+
:rtype: 2-D ndarray of floats with shape (n, 2), where n is the number of grid points.
3132
"""
32-
lats = np.arange(0, 2*len_lat, val)
33-
lons = np.arange(0, 2*len_lon, val)
34-
plat, plon = latlon_from(center_lat, center_lon, 0, 90)
35-
da = distaz(plat, plon, center_lat, center_lon)
36-
begx = -len_lon
37-
begy = -len_lat
38-
bin_loca = []
39-
bin_mat = np.zeros([lats.size, lons.size, 2])
40-
bin_map = np.zeros([lats.size, lons.size]).astype(int)
33+
ellps="WGS84"
34+
deg2km = 111.19
35+
val_m = val*deg2km*1000
36+
len_lat_m = len_lat*deg2km*1000
37+
len_lon_m = len_lon*deg2km*1000
38+
proj = pyproj.Proj(proj='aeqd', ellps=ellps, datum=ellps, lat_0=center_lat, lon_0=center_lon)
39+
dx = np.arange(-len_lon_m, len_lon_m + val_m, val_m)
40+
dy = np.arange(-len_lat_m, len_lat_m + val_m, val_m)
41+
bin_mat = np.zeros([dy.size, dx.size, 2])
42+
bin_map = np.zeros([dy.size, dx.size]).astype(int)
4143
n = 0
42-
for j in range(lats.size):
43-
delyinc = j * val + begy
44-
delt = da.delta + delyinc
45-
for i in range(lons.size):
46-
azim = da.az + (begx + i * val) / cosd(delyinc)
47-
glat, glon = latlon_from(plat, plon, azim, delt)
48-
if glon > 180:
49-
glon -= 360
44+
bin_loca = []
45+
for j, dyy in enumerate(dy):
46+
for i, dxx in enumerate(dx):
47+
glon, glat = proj(dxx, dyy, inverse=True)
5048
bin_loca.append([glat, glon])
5149
bin_mat[j, i, 0] = glat
5250
bin_mat[j, i, 1] = glon
5351
bin_map[j, i] = n
54-
n += 1
52+
n += 1
5553
return np.array(bin_loca), bin_mat, bin_map
5654

5755

seispy/ccppara.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from os.path import expanduser, join, dirname, exists
33
import configparser
44
from seispy.geo import km2deg
5+
from seispy.utils import scalar_instance
56
import warnings
67

78

@@ -40,7 +41,7 @@ def bin_radius(self):
4041

4142
@bin_radius.setter
4243
def bin_radius(self, value):
43-
if not (isinstance(value, (int, float)) or value is None):
44+
if not (scalar_instance(value) or value is None):
4445
raise TypeError('Error type of bin_radius')
4546
else:
4647
self._bin_radius = value
@@ -118,7 +119,8 @@ def ccppara(cfg_file):
118119
lon2 = cf.getfloat('line', 'profile_lon2')
119120
cpara.line = np.array([lat1, lon1, lat2, lon2])
120121
except:
121-
warnings.warn('line section not found. Setup it for ccp_profile')
122+
# warnings.warn('line section not found. Setup it for ccp_profile')
123+
pass
122124
try:
123125
# para for center bins
124126
cla = cf.getfloat('spacedbins', 'center_lat')
@@ -127,7 +129,10 @@ def ccppara(cfg_file):
127129
hllo = cf.getfloat('spacedbins', 'half_len_lon')
128130
cpara.center_bin = [cla, clo, hlla, hllo, km2deg(cpara.slide_val)]
129131
except:
130-
warnings.warn('No such section of spacedbins and line. Setup them for CCP stacking')
132+
# warnings.warn('No such section of spaced bins. Setup them for ccp3d')
133+
pass
134+
if cpara.line.size == 0 and len(cpara.center_bin) == 0:
135+
raise ValueError('Please setup line or spacedbins section')
131136
# para for depth section
132137
dep_end = cf.getfloat('depth', 'dep_end')
133138
cpara.dep_val = cf.getfloat('depth', 'dep_val')

seispy/ccpprofile.py

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,40 @@
11
import numpy as np
2-
import seispy
3-
from seispy.geo import km2deg, deg2km, latlon_from, geoproject, sind, rad2deg, skm2srad
2+
from seispy.geo import km2deg, deg2km, latlon_from, \
3+
geoproject, sind, rad2deg, skm2srad, \
4+
geo2sph, sph2geo
45
from seispy.setuplog import setuplog
56
from seispy.distaz import distaz
67
from seispy.rfcorrect import DepModel
78
from seispy.rf2depth_makedata import Station
89
from seispy.ccppara import ccppara, CCPPara
910
from scikits.bootstrap import ci
1011
from seispy.ccp3d import boot_bin_stack
11-
from seispy.utils import check_stack_val, read_rfdep, create_center_bin_profile
12+
from seispy.utils import check_stack_val, read_rfdep
13+
from scipy.interpolate import interp1d
1214
from os.path import exists, dirname, basename, join
15+
import sys
16+
17+
18+
def prof_range(lat, lon):
19+
dis = [0]
20+
for i in range(lat.size-1):
21+
dis.append(distaz(lat[i], lon[i], lat[i+1], lon[i+1]).degreesToKilometers())
22+
return np.cumsum(dis)
23+
24+
25+
def create_center_bin_profile(stations, val=5, method='linear'):
26+
if not isinstance(stations, Station):
27+
raise TypeError('Stations should be seispy.rf2depth_makedata.Station')
28+
dis_sta = prof_range(stations.stla, stations.stlo)
29+
dis_inter = np.append(np.arange(0, dis_sta[-1], val), dis_sta[-1])
30+
r, theta, phi = geo2sph(np.zeros(stations.stla.size), stations.stla, stations.stlo)
31+
# t_po = np.arange(stations.stla.size)
32+
# ip_t_po = np.linspace(0, stations.stla.size, bin_num)
33+
theta_i = interp1d(dis_sta, theta, kind=method, bounds_error=False, fill_value='extrapolate')(dis_inter)
34+
phi_i = interp1d(dis_sta, phi, kind=method, bounds_error=False, fill_value='extrapolate')(dis_inter)
35+
_, lat, lon = sph2geo(r, theta_i, phi_i)
36+
# dis = prof_range(lat, lon)
37+
return lat, lon, dis_inter
1338

1439

1540
def line_proj(lat1, lon1, lat2, lon2):
@@ -146,7 +171,9 @@ def _select_sta(self):
146171
self.logger.CCPlog.warning('{} does not in RFdepth structure'.format(sta))
147172
if self.cpara.shape == 'rect' and self.cpara.adaptive == False:
148173
self._pierce_project(self.rfdep[idx])
149-
elif self.cpara.width is None and self.cpara.shape == 'circle':
174+
elif self.cpara.shape == 'circle':
175+
if self.cpara.width is not None:
176+
self.logger.CCPlog.warning('The \'width\' will be ignored, when circle bin was set')
150177
dep_mod = DepModel(self.cpara.stack_range)
151178
x_s = np.cumsum((dep_mod.dz / dep_mod.R) / np.sqrt((1. / (skm2srad(0.085) ** 2. * (dep_mod.R / dep_mod.vs) ** -2)) - 1))
152179
dis = self.fzone[-1] + rad2deg(x_s[-1]) + 0.3
@@ -157,21 +184,19 @@ def _select_sta(self):
157184
elif self.cpara.width is not None and self.cpara.shape == 'rect':
158185
self.logger.CCPlog.info('Select stations within {} km perpendicular to the profile'.format(self.cpara.width))
159186
self.idxs = self._proj_sta(self.cpara.width)
160-
self._write_sta()
187+
for idx in self.idxs:
188+
self._pierce_project(self.rfdep[idx])
189+
if self.cpara.stack_sta_list != '':
190+
self._write_sta()
161191
else:
162-
if self.cpara.width is not None:
163-
self.logger.CCPlog.error('Width of profile was set, the bin shape of {} is invalid'.format(self.cpara.shape))
164-
raise ValueError('Width of profile was set, the bin shape of {} is invalid'.format(self.cpara.shape))
165-
else:
166-
self.logger.CCPlog.error('Width of profile was not set, the bin shape of {} is invalid'.format(self.cpara.shape))
167-
raise ValueError('Width of profile was not set, the bin shape of {} is invalid'.format(self.cpara.shape))
192+
self.logger.CCPlog.error('Width of profile was not set, the bin shape of {} is invalid'.format(self.cpara.shape))
193+
sys.exit(1)
168194

169195
def _write_sta(self):
170196
with open(self.cpara.stack_sta_list, 'w') as f:
171197
for idx in self.idxs:
172198
f.write('{}\t{:.3f}\t{:.3f}\n'.format(self.staname[idx],
173199
self.stalst[idx,0], self.stalst[idx, 1]))
174-
self._pierce_project(self.rfdep[idx])
175200

176201
def _proj_sta(self, width):
177202
az1_begin, az2_begin, az1_end, az2_end, daz = line_proj(*self.cpara.line)
@@ -192,7 +217,7 @@ def _proj_sta(self, width):
192217
final_idx = np.intersect1d(tmp_idx, proj_idx).astype(int)
193218
if not final_idx.any():
194219
self.logger.CCPlog.error('No stations within the profile belt with width of {}'.format(width))
195-
raise ValueError('Satisfied stations not found')
220+
sys.exit(1)
196221
return final_idx
197222

198223
def _pierce_project(self, rfsta):
@@ -218,7 +243,7 @@ def stack(self):
218243
bin_ci = np.zeros([self.cpara.stack_range.size, 2])
219244
bin_count = np.zeros(self.cpara.stack_range.size)
220245
self.logger.CCPlog.info('{}/{} bin from {:.2f} km at lat: {:.3f} lon: {:.3f}'.format(i + 1, self.bin_loca.shape[0], self.profile_range[i], bin_info[0], bin_info[1]))
221-
if self.cpara.width is None and self.cpara.shape == 'circle':
246+
if self.cpara.shape == 'circle' and not exists(self.cpara.stack_sta_list):
222247
idxs = self.idxs[i]
223248
else:
224249
idxs = self.idxs
@@ -262,13 +287,21 @@ def save_stack_data(self, format='npz'):
262287
for i, bin in enumerate(self.stack_data):
263288
for j, dep in enumerate(self.cpara.stack_range):
264289
if dep == self.cpara.stack_range[-1]:
265-
f.write('{:.4f}\t{:.4f}\t{:.4f}\t{:.2f} nan nan nan nan\n'.format(bin['bin_lat'], bin['bin_lon'],
266-
self.profile_range[i], dep))
290+
f.write(
291+
'{:.4f}\t{:.4f}\t{:.4f}\t{:.2f} nan nan nan nan\n'.format(
292+
bin['bin_lat'], bin['bin_lon'],
293+
self.profile_range[i], dep
294+
)
295+
)
267296
else:
268-
f.write('{:.4f}\t{:.4f}\t{:.4f}\t{:.2f}\t{:.4f}\t{:.4f}\t{:.4f}\t{:d}\n'.format(bin['bin_lat'], bin['bin_lon'],
269-
self.profile_range[i],
270-
dep, bin['mu'][j], bin['ci'][j, 0],
271-
bin['ci'][j, 1], int(bin['count'][j])))
297+
f.write(
298+
'{:.4f}\t{:.4f}\t{:.4f}\t{:.2f}\t{:.4f}\t{:.4f}\t{:.4f}\t{:d}\n'.format(
299+
bin['bin_lat'], bin['bin_lon'],
300+
self.profile_range[i],
301+
dep, bin['mu'][j], bin['ci'][j, 0],
302+
bin['ci'][j, 1], int(bin['count'][j])
303+
)
304+
)
272305

273306

274307
if __name__ == '__main__':

seispy/decon.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
# -*- coding: utf-8 -*-
22
import numpy as np
33
import obspy
4-
from obspy.io.sac import SACTrace
54
from obspy.signal.util import next_pow_2
6-
from math import pi
7-
from numpy.fft import fft, ifft, ifftshift
5+
from numpy.fft import fft, ifft
86
# from scipy.linalg import solve_toeplitz
9-
import matplotlib.pyplot as plt
107

118

129
def gaussFilter(dt, nft, f0):
1310
df = 1.0 / (nft * dt)
1411
nft21 = 0.5 * nft + 1
1512
f = df * np.arange(0, nft21)
16-
w = 2 * pi * f
13+
w = 2 * np.pi * f
1714

1815
gauss = np.zeros([nft, 1])
1916
gauss1 = np.exp(-0.25 * (w / f0) ** 2) / dt
@@ -41,9 +38,9 @@ def correl(R, W, nfft):
4138
def phaseshift(x, nfft, dt, tshift):
4239
Xf = fft(x, nfft)
4340
shift_i = int(tshift / dt)
44-
p = 2 * pi * np.arange(1, nfft + 1) * shift_i / nfft
41+
p = 2 * np.pi * np.arange(1, nfft + 1) * shift_i / nfft
4542
Xf = Xf * np.vectorize(complex)(np.cos(p), -np.sin(p))
46-
x = ifft(Xf, nfft) / np.cos(2 * pi * shift_i / nfft)
43+
x = ifft(Xf, nfft) / np.cos(2 * np.pi * shift_i / nfft)
4744
x = x.real
4845
return x
4946

@@ -164,7 +161,7 @@ def deconwater(uin, win, dt, tshift=10., wlevel=0.05, f0=2.0, normalize=False, p
164161
fny = 1. / (2.* dt); # nyquist
165162
delf = fny / (0.5 * nft)
166163
freq = delf * np.arange(nfpts)
167-
w = 2 * pi * freq
164+
w = 2 * np.pi * freq
168165

169166
# containers
170167
# rff = np.zeros(nft); # Rfn in freq domain

seispy/distaz.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def __init__(self, lat1, lon1, lat2, lon2):
153153

154154
dbaz_idx = np.where(dbaz < 0.0)[0]
155155
if len(dbaz_idx) != 0:
156-
if isinstance(dbaz, (int, float)):
156+
if isinstance(dbaz, (int, float, np.integer, np.floating)):
157157
dbaz += 2 * math.pi
158158
else:
159159
dbaz[dbaz_idx] += 2 * math.pi

seispy/eq.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from seispy.decon import RFTrace
88
from seispy.geo import snr, srad2skm, rotateSeisENtoTR, \
99
rssq, extrema
10+
from seispy.utils import scalar_instance
1011
from obspy.signal.trigger import recursive_sta_lta
1112
import glob
1213

@@ -323,7 +324,7 @@ def deconvolute(self, shift, time_after, f0=2.0, method='iter', only_r=False,
323324
Time delta for resampling, by default None
324325
"""
325326
self.method = method
326-
if isinstance(f0, (int, float)):
327+
if scalar_instance(f0):
327328
f0 = [f0]
328329
for ff in f0:
329330
if method == 'iter':

0 commit comments

Comments
 (0)