def test_bng_waveforms(ind=0): has_data, bng = test_add_data(ind) if not has_data: pass bng.calc(1., 15., [-2., 5.]) stream = bng.data.copy() stream.filter('bandpass', freqmin=0.01, freqmax=0.04, zerophase=True) trN = stream.select(component='1')[0].copy() trE = stream.select(component='2')[0].copy() azim = bng.meta.phi N, E = rotate_rt_ne(trN.data, trE.data, azim) trN.data = -1. * N trE.data = -1. * E # Update stats of streams trN.stats.channel = trN.stats.channel[:-1] + 'N' trE.stats.channel = trE.stats.channel[:-1] + 'E' # Store corrected traces in new stream and rotate to # R, T using back-azimuth stcorr = Stream(traces=[trN, trE]) stcorr.rotate('NE->RT', back_azimuth=bng.meta.baz) # Merge original and corrected streams st = stream + stcorr plot = plotting.plot_bng_waveforms(bng, st, 15., [-2., 5.])
def test_rotate_ne_rt_ne(self): """ Rotating there and back with the same back-azimuth should not change the data. """ # load the data with gzip.open(os.path.join(self.path, 'rjob_20051006_n.gz')) as f: data_n = np.loadtxt(f) with gzip.open(os.path.join(self.path, 'rjob_20051006_e.gz')) as f: data_e = np.loadtxt(f) # Use double precision to get more accuracy for testing. data_n = np.require(data_n, np.float64) data_e = np.require(data_e, np.float64) ba = 33.3 new_n, new_e = rotate_ne_rt(data_n, data_e, ba) new_n, new_e = rotate_rt_ne(new_n, new_e, ba) self.assertTrue(np.allclose(data_n, new_n, rtol=1E-7, atol=1E-12)) self.assertTrue(np.allclose(data_e, new_e, rtol=1E-7, atol=1E-12))
def calc(self, dphi, dts, tt, bp=None, showplot=False): """ Method to estimate azimuth of component `?H1` (or `?HN`). This method minimizes the energy (RMS) of the transverse component of P-wave data within some bandwidth. Parameters ---------- dphi : float Azimuth increment for search (deg) dts : float Length of time window on either side of predicted P-wave arrival time (sec) tt : list List of two floats containing the time picks relative to P-wave time, within which to perform the estimation of station orientation (sec) bp : list List of two floats containing the low- and high-frequency corners of a bandpass filter (Hz) showplot : bool Whether or not to plot waveforms. Attributes ---------- meta.phi : float Azimuth of H1 (or HN) component (deg) meta.cc : float Cross-correlation coefficient between vertical and radial component meta.snr : float Signal-to-noise ratio of P-wave measured on the vertical seismogram meta.TR : float Measure of the transverse to radial ratio. In reality this is 1 - T/R meta.RZ : float Measure of the radial to vertical ratio. In reality this is 1 - R/Z """ # Work on a copy of the waveform data stream = self.data.copy() # Filter if specified if bp: stream.filter('bandpass', freqmin=bp[0], freqmax=bp[1], zerophase=True) # Get data and noise based on symmetric waveform wrt arrival start = stream[0].stats.starttime stnoise = stream.copy().trim(start, start + dts + tt[0]) stdata = stream.copy().trim(start + dts + tt[0], start + dts + tt[1]) # Define signal and noise tr1 = stdata.select(component='1')[0].copy() tr2 = stdata.select(component='2')[0].copy() trZ = stdata.select(component='Z')[0].copy() ntrZ = stnoise.select(component='Z')[0].copy() # Calculate and store SNR as attribute self.meta.snr = 10. * np.log10( utils.rms(trZ) * utils.rms(trZ) / utils.rms(ntrZ) / utils.rms(ntrZ)) # Search through azimuths from 0 to 180 deg and find best-fit azimuth ang = np.arange(0., 180., dphi) cc1 = np.zeros(len(ang)) cc2 = np.zeros(len(ang)) cc3 = np.zeros(len(ang)) cc4 = np.zeros(len(ang)) for k, a in enumerate(ang): R, T = rotate_ne_rt(tr1.data, tr2.data, a) covmat = np.corrcoef(R, trZ.data) cc1[k] = covmat[0, 1] cc2[k] = 1. - utils.rms(T) / utils.rms(R) cc3[k] = utils.rms(T) cc4[k] = 1. - utils.rms(R) / utils.rms(trZ.data) # Get argument of minimum of cc3 and store useful measures ia = cc3.argmin() self.meta.cc = cc1[ia] self.meta.TR = cc2[ia] self.meta.RZ = cc4[ia] # correct for angles above 360 phi = (self.meta.baz - float(ia) * dphi) # Use azimuth where CC is negative if self.meta.cc < 0.: phi += 180. if phi < 0.: phi += 360. if phi >= 360.: phi -= 360. # Store the best-fit azimuth self.meta.phi = phi # If a plot is requested, rotate Z12 to ZNE and then ZRT if showplot: # Now rotate components to proper N, E and then R, T sttmp = stream.copy() # Apply filter if defined previously if bp: sttmp.filter('bandpass', freqmin=bp[0], freqmax=bp[1], zerophase=True) # Copy traces trN = sttmp.select(component='1')[0].copy() trE = sttmp.select(component='2')[0].copy() # Rotating from 1,2 to N,E is the negative of # rotation from RT to NE, with baz corresponding # to azim of component 1, or phi previously determined azim = self.meta.phi N, E = rotate_rt_ne(trN.data, trE.data, azim) trN.data = -1. * N trE.data = -1. * E # Update stats of streams trN.stats.channel = trN.stats.channel[:-1] + 'N' trE.stats.channel = trE.stats.channel[:-1] + 'E' # Store corrected traces in new stream and rotate to # R, T using back-azimuth stcorr = Stream(traces=[trN, trE]) stcorr.rotate('NE->RT', back_azimuth=self.meta.baz) # Merge original and corrected streams st = stream + stcorr # Plot plot = plotting.plot_bng_waveforms(self, st, dts, tt) plot.show() return
def rotate(self, align=None): """ Rotates 3-component seismograms from vertical (Z), east (E) and north (N) to longitudinal (L), radial (Q) and tangential (T) components of motion. Note that the method 'rotate' from ``obspy.core.stream.Stream`` is used for the rotation ``'ZNE->ZRT'`` and ``'ZNE->LQT'``. Rotation ``'ZNE->PVH'`` is implemented separately here due to different conventions. Parameters ---------- align : str Alignment of coordinate system for rotation ('ZNE' or 'LQT') """ if not self.meta.accept: return # Use default values from meta data if arguments are not specified if not align: align = self.meta.align if align == 'ZNE': # Rotating from 1,2 to N,E is the negative of # rotation from RT to NE, with # baz corresponding to azim of component 1 from obspy.signal.rotate import rotate_rt_ne # Copy traces trZ = self.dataZ12.select(component='Z')[0].copy() trN = self.dataZ12.select(component='1')[0].copy() trE = self.dataZ12.select(component='2')[0].copy() azim = self.sta.azcorr N, E = rotate_rt_ne(trN.data, trE.data, azim) trN.data = -1.*N trE.data = -1.*E # Update stats of streams trN.stats.channel = trN.stats.channel[:-1] + 'N' trE.stats.channel = trE.stats.channel[:-1] + 'E' self.dataZNE = Stream(traces=[trZ, trN, trE]) elif align == 'LQT': data = self.dataZNE.copy() data.rotate('ZNE->LQT', back_azimuth=self.meta.baz, inclination=self.meta.inc) # for tr in data: # if tr.stats.channel.endswith('Q'): # tr.data = -tr.data self.meta.align = align self.meta.rotated = True self.dataLQT = data.copy() else: raise(Exception("incorrect 'align' argument"))
def rotate(self, vp=None, vs=None, align=None): """ Rotates 3-component seismograms from vertical (Z), east (E) and north (N) to longitudinal (L), radial (Q) and tangential (T) components of motion. Note that the method 'rotate' from ``obspy.core.stream.Stream`` is used for the rotation ``'ZNE->ZRT'`` and ``'ZNE->LQT'``. Rotation ``'ZNE->PVH'`` is implemented separately here due to different conventions. Parameters ---------- vp : float P-wave velocity at surface (km/s) vs : float S-wave velocity at surface (km/s) align : str Alignment of coordinate system for rotation ('ZRT', 'LQT', or 'PVH') Returns ------- rotated : bool Whether or not the object has been rotated """ if not self.meta.accept: return if self.meta.rotated: print("Data have been rotated already - continuing") return # Use default values from meta data if arguments are not specified if not align: align = self.meta.align if align == 'ZNE': # Rotating from 1,2 to N,E is the negative of # rotation from RT to NE, with # baz corresponding to azim of component 1 from obspy.signal.rotate import rotate_rt_ne # Copy traces trZ = self.data.select(component='Z')[0].copy() trN = self.data.select(component='1')[0].copy() trE = self.data.select(component='2')[0].copy() azim = self.sta.azcorr N, E = rotate_rt_ne(trN.data, trE.data, azim) trN.data = -1. * N trE.data = -1. * E # Update stats of streams trN.stats.channel = trN.stats.channel[:-1] + 'N' trE.stats.channel = trE.stats.channel[:-1] + 'E' self.data = Stream(traces=[trZ, trN, trE]) elif align == 'ZRT': self.data.rotate('NE->RT', back_azimuth=self.meta.baz) self.meta.align = align self.meta.rotated = True elif align == 'LQT': self.data.rotate('ZNE->LQT', back_azimuth=self.meta.baz, inclination=self.meta.inc) for tr in self.data: if tr.stats.channel.endswith('Q'): tr.data = -tr.data self.meta.align = align self.meta.rotated = True elif align == 'PVH': # First rotate to ZRT self.data.rotate('NE->RT', back_azimuth=self.meta.baz) # Copy traces trP = self.data.select(component='Z')[0].copy() trV = self.data.select(component='R')[0].copy() trH = self.data.select(component='T')[0].copy() slow = self.meta.slow if not vp: vp = self.meta.vp if not vs: vs = self.meta.vs # Vertical slownesses # P vertical slowness qp = np.sqrt(1. / vp / vp - slow * slow) # S vertical slowness qs = np.sqrt(1. / vs / vs - slow * slow) # Elements of rotation matrix m11 = slow * vs * vs / vp m12 = -(1. - 2. * vs * vs * slow * slow) / (2. * vp * qp) m21 = (1. - 2. * vs * vs * slow * slow) / (2. * vs * qs) m22 = slow * vs # Rotation matrix rot = np.array([[-m11, m12], [-m21, m22]]) # Vector of Radial and Vertical r_z = np.array([trV.data, trP.data]) # Rotation vec = np.dot(rot, r_z) # Extract P and SV, SH components trP.data = vec[0, :] trV.data = vec[1, :] trH.data = -trH.data / 2. # Update stats of streams trP.stats.channel = trP.stats.channel[:-1] + 'P' trV.stats.channel = trV.stats.channel[:-1] + 'V' trH.stats.channel = trH.stats.channel[:-1] + 'H' # Over-write data attribute self.data = Stream(traces=[trP, trV, trH]) self.meta.align = align self.meta.rotated = True else: raise (Exception("incorrect 'align' argument"))