def convolve(trace, transfer_function, pre_filt=False): # write data to a numpy array data = trace.data npts = len(data) nfft = _npts2nfft(npts) # Transform data to frequency domain data = np.fft.rfft(data, n=nfft) # find the frequencies corresponding to each element of the fft delta = trace.stats.delta fy = 1. / (delta * 2.0) # start at zero to get zero for offset/ DC of fft frequencies = np.linspace(0, fy, nfft // 2 + 1) # pre-filter the data if pre_filt: freq_domain_taper = cosine_sac_taper(frequencies, flimit=pre_filt) data *= freq_domain_taper # perform the convolution data *= transfer_function(frequencies) data[-1] = abs(data[-1]) + 0.0j # transform data back into the time domain data = np.fft.irfft(data)[0:npts] # write data back into trace trace.data = data
def pre_filter(stream, pre_filt=(0.02, 0.05, 8.0, 10.0)): """ Applies the same filter as remove_response without actually removing the response. """ for tr in stream: data = tr.data.astype(np.float64) nfft = _npts2nfft(len(data)) fy = 1.0 / (tr.stats.delta * 2.0) freqs = np.linspace(0, fy, nfft // 2 + 1) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) data *= cosine_sac_taper(freqs, flimit=pre_filt) data[-1] = abs(data[-1]) + 0.0j # transform data back into the time domain data = np.fft.irfft(data)[0:len(data)] # assign processed data and store processing information tr.data = data return stream
def filter_synt(tr, pre_filt): """Perform a frequency domain taper like during the response removal just without an actual response...""" data = tr.data.astype(numpy.float64) # Smart calculation of nfft dodging large primes # nfft = _npts2nfft(len(data)) nfft = int(0.5 * (data.size - 1) + 1) fy = 1.0 / (tr.stats.delta * 2.0) # freqs = numpy.linspace(0, fy, nfft // 2 + 1) freqs = numpy.linspace(0, fy, nfft) # Transform data to frequency domain # data = numpy.fft.rfft(data, n=nfft) data = numpy.fft.rfft(data) data *= cosine_sac_taper(freqs, flimit=pre_filt) data[-1] = abs(data[-1]) + 0.0j # Transform data back into the time domain # data = numpy.fft.irfft(data)[0:len(data)] data = numpy.fft.irfft(data, int((nfft - 1) * 2.0 + 1)) # Assign processed data and store processing information tr.data = data
def filter_synt(tr, pre_filt): """Perform a frequency domain taper like during the response removal just without an actual response...""" data = tr.data.astype(numpy.float64) # Smart calculation of nfft dodging large primes # nfft = _npts2nfft(len(data)) nfft = int(0.5 * (data.size - 1) + 1) fy = 1.0 / (tr.stats.delta * 2.0) # freqs = numpy.linspace(0, fy, nfft // 2 + 1) freqs = numpy.linspace(0, fy, nfft) # Transform data to frequency domain # data = numpy.fft.rfft(data, n=nfft) data = numpy.fft.rfft(data) data *= cosine_sac_taper(freqs, flimit=pre_filt) data[-1] = abs(data[-1]) + 0.0j # Transform data back into the time domain # data = numpy.fft.irfft(data)[0:len(data)] data = numpy.fft.irfft(data, int((nfft - 1) * 2.0 + 1)) # Assign processed data and store processing information tr.data = data
def convolve(trace, func): from obspy.signal.util import _npts2nfft from obspy.signal.invsim import cosine_sac_taper data = trace.data delta = trace.stats.delta npts = len(data) nfft = _npts2nfft(npts) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) fy = 1. / (delta * 2.0) # start at zero to get zero for offset/ DC of fft freqs = np.linspace(0, fy, nfft // 2 + 1) freq_response = func(freqs) if pre_filt: freq_domain_taper = cosine_sac_taper(freqs, flimit=pre_filt) data *= freq_domain_taper ''' if water_level is None: # No water level used, so just directly invert the response. # First entry is at zero frequency and value is zero, too. # Just do not invert the first value (and set to 0 to make sure). freq_response[0] = 0.0 freq_response[1:] = 1.0 / freq_response[1:] else: # Invert spectrum with specified water level. invert_spectrum(freq_response, water_level) ''' data *= freq_response data[-1] = abs(data[-1]) + 0.0j # transform data back into the time domain data = np.fft.irfft(data)[0:npts]
def filter_synt(tr, pre_filt): """ Perform a frequency domain taper like during the response removal just without an actual response... :param tr: :param pre_filt: :return: """ data = tr.data.astype(np.float64) # smart calculation of nfft dodging large primes nfft = _npts2nfft(len(data)) fy = 1.0 / (tr.stats.delta * 2.0) freqs = np.linspace(0, fy, nfft // 2 + 1) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) data *= cosine_sac_taper(freqs, flimit=pre_filt) data[-1] = abs(data[-1]) + 0.0j # transform data back into the time domain data = np.fft.irfft(data)[0:len(data)] # assign processed data and store processing information tr.data = data
def filter_synt(tr, pre_filt): """ Perform a frequency domain taper like during the response removal just without an actual response... :param tr: :param pre_filt: :return: """ data = tr.data.astype(np.float64) # smart calculation of nfft dodging large primes nfft = _npts2nfft(len(data)) fy = 1.0 / (tr.stats.delta * 2.0) freqs = np.linspace(0, fy, nfft // 2 + 1) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) data *= cosine_sac_taper(freqs, flimit=pre_filt) data[-1] = abs(data[-1]) + 0.0j # transform data back into the time domain data = np.fft.irfft(data)[0:len(data)] # assign processed data and store processing information tr.data = data
def filter_trace(tr, pre_filt): """ Perform a frequency domain taper mimicing the behavior during the response removal, without a actual response removal. :param tr: input trace :param pre_filt: frequency array(Hz) in ascending order, to define the four corners of filter, for example, [0.01, 0.1, 0.2, 0.5]. :type pre_filt: Numpy.array or list :return: filtered trace """ if not isinstance(tr, Trace): raise TypeError("First Argument should be trace: %s" % type(tr)) if len(pre_filt) != 4: raise ValueError("Length of filter must be 4(corner frequencies)") if not check_array_order(pre_filt, order="ascending"): raise ValueError("Frequency band should be in ascending order: %s" % pre_filt) data = tr.data.astype(np.float64) origin_len = len(data) if origin_len == 0: return # smart calculation of nfft dodging large primes nfft = _npts2nfft(len(data)) fy = 1.0 / (tr.stats.delta * 2.0) freqs = np.linspace(0, fy, nfft // 2 + 1) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) data *= cosine_sac_taper(freqs, flimit=pre_filt) data[-1] = abs(data[-1]) + 0.0j # transform data back into the time domain data = np.fft.irfft(data)[0:origin_len] # assign processed data and store processing information tr.data = data
def remove_response2(self, inventory=None, output="VEL", water_level=60, pre_filt=None, zero_mean=True, taper=True, taper_fraction=0.05, plot=False, fig=None, **kwargs): """ EDIT OF OBSPY'S REMOVE_RESPONSE FUNCTION Including titles etc. Deconvolve instrument response. Uses the adequate :class:`obspy.core.inventory.response.Response` from the provided :class:`obspy.core.inventory.inventory.Inventory` data. Raises an exception if the response is not present. Note that there are two ways to prevent overamplification while convolving the inverted instrument spectrum: One possibility is to specify a water level which represents a clipping of the inverse spectrum and limits amplification to a certain maximum cut-off value (`water_level` in dB). The other possibility is to taper the waveform data in the frequency domain prior to multiplying with the inverse spectrum, i.e. perform a pre-filtering in the frequency domain (specifying the four corner frequencies of the frequency taper as a tuple in `pre_filt`). .. note:: Any additional kwargs will be passed on to :meth:`obspy.core.inventory.response.Response.get_evalresp_response`, see documentation of that method for further customization (e.g. start/stop stage). .. note:: Using :meth:`~Trace.remove_response` is equivalent to using :meth:`~Trace.simulate` with the identical response provided as a (dataless) SEED or RESP file and when using the same `water_level` and `pre_filt` (and options `sacsim=True` and `pitsasim=False` which influence very minor details in detrending and tapering). .. rubric:: Example >>> from obspy import read, read_inventory >>> st = read() >>> tr = st[0].copy() >>> inv = read_inventory() >>> tr.remove_response(inventory=inv) # doctest: +ELLIPSIS <...Trace object at 0x...> >>> tr.plot() # doctest: +SKIP .. plot:: from obspy import read, read_inventory st = read() tr = st[0] inv = read_inventory() tr.remove_response(inventory=inv) tr.plot() Using the `plot` option it is possible to visualize the individual steps during response removal in the frequency domain to check the chosen `pre_filt` and `water_level` options to stabilize the deconvolution of the inverted instrument response spectrum: >>> from obspy import read, read_inventory >>> st = read("/path/to/IU_ULN_00_LH1_2015-07-18T02.mseed") >>> tr = st[0] >>> inv = read_inventory("/path/to/IU_ULN_00_LH1.xml") >>> pre_filt = [0.001, 0.005, 45, 50] >>> tr.remove_response(inventory=inv, pre_filt=pre_filt, output="DISP", ... water_level=60, plot=True) # doctest: +SKIP <...Trace object at 0x...> .. plot:: from obspy import read, read_inventory st = read("/path/to/IU_ULN_00_LH1_2015-07-18T02.mseed", "MSEED") tr = st[0] inv = read_inventory("/path/to/IU_ULN_00_LH1.xml", "STATIONXML") pre_filt = [0.001, 0.005, 45, 50] output = "DISP" tr.remove_response(inventory=inv, pre_filt=pre_filt, output=output, water_level=60, plot=True) :type inventory: :class:`~obspy.core.inventory.inventory.Inventory` or None. :param inventory: Station metadata to use in search for adequate response. If inventory parameter is not supplied, the response has to be attached to the trace with :meth:`Trace.attach_response` beforehand. :type output: str :param output: Output units. One of: ``"DISP"`` displacement, output unit is meters ``"VEL"`` velocity, output unit is meters/second ``"ACC"`` acceleration, output unit is meters/second**2 :type water_level: float :param water_level: Water level for deconvolution. :type pre_filt: list or tuple of four float :param pre_filt: Apply a bandpass filter in frequency domain to the data before deconvolution. The list or tuple defines the four corner frequencies `(f1, f2, f3, f4)` of a cosine taper which is one between `f2` and `f3` and tapers to zero for `f1 < f < f2` and `f3 < f < f4`. :type zero_mean: bool :param zero_mean: If `True`, the mean of the waveform data is subtracted in time domain prior to deconvolution. :type taper: bool :param taper: If `True`, a cosine taper is applied to the waveform data in time domain prior to deconvolution. :type taper_fraction: float :param taper_fraction: Taper fraction of cosine taper to use. :type plot: bool or str :param plot: If `True`, brings up a plot that illustrates how the data are processed in the frequency domain in three steps. First by `pre_filt` frequency domain tapering, then by inverting the instrument response spectrum with or without `water_level` and finally showing data with inverted instrument response multiplied on it in frequency domain. It also shows the comparison of raw/corrected data in time domain. If a `str` is provided then the plot is saved to file (filename must have a valid image suffix recognizable by matplotlib e.g. '.png'). """ # limit_numpy_fft_cache() from obspy.core.inventory import PolynomialResponseStage from obspy.signal.invsim import (cosine_taper, cosine_sac_taper, invert_spectrum) if plot: import matplotlib.pyplot as plt if (isinstance(inventory, (str, native_str)) and inventory.upper() in ("DISP", "VEL", "ACC")): from obspy.core.util.deprecation_helpers import ObsPyDeprecationWarning output = inventory inventory = None msg = ("The order of optional parameters in method " "remove_response has changed. 'output' is not accepted " "as first positional argument in the next release.") warnings.warn(msg, category=ObsPyDeprecationWarning, stacklevel=3) response = self._get_response(inventory) # polynomial response using blockette 62 stage 0 if not response.response_stages and response.instrument_polynomial: coefficients = response.instrument_polynomial.coefficients self.data = np.poly1d(coefficients[::-1])(self.data) return self # polynomial response using blockette 62 stage 1 and no other stages if len(response.response_stages) == 1 and isinstance(response.response_stages[0], PolynomialResponseStage): # check for gain if response.response_stages[0].stage_gain is None: msg = 'Stage gain not defined for %s - setting it to 1.0' warnings.warn(msg % self.id) gain = 1 else: gain = response.response_stages[0].stage_gain coefficients = response.response_stages[0].coefficients[:] for i in range(len(coefficients)): coefficients[i] /= math.pow(gain, i) self.data = np.poly1d(coefficients[::-1])(self.data) return self # use evalresp data = self.data.astype(np.float64) npts = len(data) # time domain pre-processing if zero_mean: data -= data.mean() if taper: data *= cosine_taper(npts, taper_fraction, sactaper=True, halfcosine=False) if plot: fontsz = 12 color1 = "blue" color2 = "red" bbox = dict(boxstyle="round", fc="w", alpha=1, ec="w") bbox1 = dict(boxstyle="round", fc="blue", alpha=0.15) bbox2 = dict(boxstyle="round", fc="red", alpha=0.15) if fig is None: fig = plt.figure(figsize=(14, 10)) fig.suptitle(str(self), fontsize=fontsz) ax1 = fig.add_subplot(321) ax1b = ax1.twinx() ax2 = fig.add_subplot(323, sharex=ax1) ax2b = ax2.twinx() ax3 = fig.add_subplot(325, sharex=ax1) ax3b = ax3.twinx() ax4 = fig.add_subplot(322) ax5 = fig.add_subplot(324, sharex=ax4) ax6 = fig.add_subplot(326, sharex=ax4) for ax_ in (ax1, ax2, ax3, ax4, ax5, ax6): ax_.grid(zorder=-10) if pre_filt is None: text = 'pre_filt: None' else: text = 'pre_filt: [{:.3g}, {:.3g}, {:.3g}, {:.3g}]'.format( *pre_filt) ax1.text(0.05, 0.1, text, ha="left", va="bottom", transform=ax1.transAxes, fontsize=fontsz, bbox=bbox, zorder=5) ax1.set_ylabel("Data spectrum, raw", bbox=bbox1, fontsize=fontsz) ax1b.set_ylabel("'pre_filt' taper fraction", bbox=bbox2, fontsize=fontsz) evalresp_info = "\n".join( ['output: %s' % output] + ['%s: %s' % (key, value) for key, value in kwargs.items()]) ax2.text(0.05, 0.1, evalresp_info, ha="left", va="bottom", transform=ax2.transAxes, zorder=5, bbox=bbox, fontsize=fontsz) ax2.set_ylabel("Data spectrum,\n" "'pre_filt' applied", bbox=bbox1, fontsize=fontsz) ax2b.set_ylabel("Instrument response", bbox=bbox2, fontsize=fontsz) ax3.text(0.05, 0.1, 'water_level: %s' % water_level, ha="left", va="bottom", transform=ax3.transAxes, fontsize=fontsz, zorder=5, bbox=bbox) ax3.set_ylabel("Data spectrum,\nmultiplied with inverted\n" "instrument response", bbox=bbox1, fontsize=fontsz) ax3b.set_ylabel("Inverted instrument response,\n" "water level applied", bbox=bbox2, fontsize=fontsz) ax3.set_xlabel("Frequency [Hz]", fontsize=fontsz) times = self.times() ax4.plot(times, self.data, color="k") ax4.set_ylabel("Raw", fontsize=fontsz) ax4.yaxis.set_ticks_position("right") ax4.yaxis.set_label_position("right") ax5.plot(times, data, color="k") ax5.set_ylabel("Raw, after time\ndomain pre-processing", fontsize=fontsz) ax5.yaxis.set_ticks_position("right") ax5.yaxis.set_label_position("right") ax6.set_ylabel("Response removed", fontsize=fontsz) ax6.set_xlabel("Time [s]", fontsize=fontsz) ax6.yaxis.set_ticks_position("right") ax6.yaxis.set_label_position("right") ax1.tick_params(labelsize=fontsz) ax1b.tick_params(labelsize=fontsz) ax2.tick_params(labelsize=fontsz) ax2b.tick_params(labelsize=fontsz) ax3.tick_params(labelsize=fontsz) ax3b.tick_params(labelsize=fontsz) ax4.tick_params(labelsize=fontsz) ax5.tick_params(labelsize=fontsz) ax6.tick_params(labelsize=fontsz) # smart calculation of nfft dodging large primes from obspy.signal.util import _npts2nfft nfft = _npts2nfft(npts) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) # calculate and apply frequency response, # optionally prefilter in frequency domain and/or apply water level freq_response, freqs = response.get_evalresp_response(self.stats.delta, nfft, output=output, **kwargs) if plot: ax1.loglog(freqs, np.abs(data), color=color1, zorder=9, label= "Data") ax1.legend(loc = "upper left") # frequency domain pre-filtering of data spectrum # (apply cosine taper in frequency domain) if pre_filt: freq_domain_taper = cosine_sac_taper(freqs, flimit=pre_filt) data *= freq_domain_taper if plot: try: freq_domain_taper except NameError: freq_domain_taper = np.ones(len(freqs)) ax1b.semilogx(freqs, freq_domain_taper, color=color2, zorder=10) ax1b.set_ylim(-0.05, 1.05) ax2.loglog(freqs, np.abs(data), color=color1, zorder=9, label = "Data") ax2b.loglog(freqs, np.abs(freq_response), color=color2, zorder=10, label = "Filter") ax2b.legend(loc = "upper left") if water_level is None: # No water level used, so just directly invert the response. # First entry is at zero frequency and value is zero, too. # Just do not invert the first value (and set to 0 to make sure). freq_response[0] = 0.0 freq_response[1:] = 1.0 / freq_response[1:] else: # Invert spectrum with specified water level. invert_spectrum(freq_response, water_level) data *= freq_response data[-1] = abs(data[-1]) + 0.0j if plot: ax3.loglog(freqs, np.abs(data), color=color1, zorder=9, label = "Data") ax3b.loglog(freqs, np.abs(freq_response), color=color2, zorder=10, label = "Filter") # transform data back into the time domain data = np.fft.irfft(data)[0:npts] if plot: # Oftentimes raises NumPy warnings which we don't want to see. with np.errstate(all="ignore"): ax6.plot(times, data, color="k") plt.subplots_adjust(wspace=0.4) if plot is True and fig is None: plt.show() elif plot is True and fig is not None: pass else: plt.savefig(plot) plt.close(fig) # assign processed data and store processing information self.data = data return self, np.abs(freq_response)
def remove_response(trace, inventory=None, output="VEL", water_level=60, pre_filt=None, zero_mean=True, taper=True, taper_fraction=0.05, plot=False, fig=None, return_spectra=False, **kwargs): """ Deconvolve instrument response. Based on a method on trace. It adds units, which are obviously dependent on the problem. axis ax5b is modified to show the pre-filtered trace instead of the time domain pre filter. Uses the adequate :class:`obspy.core.inventory.response.Response` from the provided :class:`obspy.core.inventory.inventory.Inventory` data. Raises an exception if the response is not present. Note that there are two ways to prevent overamplification while convolving the inverted instrument spectrum: One possibility is to specify a water level which represents a clipping of the inverse spectrum and limits amplification to a certain maximum cut-off value (`water_level` in dB). The other possibility is to taper the waveform data in the frequency domain prior to multiplying with the inverse spectrum, i.e. perform a pre-filtering in the frequency domain (specifying the four corner frequencies of the frequency taper as a tuple in `pre_filt`). .. note:: Any additional kwargs will be passed on to :meth:`obspy.core.inventory.response.Response.get_evalresp_response`, see documentation of that method for further customization (e.g. start/stop stage). .. note:: Using :meth:`~Trace.remove_response` is equivalent to using :meth:`~Trace.simulate` with the identical response provided as a (dataless) SEED or RESP file and when using the same `water_level` and `pre_filt` (and options `sacsim=True` and `pitsasim=False` which influence very minor details in detrending and tapering). .. rubric:: Example >>> from obspy import read, read_inventory >>> st = read() >>> tr = st[0].copy() >>> inv = read_inventory() >>> tr.remove_response(inventory=inv) # doctest: +ELLIPSIS <...Trace object at 0x...> >>> tr.plot() # doctest: +SKIP .. plot:: from obspy import read, read_inventory st = read() tr = st[0] inv = read_inventory() tr.remove_response(inventory=inv) tr.plot() Using the `plot` option it is possible to visualize the individual steps during response removal in the frequency domain to check the chosen `pre_filt` and `water_level` options to stabilize the deconvolution of the inverted instrument response spectrum: >>> from obspy import read, read_inventory >>> st = read("/path/to/IU_ULN_00_LH1_2015-07-18T02.mseed") >>> tr = st[0] >>> inv = read_inventory("/path/to/IU_ULN_00_LH1.xml") >>> pre_filt = [0.001, 0.005, 45, 50] >>> tr.remove_response(inventory=inv, pre_filt=pre_filt, output="DISP", ... water_level=60, plot=True) # doctest: +SKIP <...Trace object at 0x...> .. plot:: from obspy import read, read_inventory st = read("/path/to/IU_ULN_00_LH1_2015-07-18T02.mseed", "MSEED") tr = st[0] inv = read_inventory("/path/to/IU_ULN_00_LH1.xml", "STATIONXML") pre_filt = [0.001, 0.005, 45, 50] output = "DISP" tr.remove_response(inventory=inv, pre_filt=pre_filt, output=output, water_level=60, plot=True) :type inventory: :class:`~obspy.core.inventory.inventory.Inventory` or None. :param inventory: Station metadata to use in search for adequate response. If inventory parameter is not supplied, the response has to be attached to the trace with :meth:`Trace.attach_response` beforehand. :type output: str :param output: Output units. One of: ``"DISP"`` displacement, output unit is meters ``"VEL"`` velocity, output unit is meters/second ``"ACC"`` acceleration, output unit is meters/second**2 :type water_level: float :param water_level: Water level for deconvolution. :type pre_filt: list or tuple of four float :param pre_filt: Apply a bandpass filter in frequency domain to the data before deconvolution. The list or tuple defines the four corner frequencies `(f1, f2, f3, f4)` of a cosine taper which is one between `f2` and `f3` and tapers to zero for `f1 < f < f2` and `f3 < f < f4`. :type zero_mean: bool :param zero_mean: If `True`, the mean of the waveform data is subtracted in time domain prior to deconvolution. :type taper: bool :param taper: If `True`, a cosine taper is applied to the waveform data in time domain prior to deconvolution. :type taper_fraction: float :param taper_fraction: Taper fraction of cosine taper to use. :type plot: bool or str :param plot: If `True`, brings up a plot that illustrates how the data are processed in the frequency domain in three steps. First by `pre_filt` frequency domain tapering, then by inverting the instrument response spectrum with or without `water_level` and finally showing data with inverted instrument response multiplied on it in frequency domain. It also shows the comparison of raw/corrected data in time domain. If a `str` is provided then the plot is saved to file (filename must have a valid image suffix recognizable by matplotlib e.g. '.png'). """ limit_numpy_fft_cache() from obspy.core.inventory import PolynomialResponseStage from obspy.signal.invsim import (cosine_taper, cosine_sac_taper, invert_spectrum) if plot: import matplotlib.pyplot as plt if (isinstance(inventory, (str, native_str)) and inventory.upper() in ("DISP", "VEL", "ACC")): from obspy.core.util.deprecation_helpers import \ ObsPyDeprecationWarning output = inventory inventory = None msg = ("The order of optional parameters in method " "remove_response has changed. 'output' is not accepted " "as first positional argument in the next release.") warnings.warn(msg, category=ObsPyDeprecationWarning, stacklevel=3) response = trace._get_response(inventory) # polynomial response using blockette 62 stage 0 if not response.response_stages and response.instrument_polynomial: coefficients = response.instrument_polynomial.coefficients trace.data = np.poly1d(coefficients[::-1])(trace.data) return trace # polynomial response using blockette 62 stage 1 and no other stages if len(response.response_stages) == 1 and \ isinstance(response.response_stages[0], PolynomialResponseStage): # check for gain if response.response_stages[0].stage_gain is None: msg = 'Stage gain not defined for %s - setting it to 1.0' warnings.warn(msg % trace.id) gain = 1 else: gain = response.response_stages[0].stage_gain coefficients = response.response_stages[0].coefficients[:] for i in range(len(coefficients)): coefficients[i] /= math.pow(gain, i) trace.data = np.poly1d(coefficients[::-1])(trace.data) return trace # use evalresp data = trace.data.astype(np.float64) npts = len(data) # time domain pre-processing if zero_mean: data -= data.mean() if taper: data *= cosine_taper(npts, taper_fraction, sactaper=True, halfcosine=False) if plot: color1 = "blue" color2 = "red" bbox = dict(boxstyle="round", fc="w", alpha=1, ec="w") bbox1 = dict(boxstyle="round", fc="blue", alpha=0.15) bbox2 = dict(boxstyle="round", fc="red", alpha=0.15) if fig is None: fig = plt.figure(figsize=(14, 10)) fig.suptitle("{}:\nRemoving the Instrument Response".format( str(trace)), fontsize=16) ax1 = fig.add_subplot(321) ax1b = ax1.twinx() ax2 = fig.add_subplot(323, sharex=ax1) ax2b = ax2.twinx() ax3 = fig.add_subplot(325, sharex=ax1) ax3b = ax3.twinx() ax4 = fig.add_subplot(322) ax5 = fig.add_subplot(324, sharex=ax4) ax6 = fig.add_subplot(326, sharex=ax4) for ax_ in (ax1, ax2, ax3, ax4, ax5, ax6): ax_.grid(zorder=-10) ax4.set_title("Waveform") # if pre_filt is not None: # # text = 'pre_filt: None' # # else: output_dict = { 'DISP': 'Displacement', 'VEL': 'Velocity', 'ACC': 'Acceleration' } # labels - specific to Apollo data unit_dict = { 'DISP': 'DU/m', 'VEL': 'DU/(m/s)', 'ACC': r'DU/(m/s$\mathrm{^2}$)' } unit_inverse_dict = { 'DISP': 'm/DU', 'VEL': '(m/s)/DU', 'ACC': r'(m/s$\mathrm{^2}$)/DU' } unit_final_dict = { 'DISP': 'm', 'VEL': 'm/s', 'ACC': r'm/s$\mathrm{^2}$' } raw_unit = 'DU' ax1.set_ylabel("Raw [{}]".format(raw_unit), bbox=bbox1, fontsize=16) ax1.yaxis.set_label_coords(-0.13, 0.5) if pre_filt is not None: text = 'Pre-filter parameters:\n [{:.3g}, {:.3g}, {:.3g}, {:.3g}]'.format( *pre_filt) ax1.text(0.05, 0.1, text, ha="left", va="bottom", transform=ax1.transAxes, fontsize="large", bbox=bbox, zorder=5) ax1b.set_ylabel("Pre-filter", bbox=bbox2, fontsize=16) ax1b.yaxis.set_label_coords(1.14, 0.5) ax1.set_title("Spectrum", color='b') output_dict = { 'DISP': 'Displacement', 'VEL': 'Velocity', 'ACC': 'Acceleration' } evalresp_info = "\n".join( ['Output: %s' % output_dict[output]] + ['%s: %s' % (key, value) for key, value in kwargs.items()]) ax2.text(0.05, 0.1, evalresp_info, ha="left", va="bottom", transform=ax2.transAxes, fontsize="large", zorder=5, bbox=bbox) ax2.set_ylabel("Pre-processed [{}]".format(raw_unit), bbox=bbox1, fontsize=16) ax2.yaxis.set_label_coords(-0.13, 0.5) ax2b.set_ylabel("Instrument\nresponse [{}]".format(unit_dict[output]), bbox=bbox2, fontsize=16) ax2b.yaxis.set_label_coords(1.14, 0.5) if water_level is not None: ax3.text(0.05, 0.1, "Water Level: %s" % water_level, ha="left", va="bottom", transform=ax3.transAxes, fontsize="large", zorder=5, bbox=bbox) ax3.set_ylabel("Multiplied with inverted\n" "instrument response [{}]".format( unit_final_dict[output]), bbox=bbox1, fontsize=16) ax3.yaxis.set_label_coords(-0.13, 0.5) if water_level is not None: ax3b.set_ylabel("Inverted instrument response,\n" "water level applied [{}]".format( unit_inverse_dict[output]), bbox=bbox2, fontsize=16) else: ax3b.set_ylabel("Inverted instrument\nresponse [{}]".format( unit_inverse_dict[output]), bbox=bbox2, fontsize=16) ax3b.yaxis.set_label_coords(1.14, 0.5) # xlbl = ax3b.yaxis.get_label() # print(xlbl.get_position()) # # # xlbl = ax3b.yaxis.get_label() # print(xlbl.get_position()) ax3.set_xlabel("Frequency [Hz]") times = trace.times() ax4.plot(times, trace.data, color="k") ax4.set_ylabel("Raw [{}]".format(raw_unit), fontsize=16) ax4.yaxis.set_ticks_position("right") ax4.yaxis.set_label_position("right") ax4.yaxis.set_label_coords(1.14, 0.5) # Ceri # ax5.plot(times, data, color="k") ax5.set_ylabel("Pre-processed [{}]".format(raw_unit), fontsize=16) ax5.yaxis.set_ticks_position("right") ax5.yaxis.set_label_position("right") ax5.yaxis.set_label_coords(1.14, 0.5) ax6.set_ylabel("Response removed [{}]".format(unit_final_dict[output]), fontsize=16) ax6.set_xlabel("Time [s]") ax6.yaxis.set_ticks_position("right") ax6.yaxis.set_label_position("right") ax6.yaxis.set_label_coords(1.14, 0.5) # smart calculation of nfft dodging large primes from obspy.signal.util import _npts2nfft nfft = _npts2nfft(npts) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) # calculate and apply frequency response, # optionally prefilter in frequency domain and/or apply water level freq_response, freqs = \ response.get_evalresp_response(trace.stats.delta, nfft, output=output, **kwargs) if plot: ax1.loglog(freqs, np.abs(data), color=color1, zorder=9) # frequency domain pre-filtering of data spectrum # (apply cosine taper in frequency domain) if pre_filt: freq_domain_taper = cosine_sac_taper(freqs, flimit=pre_filt) data *= freq_domain_taper if plot: try: freq_domain_taper except NameError: freq_domain_taper = np.ones(len(freqs)) if pre_filt is not None: ax1b.semilogx(freqs, freq_domain_taper, color=color2, zorder=10) ax1b.set_ylim(-0.05, 1.05) ax2.loglog(freqs, np.abs(data), color=color1, zorder=9) ax2b.loglog(freqs, np.abs(freq_response), color=color2, zorder=10) # Ceri - transform data back into the time domain - just to view it filt_data = np.fft.irfft(data)[0:npts] ax5.plot(times, filt_data, color="k") if water_level is None: # No water level used, so just directly invert the response. # First entry is at zero frequency and value is zero, too. # Just do not invert the first value (and set to 0 to make sure). freq_response[0] = 0.0 freq_response[1:] = 1.0 / freq_response[1:] else: # Invert spectrum with specified water level. invert_spectrum(freq_response, water_level) data *= freq_response data[-1] = abs(data[-1]) + 0.0j spectra = np.abs(data) if plot: ax3.loglog(freqs, np.abs(data), color=color1, zorder=9) ax3b.loglog(freqs, np.abs(freq_response), color=color2, zorder=10) # transform data back into the time domain data = np.fft.irfft(data)[0:npts] if plot: # Oftentimes raises NumPy warnings which we don't want to see. with np.errstate(all="ignore"): ax6.plot(times, data, color="k") plt.subplots_adjust(wspace=0.4) if plot is True and fig is None: plt.show() elif plot is True and fig is not None: # Ceri - changed this so that the graph does actually show plt.show() else: plt.savefig(plot) plt.close(fig) # assign processed data and store processing information trace.data = data if return_spectra: return trace, freqs, spectra else: return trace
def _remove_response_trace(trace, inventory=None, output="VEL", pre_filt=None, zero_mean=True, taper=True, taper_fraction=0.05, **kwargs): response = trace._get_response(inventory) # polynomial response using blockette 62 stage 0 if not response.response_stages and response.instrument_polynomial: coefficients = response.instrument_polynomial.coefficients trace.data = np.poly1d(coefficients[::-1])(trace.data) return trace # polynomial response using blockette 62 stage 1 and no other stages if len(response.response_stages) == 1 and isinstance( response.response_stages[0], PolynomialResponseStage): # check for gain if response.response_stages[0].stage_gain is None: msg = 'Stage gain not defined for %s - setting it to 1.0' warnings.warn(msg % trace.id) gain = 1 else: gain = response.response_stages[0].stage_gain coefficients = response.response_stages[0].coefficients[:] for i in range(len(coefficients)): coefficients[i] /= np.pow(gain, i) trace.data = np.poly1d(coefficients[::-1])(trace.data) return trace # use evalresp data = trace.data.astype(np.float64) npts = len(data) # time domain pre-processing if zero_mean: data -= data.mean() if taper: data *= cosine_taper(npts, taper_fraction, sactaper=True, halfcosine=False) nfft = _npts2nfft(npts) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) # calculate and apply frequency response, # optionally prefilter in frequency domain and/or apply water level freq_response, freqs = response.get_evalresp_response(trace.stats.delta, nfft, output=output, **kwargs) if pre_filt: freq_domain_taper = cosine_sac_taper(freqs, flimit=pre_filt) data *= freq_domain_taper freq_response[0] = 0.0 freq_response[1:] = 1.0 / freq_response[1:] data *= freq_response data[-1] = abs(data[-1]) + 0.0j # transform data back into the time domain data = np.fft.irfft(data)[0:npts] # assign processed data and store processing information trace.data = data return trace
Original plot from obspy website. Modified by Lucas Sawade, June 2019 """ import matplotlib.pylab as plt import numpy as np from obspy.signal.invsim import cosine_sac_taper plt.figure(figsize=(10, 3)) freqs = np.logspace(-2.01, 0, 2000) plt.vlines([0.015, 0.03, 0.2, 0.4], -0.1, 1.3, color="#89160F") plt.semilogx(freqs, cosine_sac_taper(freqs, (0.015, 0.03, 0.2, 0.4)), lw=2, color="#4C72B0") props = { "bbox": dict(facecolor='white', edgecolor="0.5", boxstyle="square,pad=0.2"), "va": "top", "ha": "center", "color": "#89160F", "size": "large"} plt.text(0.015, 1.25, "f1", **props) plt.text(0.03, 1.25, "f2", **props) plt.text(0.2, 1.25, "f3", **props) plt.text(0.4, 1.25, "f4", **props) plt.xlim(freqs[0], freqs[-1]) plt.ylim(-0.1, 1.3)
def process_synthetics(st, freqmax, freqmin): f2 = 0.9 * freqmin f3 = 1.1 * freqmax f1 = 0.5 * f2 f4 = 2.0 * f3 pre_filt = (f1, f2, f3, f4) # Detrend and taper. st.detrend("linear") st.detrend("demean") st.taper(max_percentage=0.05, type="hann") # Perform a frequency domain taper like during the response removal # just without an actual response... # See function remove_response in trace.py from obspy. for tr in st: # Assuming displacement seismograms tr.differentiate() data = tr.data.astype(np.float64) orig_len = len(data) # smart calculation of nfft dodging large primes # noinspection PyProtectedMember from obspy.signal.util import _npts2nfft nfft = _npts2nfft(len(data)) fy = 1.0 / (tr.stats.delta * 2.0) freqs = np.linspace(0, fy, nfft // 2 + 1) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) # noinspection PyTypeChecker data *= cosine_sac_taper(freqs, flimit=pre_filt) data[-1] = abs(data[-1]) + 0.0j # transform data back into the time domain data = np.fft.irfft(data)[0:orig_len] # assign processed data and store processing information tr.data = data tr.detrend("linear") tr.detrend("demean") tr.taper(0.05, type="cosine") tr.filter("bandpass", freqmin=freqmin, freqmax=freqmax, corners=3, zerophase=True) tr.detrend("linear") tr.detrend("demean") tr.taper(0.05, type="cosine") tr.filter("bandpass", freqmin=freqmin, freqmax=freqmax, corners=3, zerophase=True) return st