def __toPick(parser, pick_el, evaluation_mode): """ """ pick = Pick() waveform = pick_el.xpath("waveform")[0] pick.waveform_id = WaveformStreamID(\ network_code=waveform.get("networkCode"), station_code=waveform.get("stationCode"), channel_code=waveform.get("channelCode"), location_code=waveform.get("locationCode")) pick.time, pick.time_errors = __toTimeQuantity(parser, pick_el, "time") pick.phase_hint = parser.xpath2obj('phaseHint', pick_el) onset = parser.xpath2obj('onset', pick_el) if onset and onset.lower() in ["emergent", "impulsive", "questionable"]: pick.onset = onset.lower() # Evaluation mode of a pick is global in the SeisHub Event file format. pick.evaluation_mode = evaluation_mode # The polarity needs to be mapped. polarity = parser.xpath2obj('polarity', pick_el) pol_map_dict = {'up': 'positive', 'positive': 'positive', 'down': 'negative', 'negative': 'negative', 'undecidable': 'undecidable'} if polarity and polarity.lower() in pol_map_dict: pick.polarity = pol_map_dict[polarity.lower()] # Convert azimuth to backazmith azimuth = __toFloatQuantity(parser, pick_el, "azimuth") if len(azimuth) == 2 and azimuth[0] and azimuth[1]: # Convert to backazimuth pick.backazimuth = (azimuth[0] + 180.0) % 360.0 pick.backzimuth_errors = azimuth[1] return pick
def test_clear_method_resets_objects(self): """ Tests that the clear() method properly resets all objects. Test for #449. """ # Test with basic event object. e = Event(force_resource_id=False) e.comments.append(Comment(text="test")) e.event_type = "explosion" self.assertEqual(len(e.comments), 1) self.assertEqual(e.event_type, "explosion") e.clear() self.assertEqual(e, Event(force_resource_id=False)) self.assertEqual(len(e.comments), 0) self.assertEqual(e.event_type, None) # Test with pick object. Does not really fit in the event test case but # it tests the same thing... p = Pick() p.comments.append(Comment(text="test")) p.phase_hint = "p" self.assertEqual(len(p.comments), 1) self.assertEqual(p.phase_hint, "p") # Add some more random attributes. These should disappear upon # cleaning. p.test_1 = "a" p.test_2 = "b" self.assertEqual(p.test_1, "a") self.assertEqual(p.test_2, "b") p.clear() self.assertEqual(len(p.comments), 0) self.assertEqual(p.phase_hint, None) self.assertFalse(hasattr(p, "test_1")) self.assertFalse(hasattr(p, "test_2"))
def test_clear_method_resets_objects(self): """ Tests that the clear() method properly resets all objects. Test for #449. """ # Test with basic event object. e = Event(force_resource_id=False) e.comments.append(Comment(text="test")) e.event_type = "explosion" self.assertEqual(len(e.comments), 1) self.assertEqual(e.event_type, "explosion") e.clear() self.assertEqual(e, Event(force_resource_id=False)) self.assertEqual(len(e.comments), 0) self.assertEqual(e.event_type, None) # Test with pick object. Does not really fit in the event test case but # it tests the same thing... p = Pick() p.comments.append(Comment(text="test")) p.phase_hint = "p" self.assertEqual(len(p.comments), 1) self.assertEqual(p.phase_hint, "p") # Add some more random attributes. These should disappear upon # cleaning. with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") p.test_1 = "a" p.test_2 = "b" # two warnings should have been issued by setting non-default keys self.assertEqual(len(w), 2) self.assertEqual(p.test_1, "a") self.assertEqual(p.test_2, "b") p.clear() self.assertEqual(len(p.comments), 0) self.assertEqual(p.phase_hint, None) self.assertFalse(hasattr(p, "test_1")) self.assertFalse(hasattr(p, "test_2"))
def xcorr_pick_family(family, stream, shift_len=0.2, min_cc=0.4, min_cc_from_mean_cc_factor=None, horizontal_chans=['E', 'N', '1', '2'], vertical_chans=['Z'], cores=1, interpolate=False, plot=False, plotdir=None, export_cc=False, cc_dir=None): """ Compute cross-correlation picks for detections in a family. :type family: `eqcorrscan.core.match_filter.family.Family` :param family: Family to calculate correlation picks for. :type stream: `obspy.core.stream.Stream` :param stream: Data stream containing data for all (or a subset of) detections in the Family :type shift_len: float :param shift_len: Shift length allowed for the pick in seconds, will be plus/minus this amount - default=0.2 :type min_cc: float :param min_cc: Minimum cross-correlation value to be considered a pick, default=0.4. :type min_cc_from_mean_cc_factor: float :param min_cc_from_mean_cc_factor: If set to a value other than None, then the minimum cross-correlation value for a trace is set individually for each detection based on: min(detect_val / n_chans * min_cc_from_mean_cc_factor, min_cc). :type horizontal_chans: list :param horizontal_chans: List of channel endings for horizontal-channels, on which S-picks will be made. :type vertical_chans: list :param vertical_chans: List of channel endings for vertical-channels, on which P-picks will be made. :type cores: int :param cores: Number of cores to use in parallel processing, defaults to one. :type interpolate: bool :param interpolate: Interpolate the correlation function to achieve sub-sample precision. :type plot: bool :param plot: To generate a plot for every detection or not, defaults to False :type plotdir: str :param plotdir: Path to plotting folder, plots will be output here. :type export_cc: bool :param export_cc: To generate a binary file in NumPy for every detection or not, defaults to False :type cc_dir: str :param cc_dir: Path to saving folder, NumPy files will be output here. :return: Dictionary of picked events keyed by detection id. """ picked_dict = {} delta = family.template.st[0].stats.delta detect_streams_dict = _prepare_data(family=family, detect_data=stream, shift_len=shift_len) detection_ids = list(detect_streams_dict.keys()) detect_streams = [ detect_streams_dict[detection_id] for detection_id in detection_ids ] if len(detect_streams) == 0: Logger.warning("No appropriate data found, check your family and " "detections - make sure seed ids match") return picked_dict if len(detect_streams) != len(family): Logger.warning("Not all detections have matching data. " "Proceeding anyway. HINT: Make sure SEED IDs match") # Correlation function needs a list of streams, we need to maintain order. ccc, chans = _concatenate_and_correlate(streams=detect_streams, template=family.template.st, cores=cores) for i, detection_id in enumerate(detection_ids): detection = [d for d in family.detections if d.id == detection_id][0] correlations = ccc[i] if export_cc: os.makedirs(cc_dir, exist_ok=True) fname = f"{detection_id}-cc.npy" np.save(os.path.join(cc_dir, f'{fname}'), correlations) Logger.info(f"Saved correlation statistic to {fname} (lag_calc)") picked_chans = chans[i] detect_stream = detect_streams_dict[detection_id] checksum, cccsum, used_chans = 0.0, 0.0, 0 event = Event() if min_cc_from_mean_cc_factor is not None: cc_thresh = min( detection.detect_val / detection.no_chans * min_cc_from_mean_cc_factor, min_cc) Logger.info('Setting minimum cc-threshold for detection %s to %s', detection.id, str(cc_thresh)) else: cc_thresh = min_cc for correlation, stachan in zip(correlations, picked_chans): if not stachan.used: continue tr = detect_stream.select(station=stachan.channel[0], channel=stachan.channel[1])[0] if interpolate: shift, cc_max = _xcorr_interp(correlation, dt=delta) else: cc_max = np.amax(correlation) shift = np.argmax(correlation) * delta if np.isnan(cc_max): # pragma: no cover Logger.error( 'Problematic trace, no cross correlation possible') continue picktime = tr.stats.starttime + shift checksum += cc_max used_chans += 1 if cc_max < cc_thresh: Logger.debug('Correlation of {0} is below threshold, not ' 'using'.format(cc_max)) continue cccsum += cc_max phase = None if stachan.channel[1][-1] in vertical_chans: phase = 'P' elif stachan.channel[1][-1] in horizontal_chans: phase = 'S' _waveform_id = WaveformStreamID(seed_string=tr.id) event.picks.append( Pick(waveform_id=_waveform_id, time=picktime, method_id=ResourceIdentifier('EQcorrscan'), phase_hint=phase, creation_info='eqcorrscan.core.lag_calc', evaluation_mode='automatic', comments=[Comment(text='cc_max={0}'.format(cc_max))])) event.resource_id = ResourceIdentifier(detection_id) event.comments.append(Comment(text="detect_val={0}".format(cccsum))) # Add template-name as comment to events event.comments.append( Comment(text="Detected using template: {0}".format( family.template.name))) if used_chans == detection.no_chans: # pragma: no cover if detection.detect_val is not None and\ checksum - detection.detect_val < -(0.3 * detection.detect_val): msg = ('lag-calc has decreased cccsum from %f to %f - ' % (detection.detect_val, checksum)) Logger.error(msg) continue else: Logger.warning( 'Cannot check if cccsum is better, used {0} channels for ' 'detection, but {1} are used here'.format( detection.no_chans, used_chans)) picked_dict.update({detection_id: event}) if plot: # pragma: no cover for i, event in enumerate(picked_dict.values()): if len(event.picks) == 0: continue plot_stream = detect_streams[i].copy() template_plot = family.template.st.copy() pick_stachans = [(pick.waveform_id.station_code, pick.waveform_id.channel_code) for pick in event.picks] for tr in plot_stream: if (tr.stats.station, tr.stats.channel) \ not in pick_stachans: plot_stream.remove(tr) for tr in template_plot: if (tr.stats.station, tr.stats.channel) \ not in pick_stachans: template_plot.remove(tr) if plotdir is not None: if not os.path.isdir(plotdir): os.makedirs(plotdir) savefile = "{plotdir}/{rid}.png".format( plotdir=plotdir, rid=event.resource_id.id) plot_repicked(template=template_plot, picks=event.picks, det_stream=plot_stream, show=False, save=True, savefile=savefile) else: plot_repicked(template=template_plot, picks=event.picks, det_stream=plot_stream, show=True) return picked_dict
def amp_pick_event(event, st, respdir, chans=['Z'], var_wintype=True, winlen=0.9, pre_pick=0.2, pre_filt=True, lowcut=1.0, highcut=20.0, corners=4, min_snr=1.0, plot=False, remove_old=False, ps_multiplier=0.34, velocity=False): """ Pick amplitudes for local magnitude for a single event. Looks for maximum peak-to-trough amplitude for a channel in a stream, and picks this amplitude and period. There are a few things it does internally to stabilise the result: 1. Applies a given filter to the data - very necessary for small magnitude earthquakes; 2. Keeps track of the poles and zeros of this filter and removes them from the picked amplitude; 3. Picks the peak-to-trough amplitude, but records half of this: the specification for the local magnitude is to use a peak amplitude on a horizontal, however, with modern digital seismometers, the peak amplitude often has an additional, DC-shift applied to it, to stabilise this, and to remove possible issues with de-meaning data recorded during the wave-train of an event (e.g. the mean may not be the same as it would be for longer durations), we use half the peak-to-trough amplitude; 4. Despite the original definition of local magnitude requiring the use of a horizontal channel, more recent work has shown that the vertical channels give more consistent magnitude estimations between stations, due to a reduction in site-amplification effects, we therefore use the vertical channels by default, but allow the user to chose which channels they deem appropriate; 5. We do not specify that the maximum amplitude should be the S-phase: The original definition holds that the maximum body-wave amplitude should be used - while this is often the S-phase, we do not discriminate against the P-phase. We do note that, unless the user takes care when assigning winlen and filters, they may end up with amplitude picks for surface waves; 6. We use a variable window-length by default that takes into account P-S times if available, this is in an effort to include only the body waves. When P-S times are not available we us the ps_multiplier variable, which defaults to 0.34 x hypocentral distance. :type event: obspy.core.event.event.Event :param event: Event to pick :type st: obspy.core.stream.Stream :param st: Stream associated with event :type respdir: str :param respdir: Path to the response information directory :type chans: list :param chans: List of the channels to pick on, defaults to ['Z'] - should just be the orientations, e.g. Z, 1, 2, N, E :type var_wintype: bool :param var_wintype: If True, the winlen will be multiplied by the P-S time if both P and S picks are available, otherwise it will be multiplied by the hypocentral distance*ps_multiplier, defaults to True :type winlen: float :param winlen: Length of window, see above parameter, if var_wintype is False then this will be in seconds, otherwise it is the multiplier to the p-s time, defaults to 0.9. :type pre_pick: float :param pre_pick: Time before the s-pick to start the cut window, defaults to 0.2. :type pre_filt: bool :param pre_filt: To apply a pre-filter or not, defaults to True :type lowcut: float :param lowcut: Lowcut in Hz for the pre-filter, defaults to 1.0 :type highcut: float :param highcut: Highcut in Hz for the pre-filter, defaults to 20.0 :type corners: int :param corners: Number of corners to use in the pre-filter :type min_snr: float :param min_snr: Minimum signal-to-noise ratio to allow a pick - see note below on signal-to-noise ratio calculation. :type plot: bool :param plot: Turn plotting on or off. :type remove_old: bool :param remove_old: If True, will remove old amplitude picks from event and overwrite with new picks. Defaults to False. :type ps_multiplier: float :param ps_multiplier: A p-s time multiplier of hypocentral distance - defaults to 0.34, based on p-s ratio of 1.68 and an S-velocity 0f 1.5km/s, deliberately chosen to be quite slow. :type velocity: bool :param velocity: Whether to make the pick in velocity space or not. Original definition of local magnitude used displacement of Wood-Anderson, MLv in seiscomp and Antelope uses a velocity measurement. :returns: Picked event :rtype: :class:`obspy.core.event.Event` .. Note:: Signal-to-noise ratio is calculated using the filtered data by dividing the maximum amplitude in the signal window (pick window) by the normalized noise amplitude (taken from the whole window supplied). .. Warning:: Works in place on data - will filter and remove response from data, you are recommended to give this function a copy of the data if you are using it in a loop. """ # Convert these picks into a lists stations = [] # List of stations channels = [] # List of channels picktimes = [] # List of pick times picktypes = [] # List of pick types picks_out = [] try: depth = _get_origin(event).depth except MatchFilterError: depth = 0 if remove_old and event.amplitudes: for amp in event.amplitudes: # Find the pick and remove it too pick = [p for p in event.picks if p.resource_id == amp.pick_id][0] event.picks.remove(pick) event.amplitudes = [] for pick in event.picks: if pick.phase_hint in ['P', 'S']: picks_out.append(pick) # Need to be able to remove this if there # isn't data for a station! stations.append(pick.waveform_id.station_code) channels.append(pick.waveform_id.channel_code) picktimes.append(pick.time) picktypes.append(pick.phase_hint) if len(picktypes) == 0: warnings.warn('No P or S picks found') st.merge() # merge the data, just in case! # For each station cut the window uniq_stas = list(set(stations)) for sta in uniq_stas: for chan in chans: print('Working on ' + sta + ' ' + chan) tr = st.select(station=sta, channel='*' + chan) if not tr: warnings.warn( 'There is no station and channel match in the wavefile!') continue else: tr = tr[0] # Apply the pre-filter if pre_filt: try: tr.split().detrend('simple').merge(fill_value=0) except: print('Some issue splitting this one') dummy = tr.split() dummy.detrend('simple') tr = dummy.merge(fill_value=0) try: tr.filter('bandpass', freqmin=lowcut, freqmax=highcut, corners=corners) except NotImplementedError: print('For some reason trace is not continuous:') print(tr) continue # Find the response information resp_info = _find_resp(tr.stats.station, tr.stats.channel, tr.stats.network, tr.stats.starttime, tr.stats.delta, respdir) PAZ = [] seedresp = [] if resp_info and 'gain' in resp_info: PAZ = resp_info elif resp_info: seedresp = resp_info # Simulate a Wood Anderson Seismograph if PAZ and len(tr.data) > 10: # Set ten data points to be the minimum to pass tr = _sim_WA(tr, PAZ, None, 10, velocity=velocity) elif seedresp and len(tr.data) > 10: tr = _sim_WA(tr, None, seedresp, 10, velocity=velocity) elif len(tr.data) > 10: warnings.warn('No PAZ for ' + tr.stats.station + ' ' + tr.stats.channel + ' at time: ' + str(tr.stats.starttime)) continue sta_picks = [i for i in range(len(stations)) if stations[i] == sta] pick_id = event.picks[sta_picks[0]].resource_id arrival = [ arrival for arrival in event.origins[0].arrivals if arrival.pick_id == pick_id ][0] hypo_dist = np.sqrt( np.square(degrees2kilometers(arrival.distance)) + np.square(depth / 1000)) if var_wintype and hypo_dist: if 'S' in [picktypes[i] for i in sta_picks] and\ 'P' in [picktypes[i] for i in sta_picks]: # If there is an S-pick we can use this :D s_pick = [ picktimes[i] for i in sta_picks if picktypes[i] == 'S' ] s_pick = min(s_pick) p_pick = [ picktimes[i] for i in sta_picks if picktypes[i] == 'P' ] p_pick = min(p_pick) try: tr.trim(starttime=s_pick - pre_pick, endtime=s_pick + (s_pick - p_pick) * winlen) except ValueError: continue elif 'S' in [picktypes[i] for i in sta_picks]: s_pick = [ picktimes[i] for i in sta_picks if picktypes[i] == 'S' ] s_pick = min(s_pick) p_modelled = s_pick - (hypo_dist * ps_multiplier) try: tr.trim(starttime=s_pick - pre_pick, endtime=s_pick + (s_pick - p_modelled) * winlen) except ValueError: continue else: # In this case we only have a P pick p_pick = [ picktimes[i] for i in sta_picks if picktypes[i] == 'P' ] p_pick = min(p_pick) s_modelled = p_pick + (hypo_dist * ps_multiplier) print('P_pick=%s' % str(p_pick)) print('hypo_dist: %s' % str(hypo_dist)) print('S modelled=%s' % str(s_modelled)) try: tr.trim(starttime=s_modelled - pre_pick, endtime=s_modelled + (s_modelled - p_pick) * winlen) print(tr) except ValueError: continue # Work out the window length based on p-s time or distance elif 'S' in [picktypes[i] for i in sta_picks]: # If the window is fixed we still need to find the start time, # which can be based either on the S-pick (this elif), or # on the hypocentral distance and the P-pick # Take the minimum S-pick time if more than one S-pick is # available s_pick = [ picktimes[i] for i in sta_picks if picktypes[i] == 'S' ] s_pick = min(s_pick) try: tr.trim(starttime=s_pick - pre_pick, endtime=s_pick + winlen) except ValueError: continue else: # In this case, there is no S-pick and the window length is # fixed we need to calculate an expected S_pick based on the # hypocentral distance, this will be quite hand-wavey as we # are not using any kind of velocity model. p_pick = [ picktimes[i] for i in sta_picks if picktypes[i] == 'P' ] print(picktimes) p_pick = min(p_pick) s_modelled = p_pick + hypo_dist * ps_multiplier try: tr.trim(starttime=s_modelled - pre_pick, endtime=s_modelled + winlen) except ValueError: continue if len(tr.data) <= 10: warnings.warn('No data found for: ' + tr.stats.station) continue # Get the amplitude try: amplitude, period, delay = _max_p2t(tr.data, tr.stats.delta) except ValueError: print('No amplitude picked for tr %s' % str(tr)) continue # Calculate the normalized noise amplitude noise_amplitude = np.sqrt(np.mean(np.square(tr.data))) if amplitude == 0.0: continue if amplitude / noise_amplitude < min_snr: print('Signal to noise ratio of %s is below threshold.' % (amplitude / noise_amplitude)) continue if plot: plt.plot(np.arange(len(tr.data)), tr.data, 'k') plt.scatter(tr.stats.sampling_rate * delay, amplitude / 2) plt.scatter(tr.stats.sampling_rate * (delay + period), -amplitude / 2) plt.show() print('Amplitude picked: ' + str(amplitude)) print('Signal-to-noise ratio is: %s' % (amplitude / noise_amplitude)) # Note, amplitude should be in meters at the moment! # Remove the pre-filter response if pre_filt: # Generate poles and zeros for the filter we used earlier: this # is how the filter is designed in the convenience methods of # filtering in obspy. z, p, k = iirfilter(corners, [ lowcut / (0.5 * tr.stats.sampling_rate), highcut / (0.5 * tr.stats.sampling_rate) ], btype='band', ftype='butter', output='zpk') filt_paz = { 'poles': list(p), 'zeros': list(z), 'gain': k, 'sensitivity': 1.0 } amplitude /= ( paz_2_amplitude_value_of_freq_resp(filt_paz, 1 / period) * filt_paz['sensitivity']) if PAZ: amplitude /= 1000 if seedresp: # Seedresp method returns mm amplitude *= 1000000 # Write out the half amplitude, approximately the peak amplitude as # used directly in magnitude calculations amplitude *= 0.5 # Append an amplitude reading to the event _waveform_id = WaveformStreamID(station_code=tr.stats.station, channel_code=tr.stats.channel, network_code=tr.stats.network) pick_ind = len(event.picks) event.picks.append( Pick(waveform_id=_waveform_id, phase_hint='IAML', polarity='undecidable', time=tr.stats.starttime + delay, evaluation_mode='automatic')) if not velocity: event.amplitudes.append( Amplitude(generic_amplitude=amplitude / 1e9, period=period, pick_id=event.picks[pick_ind].resource_id, waveform_id=event.picks[pick_ind].waveform_id, unit='m', magnitude_hint='ML', type='AML', category='point')) else: event.amplitudes.append( Amplitude(generic_amplitude=amplitude / 1e9, period=period, pick_id=event.picks[pick_ind].resource_id, waveform_id=event.picks[pick_ind].waveform_id, unit='m/s', magnitude_hint='ML', type='AML', category='point')) return event
def test_write_with_extra_tags_and_read(self): """ Tests that a QuakeML file with additional custom "extra" tags gets written correctly and that when reading it again the extra tags are parsed correctly. """ filename = os.path.join(self.path, "quakeml_1.2_origin.xml") with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") cat = _read_quakeml(filename) self.assertEqual(len(w), 0) # add some custom tags to first event: # - tag with explicit namespace but no explicit ns abbreviation # - tag without explicit namespace (gets obspy default ns) # - tag with explicit namespace and namespace abbreviation my_extra = AttribDict( { "public": { "value": False, "namespace": "http://some-page.de/xmlns/1.0", "attrib": {"some_attrib": "some_value", "another_attrib": "another_value"}, }, "custom": {"value": "True", "namespace": "http://test.org/xmlns/0.1"}, "new_tag": {"value": 1234, "namespace": "http://test.org/xmlns/0.1"}, "tX": {"value": UTCDateTime("2013-01-02T13:12:14.600000Z"), "namespace": "http://test.org/xmlns/0.1"}, "dataid": {"namespace": "http://anss.org/xmlns/catalog/0.1", "type": "attribute", "value": "00999999"}, # some nested tags : "quantity": { "namespace": "http://some-page.de/xmlns/1.0", "attrib": {"attrib1": "attrib_value1", "attrib2": "attrib_value2"}, "value": { "my_nested_tag1": {"namespace": "http://some-page.de/xmlns/1.0", "value": 1.23e10}, "my_nested_tag2": {"namespace": "http://some-page.de/xmlns/1.0", "value": False}, }, }, } ) nsmap = {"ns0": "http://test.org/xmlns/0.1", "catalog": "http://anss.org/xmlns/catalog/0.1"} cat[0].extra = my_extra.copy() # insert a pick with an extra field p = Pick() p.extra = {"weight": {"value": 2, "namespace": "http://test.org/xmlns/0.1"}} cat[0].picks.append(p) with NamedTemporaryFile() as tf: tmpfile = tf.name # write file cat.write(tmpfile, format="QUAKEML", nsmap=nsmap) # check contents with open(tmpfile, "rb") as fh: # enforce reproducible attribute orders through write_c14n obj = etree.fromstring(fh.read()).getroottree() buf = io.BytesIO() obj.write_c14n(buf) buf.seek(0, 0) content = buf.read() # check namespace definitions in root element expected = [ b"<q:quakeml", b'xmlns:catalog="http://anss.org/xmlns/catalog/0.1"', b'xmlns:ns0="http://test.org/xmlns/0.1"', b'xmlns:ns1="http://some-page.de/xmlns/1.0"', b'xmlns:q="http://quakeml.org/xmlns/quakeml/1.2"', b'xmlns="http://quakeml.org/xmlns/bed/1.2"', ] for line in expected: self.assertIn(line, content) # check additional tags expected = [ b"<ns0:custom>True</ns0:custom>", b"<ns0:new_tag>1234</ns0:new_tag>", b"<ns0:tX>2013-01-02T13:12:14.600000Z</ns0:tX>", b"<ns1:public " b'another_attrib="another_value" ' b'some_attrib="some_value">false</ns1:public>', ] for line in expected: self.assertIn(line, content) # now, read again to test if it's parsed correctly.. cat = _read_quakeml(tmpfile) # when reading.. # - namespace abbreviations should be disregarded # - we always end up with a namespace definition, even if it was # omitted when originally setting the custom tag # - custom namespace abbreviations should attached to Catalog self.assertTrue(hasattr(cat[0], "extra")) def _tostr(x): if isinstance(x, bool): if x: return str("true") else: return str("false") elif isinstance(x, AttribDict): for key, value in x.items(): x[key].value = _tostr(value["value"]) return x else: return str(x) for key, value in my_extra.items(): my_extra[key]["value"] = _tostr(value["value"]) self.assertEqual(cat[0].extra, my_extra) self.assertTrue(hasattr(cat[0].picks[0], "extra")) self.assertEqual(cat[0].picks[0].extra, {"weight": {"value": "2", "namespace": "http://test.org/xmlns/0.1"}}) self.assertTrue(hasattr(cat, "nsmap")) self.assertEqual(getattr(cat, "nsmap")["ns0"], nsmap["ns0"])
def cross_net(stream, env=False, debug=0, master=False): """ Generate picks using a simple envelope cross-correlation. Picks are made for each channel based on optimal moveout defined by maximum cross-correlation with master trace. Master trace will be the first trace in the stream if not set. Requires good inter-station coherance. :type stream: obspy.core.stream.Stream :param stream: Stream to pick :type env: bool :param env: To compute cross-correlations on the envelope or not. :type debug: int :param debug: Debug level from 0-5 :type master: obspy.core.trace.Trace :param master: Trace to use as master, if False, will use the first trace in stream. :returns: :class:`obspy.core.event.event.Event` .. rubric:: Example >>> from obspy import read >>> from eqcorrscan.utils.picker import cross_net >>> st = read() >>> event = cross_net(st, env=True) >>> print(event.creation_info.author) EQcorrscan .. warning:: This routine is not designed for accurate picking, rather it can be used for a first-pass at picks to obtain simple locations. Based on the waveform-envelope cross-correlation method. """ event = Event() event.origins.append(Origin()) event.creation_info = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) event.comments.append(Comment(text='cross_net')) samp_rate = stream[0].stats.sampling_rate if not env: if debug > 2: print('Using the raw data') st = stream.copy() st.resample(samp_rate) else: st = stream.copy() if debug > 2: print('Computing envelope') for tr in st: tr.resample(samp_rate) tr.data = envelope(tr.data) if debug > 2: st.plot(equal_scale=False, size=(800, 600)) if not master: master = st[0] else: master = master master.data = np.nan_to_num(master.data) for i, tr in enumerate(st): tr.data = np.nan_to_num(tr.data) if debug > 2: msg = ' '.join(['Comparing', tr.stats.station, tr.stats.channel, 'with the master']) print(msg) shift_len = int(0.3 * len(tr)) if debug > 2: print('Shift length is set to ' + str(shift_len) + ' samples') if debug > 3: index, cc, cc_vec = xcorr(master, tr, shift_len, full_xcorr=True) cc_vec = np.nan_to_num(cc_vec) if debug > 4: print(cc_vec) fig = plt.figure() ax1 = fig.add_subplot(211) x = np.linspace(0, len(master) / samp_rate, len(master)) ax1.plot(x, master.data / float(master.data.max()), 'k', label='Master') ax1.plot(x + (index / samp_rate), tr.data / float(tr.data.max()), 'r', label='Slave shifted') ax1.legend(loc="lower right", prop={'size': "small"}) ax1.set_xlabel("time [s]") ax1.set_ylabel("norm. amplitude") ax2 = fig.add_subplot(212) print(len(cc_vec)) x = np.linspace(0, len(cc_vec) / samp_rate, len(cc_vec)) ax2.plot(x, cc_vec, label='xcorr') # ax2.set_ylim(-1, 1) # ax2.set_xlim(0, len(master)) plt.show() index, cc = xcorr(master, tr, shift_len) wav_id = WaveformStreamID(station_code=tr.stats.station, channel_code=tr.stats.channel, network_code=tr.stats.network) event.picks.append(Pick(time=tr.stats.starttime + (index / tr.stats.sampling_rate), waveform_id=wav_id, phase_hint='S', onset='emergent')) if debug > 2: print(event.picks[i]) event.origins[0].time = min([pick.time for pick in event.picks]) - 1 event.origins[0].latitude = float('nan') event.origins[0].longitude = float('nan') # Set arbitrary origin time del st return event
def _parse_record_p(self, line, event): """ Parses the 'primary phase record' P The primary phase is the first phase of the reading, regardless its type. """ station = line[2:7].strip() phase = line[7:15] arrival_time = line[15:24] residual = self._float(line[25:30]) # unused: residual_flag = line[30] distance = self._float(line[32:38]) # degrees azimuth = self._float(line[39:44]) backazimuth = round(azimuth % -360 + 180, 1) mb_period = self._float(line[44:48]) mb_amplitude = self._float(line[48:55]) # nanometers mb_magnitude = self._float(line[56:59]) # unused: mb_usage_flag = line[59] origin = event.origins[0] evid = event.resource_id.id.split('/')[-1] waveform_id = WaveformStreamID() waveform_id.station_code = station # network_code is required for QuakeML validation waveform_id.network_code = ' ' station_string = \ waveform_id.get_seed_string()\ .replace(' ', '-').replace('.', '_').lower() prefix = '/'.join( (res_id_prefix, 'waveformstream', evid, station_string)) waveform_id.resource_uri = ResourceIdentifier(prefix=prefix) pick = Pick() prefix = '/'.join((res_id_prefix, 'pick', evid, station_string)) pick.resource_id = ResourceIdentifier(prefix=prefix) date = origin.time.strftime('%Y%m%d') pick.time = UTCDateTime(date + arrival_time) # Check if pick is on the next day: if pick.time < origin.time: pick.time += timedelta(days=1) pick.waveform_id = waveform_id pick.backazimuth = backazimuth onset = phase[0] if onset == 'e': pick.onset = 'emergent' phase = phase[1:] elif onset == 'i': pick.onset = 'impulsive' phase = phase[1:] elif onset == 'q': pick.onset = 'questionable' phase = phase[1:] pick.phase_hint = phase.strip() event.picks.append(pick) if mb_amplitude is not None: amplitude = Amplitude() prefix = '/'.join((res_id_prefix, 'amp', evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = mb_amplitude * 1E-9 amplitude.unit = 'm' amplitude.period = mb_period amplitude.type = 'AB' amplitude.magnitude_hint = 'Mb' amplitude.pick_id = pick.resource_id amplitude.waveform_id = pick.waveform_id event.amplitudes.append(amplitude) station_magnitude = StationMagnitude() prefix = '/'.join( (res_id_prefix, 'stationmagntiude', evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = origin.resource_id station_magnitude.mag = mb_magnitude # station_magnitude.mag_errors['uncertainty'] = 0.0 station_magnitude.station_magnitude_type = 'Mb' station_magnitude.amplitude_id = amplitude.resource_id station_magnitude.waveform_id = pick.waveform_id res_id = '/'.join( (res_id_prefix, 'magnitude/generic/body_wave_magnitude')) station_magnitude.method_id = \ ResourceIdentifier(id=res_id) event.station_magnitudes.append(station_magnitude) arrival = Arrival() prefix = '/'.join((res_id_prefix, 'arrival', evid, station_string)) arrival.resource_id = ResourceIdentifier(prefix=prefix) arrival.pick_id = pick.resource_id arrival.phase = pick.phase_hint arrival.azimuth = azimuth arrival.distance = distance arrival.time_residual = residual res_id = '/'.join((res_id_prefix, 'earthmodel/ak135')) arrival.earth_model_id = ResourceIdentifier(id=res_id) origin.arrivals.append(arrival) origin.quality.minimum_distance = min( d for d in (arrival.distance, origin.quality.minimum_distance) if d is not None) origin.quality.maximum_distance = \ max(arrival.distance, origin.quality.minimum_distance) origin.quality.associated_phase_count += 1 return pick, arrival
def setEventData(eventParser, arrivals, count): global originCount global eventCount global pickCount creation_info = CreationInfo( author='niket_engdahl_parser', creation_time=UTCDateTime(), agency_uri=ResourceIdentifier(id='smi:engdahl.ga.gov.au/ga-engdahl'), agency_id='ga-engdahl') # magnitudeSurface = Magnitude(resource_id=ResourceIdentifier(id='smi:engdahl.ga.gov.au/origin/'+str(originCount)+'#netMag.Ms'), # mag=eventParser.ms, # magnitude_type='Ms', # origin_id=ResourceIdentifier(id='smi:engdahl.ga.gov.au/origin/'+str(originCount)), # azimuthal_gap=eventParser.openaz2, # creation_info=creation_info) origin = Origin( resource_id=ResourceIdentifier(id='smi:engdahl.ga.gov.au/origin/' + str(originCount)), time=UTCDateTime(int(str(2000 + int(eventParser.iyr))), int(eventParser.mon), int(eventParser.iday), int(eventParser.ihr), int(eventParser.min), int(eventParser.sec.split('.')[0]), int(eventParser.sec.split('.')[1] + '0')), longitude=eventParser.glon, latitude=eventParser.glat, depth=float(eventParser.depth) * 1000, # engdahl files report kms, obspy expects m depth_errors=eventParser.sedep, method_id=ResourceIdentifier(id='EHB'), earth_model_id=ResourceIdentifier(id='ak135'), quality=OriginQuality(associated_phase_count=len(arrivals), used_phase_count=len(arrivals), standard_error=eventParser.se, azimuthal_gap=eventParser.openaz2), evaluation_mode='automatic', creation_info=creation_info) magnitude = Magnitude( resource_id=ResourceIdentifier(id='smi:engdahl.ga.gov.au/origin/' + str(originCount) + '#netMag.Mb'), mag=eventParser.mb, magnitude_type='Mb', origin_id=ResourceIdentifier(id='smi:engdahl.ga.gov.au/origin/' + str(originCount)), azimuthal_gap=eventParser.openaz1, creation_info=creation_info) originCount += 1 pickList = [] arrivalList = [] pPhaseArrival = None for arrParser in arrivals: pickOnset = None pol = None if arrParser.year and arrParser.month and arrParser.day and arrParser.station: pPhaseArrival = arrParser else: arrParser.year = pPhaseArrival.year arrParser.day = pPhaseArrival.day arrParser.month = pPhaseArrival.month arrParser.station = pPhaseArrival.station arrParser.delta = pPhaseArrival.delta arrParser.dtdd = pPhaseArrival.dtdd arrParser.backaz = pPhaseArrival.backaz arrParser.focalDip = pPhaseArrival.focalDip arrParser.angleAzimuth = pPhaseArrival.angleAzimuth if arrParser.phase1 == 'LR' or arrParser.phase2 == 'LR' or arrParser.hour == '24': continue if arrParser.phase1.startswith('i'): pickOnset = PickOnset.impulsive if arrParser.fm == '+': pol = PickPolarity.positive elif arrParser.fm == '-': pol = PickPolarity.negative elif arrParser.phase1.startswith('e'): pickOnset = PickOnset.emergent pick = Pick( resource_id=ResourceIdentifier(id='smi:engdahl.ga.gov.au/pick/' + str(pickCount)), time=UTCDateTime(int(str(2000 + int(arrParser.year))), int(arrParser.month), int(arrParser.day), int(arrParser.hour), int(arrParser.minute), int(arrParser.second.split('.')[0]), int(arrParser.second.split('.')[1] + '0')), waveform_id=WaveformStreamID(network_code='', station_code=arrParser.station, channel_code='BHZ'), methodID=ResourceIdentifier('STA/LTA'), backazimuth=arrParser.backaz if arrParser.backaz else None, onset=pickOnset, phase_hint=arrParser.phase, polarity=pol, evaluation_mode='automatic', # TO-DO comment='populate all the remaining fields here as key value', creation_info=creation_info) if not arrParser.backaz: print "arrParser.backaz is empty. printing the arrParser for debugging" pickCount += 1 pickList.append(pick) arrival = Arrival( pick_id=ResourceIdentifier(id='smi:engdahl.ga.gov.au/pick/' + str(pickCount - 1)), phase=arrParser.phase if arrParser.phase else None, azimuth=arrParser.backaz if arrParser.backaz else None, distance=arrParser.delta if arrParser.delta else None, # if the * has some significance, it should be accounted for. ignoring for now. time_residual=arrParser.residual.rstrip('*'), time_weight=arrParser.wgt if arrParser.wgt else None, backazimuth_weight=arrParser.wgt if arrParser.wgt else None) arrivalList.append(arrival) if not arrParser.wgt: print "arrParser.wgt is empty. printing the arrParser for debugging" # pprint.pprint(arrParser) origin.arrivals = arrivalList event = Event(resource_id=ResourceIdentifier( id='smi:engdahl.ga.gov.au/event/' + str(eventCount)), creation_info=creation_info, event_type='earthquake') eventCount += 1 event.picks = pickList event.origins = [ origin, ] event.magnitudes = [ magnitude, ] event.preferred_origin_id = origin.resource_id event.preferred_magnitude_id = magnitude.resource_id return event
def _read_single_hypocenter(lines, coordinate_converter, original_picks, **kwargs): """ Given a list of lines (starting with a 'NLLOC' line and ending with a 'END_NLLOC' line), parse them into an Event. """ try: # some paranoid checks.. assert lines[0].startswith("NLLOC ") assert lines[-1].startswith("END_NLLOC") for line in lines[1:-1]: assert not line.startswith("NLLOC ") assert not line.startswith("END_NLLOC") except Exception: msg = ("This should not have happened, please report this as a bug at " "https://github.com/obspy/obspy/issues.") raise Exception(msg) indices_phases = [None, None] for i, line in enumerate(lines): if line.startswith("PHASE "): indices_phases[0] = i elif line.startswith("END_PHASE"): indices_phases[1] = i # extract PHASES lines (if any) if any(indices_phases): if not all(indices_phases): msg = ("NLLOC HYP file seems corrupt, 'PHASE' block is corrupt.") raise RuntimeError(msg) i1, i2 = indices_phases lines, phases_lines = lines[:i1] + lines[i2 + 1:], lines[i1 + 1:i2] else: phases_lines = [] lines = dict([line.split(None, 1) for line in lines[:-1]]) line = lines["SIGNATURE"] line = line.rstrip().split('"')[1] signature, version, date, time = line.rsplit(" ", 3) # new NLLoc > 6.0 seems to add prefix 'run:' before date if date.startswith('run:'): date = date[4:] signature = signature.strip() creation_time = UTCDateTime.strptime(date + time, str("%d%b%Y%Hh%Mm%S")) if coordinate_converter: # maximum likelihood origin location in km info line line = lines["HYPOCENTER"] x, y, z = coordinate_converter(*map(float, line.split()[1:7:2])) else: # maximum likelihood origin location lon lat info line line = lines["GEOGRAPHIC"] y, x, z = map(float, line.split()[8:13:2]) # maximum likelihood origin time info line line = lines["GEOGRAPHIC"] year, mon, day, hour, min = map(int, line.split()[1:6]) seconds = float(line.split()[6]) time = UTCDateTime(year, mon, day, hour, min, seconds, strict=False) # distribution statistics line line = lines["STATISTICS"] covariance_xx = float(line.split()[7]) covariance_yy = float(line.split()[13]) covariance_zz = float(line.split()[17]) stats_info_string = str( "Note: Depth/Latitude/Longitude errors are calculated from covariance " "matrix as 1D marginal (Lon/Lat errors as great circle degrees) " "while OriginUncertainty min/max horizontal errors are calculated " "from 2D error ellipsoid and are therefore seemingly higher compared " "to 1D errors. Error estimates can be reconstructed from the " "following original NonLinLoc error statistics line:\nSTATISTICS " + lines["STATISTICS"]) # goto location quality info line line = lines["QML_OriginQuality"].split() (assoc_phase_count, used_phase_count, assoc_station_count, used_station_count, depth_phase_count) = map(int, line[1:11:2]) stderr, az_gap, sec_az_gap = map(float, line[11:17:2]) gt_level = line[17] min_dist, max_dist, med_dist = map(float, line[19:25:2]) # goto location quality info line line = lines["QML_OriginUncertainty"] if "COMMENT" in lines: comment = lines["COMMENT"].strip() comment = comment.strip('\'"') comment = comment.strip() hor_unc, min_hor_unc, max_hor_unc, hor_unc_azim = \ map(float, line.split()[1:9:2]) # assign origin info event = Event() o = Origin() event.origins = [o] event.preferred_origin_id = o.resource_id o.origin_uncertainty = OriginUncertainty() o.quality = OriginQuality() ou = o.origin_uncertainty oq = o.quality o.comments.append(Comment(text=stats_info_string, force_resource_id=False)) event.comments.append(Comment(text=comment, force_resource_id=False)) # SIGNATURE field's first item is LOCSIG, which is supposed to be # 'Identification of an individual, institiution or other entity' # according to # http://alomax.free.fr/nlloc/soft6.00/control.html#_NLLoc_locsig_ # so use it as author in creation info event.creation_info = CreationInfo(creation_time=creation_time, version=version, author=signature) o.creation_info = CreationInfo(creation_time=creation_time, version=version, author=signature) # negative values can appear on diagonal of covariance matrix due to a # precision problem in NLLoc implementation when location coordinates are # large compared to the covariances. o.longitude = x try: o.longitude_errors.uncertainty = kilometer2degrees(sqrt(covariance_xx)) except ValueError: if covariance_xx < 0: msg = ("Negative value in XX value of covariance matrix, not " "setting longitude error (epicentral uncertainties will " "still be set in origin uncertainty).") warnings.warn(msg) else: raise o.latitude = y try: o.latitude_errors.uncertainty = kilometer2degrees(sqrt(covariance_yy)) except ValueError: if covariance_yy < 0: msg = ("Negative value in YY value of covariance matrix, not " "setting longitude error (epicentral uncertainties will " "still be set in origin uncertainty).") warnings.warn(msg) else: raise o.depth = z * 1e3 # meters! o.depth_errors.uncertainty = sqrt(covariance_zz) * 1e3 # meters! o.depth_errors.confidence_level = 68 o.depth_type = str("from location") o.time = time ou.horizontal_uncertainty = hor_unc ou.min_horizontal_uncertainty = min_hor_unc ou.max_horizontal_uncertainty = max_hor_unc # values of -1 seem to be used for unset values, set to None for field in ("horizontal_uncertainty", "min_horizontal_uncertainty", "max_horizontal_uncertainty"): if ou.get(field, -1) == -1: ou[field] = None else: ou[field] *= 1e3 # meters! ou.azimuth_max_horizontal_uncertainty = hor_unc_azim ou.preferred_description = str("uncertainty ellipse") ou.confidence_level = 68 # NonLinLoc in general uses 1-sigma (68%) level oq.standard_error = stderr oq.azimuthal_gap = az_gap oq.secondary_azimuthal_gap = sec_az_gap oq.used_phase_count = used_phase_count oq.used_station_count = used_station_count oq.associated_phase_count = assoc_phase_count oq.associated_station_count = assoc_station_count oq.depth_phase_count = depth_phase_count oq.ground_truth_level = gt_level oq.minimum_distance = kilometer2degrees(min_dist) oq.maximum_distance = kilometer2degrees(max_dist) oq.median_distance = kilometer2degrees(med_dist) # go through all phase info lines for line in phases_lines: line = line.split() arrival = Arrival() o.arrivals.append(arrival) station = line[0] channel = line[2] phase = line[4] arrival.phase = phase arrival.distance = kilometer2degrees(float(line[21])) arrival.azimuth = float(line[23]) arrival.takeoff_angle = float(line[24]) arrival.time_residual = float(line[16]) arrival.time_weight = float(line[17]) pick = Pick() # have to split this into ints for overflow to work correctly date, hourmin, sec = map(str, line[6:9]) ymd = [int(date[:4]), int(date[4:6]), int(date[6:8])] hm = [int(hourmin[:2]), int(hourmin[2:4])] t = UTCDateTime(*(ymd + hm), strict=False) + float(sec) pick.time = t # network codes are not used by NonLinLoc, so they can not be known # when reading the .hyp file.. if an inventory is provided, a lookup # is done widargs = _resolve_seedid(station=station, component=channel, time=t, phase=phase, unused_kwargs=True, **kwargs) wid = WaveformStreamID(*widargs) pick.waveform_id = wid pick.time_errors.uncertainty = float(line[10]) pick.phase_hint = phase pick.onset = ONSETS.get(line[3].lower(), None) pick.polarity = POLARITIES.get(line[5].lower(), None) # try to determine original pick for each arrival for pick_ in original_picks: wid = pick_.waveform_id if station == wid.station_code and phase == pick_.phase_hint: pick = pick_ break else: # warn if original picks were specified and we could not associate # the arrival correctly if original_picks: msg = ("Could not determine corresponding original pick for " "arrival. " "Falling back to pick information in NonLinLoc " "hypocenter-phase file.") warnings.warn(msg) event.picks.append(pick) arrival.pick_id = pick.resource_id event.scope_resource_ids() return event
def simulate_trace( self, max_amplitude: float = 1e-4, width: float = 0.2, noise: bool = False, ) -> Tuple[Trace, Event, float, float]: """ Make a wood-anderson trace and convolve it with a response. """ from scipy.signal import ricker # Make dummy data in meters on Wood Anderson np.random.seed(42) if noise: data = np.random.randn(int(self.sampling_rate * self.length)) else: data = np.zeros(int(self.sampling_rate * self.length)) wavelet = ricker( int(self.sampling_rate * self.length), a=(width * self.sampling_rate / 4)) wavelet /= wavelet.max() wavelet *= max_amplitude vals = sorted([(val, ind) for ind, val in enumerate(wavelet)], key=lambda x: x[0]) period = abs(vals[0][1] - vals[1][1]) / self.sampling_rate half_max = (wavelet.max() - wavelet.min()) / 2 data += wavelet # Make dummy trace tr = Trace(data=data, header=dict( station=self.inv[0][0].code, network=self.inv[0].code, location=self.inv[0][0][0].location_code, channel=self.inv[0][0][0].code, sampling_rate=self.sampling_rate, starttime=UTCDateTime(2020, 1, 1))) tr = tr.detrend() # Remove Wood-Anderson response and simulate seismometer resp = self.inv[0][0][0].response paz = resp.get_paz() paz = { 'poles': paz.poles, 'zeros': paz.zeros, 'gain': paz.normalization_factor, 'sensitivity': resp.instrument_sensitivity.value, } tr = tr.simulate( paz_remove=PAZ_WA, paz_simulate=paz) # Make an event mid_point = tr.stats.starttime + 0.5 * ( tr.stats.endtime - tr.stats.starttime) event = Event(picks=[ Pick(phase_hint="P", time=mid_point - 3.0, waveform_id=WaveformStreamID( network_code=tr.stats.network, station_code=tr.stats.station, location_code=tr.stats.location, channel_code=tr.stats.channel)), Pick(phase_hint="S", time=mid_point, waveform_id=WaveformStreamID( network_code=tr.stats.network, station_code=tr.stats.station, location_code=tr.stats.location, channel_code=tr.stats.channel))]) return tr, event, period, half_max
def _map_join2phase(self, db): """ Return an obspy Arrival and Pick from an dict of CSS key/values corresponding to one record. See the 'Join' section for the implied database table join expected. Inputs ====== db : dict of key/values of CSS fields related to the phases (see Join) Returns ======= obspy.core.event.Pick, obspy.core.event.Arrival Notes ===== Any object that supports the dict 'get' method can be passed as input, e.g. OrderedDict, custom classes, etc. Join ---- assoc <- arrival <- affiliation (outer) <- schanloc [sta chan] (outer) """ p = Pick() p.time = _utc(db.get('time')) def_net = self.agency[:2].upper() css_sta = db.get('sta') css_chan = db.get('chan') p.waveform_id = WaveformStreamID( station_code = db.get('fsta') or css_sta, channel_code = db.get('fchan') or css_chan, network_code = db.get('snet') or def_net, location_code = db.get('loc'), ) p.horizontal_slowness = db.get('slow') #p.horizontal_slowness_errors = self._create_dict(db, 'delslo') p.backazimuth = db.get('azimuth') #p.backazimuth_errors = self._create_dict(db, 'delaz') on_qual = _str(db.get('qual')).lower() if 'i' in on_qual: p.onset = "impulsive" elif 'e' in on_qual: p.onset = "emergent" elif 'w' in on_qual: p.onset = "questionable" else: p.onset = None p.phase_hint = db.get('iphase') pol = _str(db.get('fm')).lower() if 'c' in pol or 'u' in pol: p.polarity = "positive" elif 'd' in pol or 'r' in pol: p.polarity = "negative" elif '.' in pol: p.polarity = "undecidable" else: p.polarity = None p.evaluation_mode = "automatic" if 'orbassoc' not in _str(db.get('auth')): p.evaluation_mode = "manual" p.evaluation_status = "preliminary" if p.evaluation_mode is "manual": p.evaluation_status = "reviewed" p.creation_info = CreationInfo( version = db.get('arid'), creation_time = _utc(db.get('arrival.lddate')), agency_id = self.agency, author = db.get('auth'), ) p.resource_id = self._rid(p) a = Arrival() a.pick_id = ResourceIdentifier(str(p.resource_id), referred_object=p) a.phase = db.get('phase') a.azimuth = db.get('esaz') a.distance = db.get('delta') a.takeoff_angle = db.get('ema') #a.takeoff_angle_errors = self._create_dict(db, 'emares') a.time_residual = db.get('timeres') a.horizontal_slowness_residual = db.get('slores') a.time_weight = db.get('wgt') a.earth_model_id = ResourceIdentifier(self._prefix+'/VelocityModel/'+_str(db.get('vmodel'))) a.creation_info = CreationInfo( version = db.get('arid'), creation_time = _utc(db.get('lddate')), agency_id = self.agency, ) a.extra = {} a.extra['timedef'] = { 'value': _str(db.get('timedef')), 'namespace': CSS_NAMESPACE } a.resource_id = self._rid(a) return p, a
def sactoevent(st, debug=0): """ Convert SAC headers (picks only) to obspy event class. Picks are taken from header values a, t[0-9]. :type st: obspy.core.stream.Stream :param st: Stream of waveforms including SAC headers. :type debug: int :param debug: Debug level, larger number = more output. :returns: Event with picks taken from SAC headers. :rtype: :class:`obspy.core.event.event.Event` .. note:: This functionality is not supported for obspy versions below 1.0.0 as reference times are not read in by SACIO, which are needed for defining pick times. .. note:: Takes the event origin information from the first trace in the stream - to ensure this works as you expect, please populate the evla, evlo, evdp and nzyear, nzjday, nzhour, nzmin, nzsec, nzmsec for all traces with the same values. >>> from obspy import read >>> # Get the path to the test data >>> import eqcorrscan >>> import os >>> TEST_PATH = os.path.dirname(eqcorrscan.__file__) + '/tests/test_data' >>> st = read(TEST_PATH + '/SAC/2014p611252/*') >>> event = sactoevent(st) >>> print(event.origins[0].time) 2014-08-15T03:55:21.057000Z >>> print(event.picks[0].phase_hint) S """ # Check the version _version_check() # Set the default SAC nan values float_nan = -12345.0 if not isinstance(st, Stream): msg = ('st must be a stream object, if you have just read in ' + 'multiple SAC files you may have a list of streams, convert ' + 'using: st = Stream([tr[0] for tr in st])') raise ValueError(msg) # Note, don't do this internally as we need to ensure that we are not # taking traces from other events, the user should check this. for tr in st: if not tr.stats._format == 'SAC': msg = ('%s.%s is not SAC formatted.' % (tr.stats.station, tr.stats.channel)) raise ValueError(msg) # Now we need to create an event! event = Event() event.origins.append(Origin()) # print(st[0].stats.sac.keys()) event.origins[0].time = UTCDateTime(year=st[0].stats.sac.nzyear, julday=st[0].stats.sac.nzjday, hour=st[0].stats.sac.nzhour, minute=st[0].stats.sac.nzmin, second=st[0].stats.sac.nzsec, microsecond=st[0].stats.sac.nzmsec * 1000) try: event.origins[0].latitude = st[0].stats.sac.evla event.origins[0].longitude = st[0].stats.sac.evlo event.origins[0].depth = st[0].stats.sac.evdp # Catch filled with 12345.0 as nan if event.origins[0].latitude == float_nan: event.origins[0].latitude = None if event.origins[0].longitude == float_nan: event.origins[0].longitude = None if event.origins[0].depth == float_nan: event.origins[0].depth = None except KeyError: event.origins[0].latitude = None event.origins[0].longitude = None event.origins[0].depth = None except AttributeError: event.origins[0].latitude = None event.origins[0].longitude = None event.origins[0].depth = None # Add in the picks for tr in st: reference_time = UTCDateTime(year=tr.stats.sac.nzyear, julday=tr.stats.sac.nzjday, hour=tr.stats.sac.nzhour, minute=tr.stats.sac.nzmin, second=tr.stats.sac.nzsec, microsecond=tr.stats.sac.nzmsec * 1000) # Possible pick locations are in the t[0-9] slot for pick_number in range(10): pick_key = 't' + str(pick_number) phase_key = 'kt' + str(pick_number) try: if tr.stats.sac[pick_key] == float_nan: # in version 0.10.2 and before. rather than not include # the keys, the variables are filled with SAC nans. if debug > 1: msg = 'No pick in position ' + pick_key + \ ' for trace: ' + tr.stats.station + '.' + \ tr.stats.channel warnings.warn(msg) continue pick_time = reference_time + tr.stats.sac[pick_key] phase_hint = tr.stats.sac[phase_key].split()[0] except KeyError: if debug > 1: msg = 'No pick in position ' + pick_key + ' for trace: ' +\ tr.stats.station + '.' + tr.stats.channel warnings.warn(msg) continue if debug > 0: msg = 'Found pick in position ' + pick_key + ' for trace: ' +\ tr.stats.station + '.' + tr.stats.channel print(msg) waveform_id = WaveformStreamID(station_code=tr.stats.station, network_code=tr.stats.network, channel_code=tr.stats.channel) pick = Pick(waveform_id=waveform_id, phase_hint=phase_hint, time=pick_time) event.picks.append(pick) # Also check header slots 'a' and 'ka' try: if tr.stats.sac['a'] == float_nan: if debug > 1: msg = 'No pick in position ' + pick_key + \ ' for trace: ' + tr.stats.station + '.' + \ tr.stats.channel warnings.warn(msg) continue pick_time = reference_time + tr.stats.sac['a'] phase_hint = tr.stats.sac['ka'].split()[0] except KeyError: if debug > 1: msg = 'No pick in position ' + pick_key + ' for trace: ' +\ tr.stats.station + '.' + tr.stats.channel warnings.warn(msg) continue if debug > 0: msg = 'Found pick in position a for trace: ' +\ tr.stats.station + '.' + tr.stats.channel print(msg) waveform_id = WaveformStreamID(station_code=tr.stats.station, network_code=tr.stats.network, channel_code=tr.stats.channel) pick = Pick(waveform_id=waveform_id, phase_hint=phase_hint, time=pick_time) event.picks.append(pick) return event
def outputOBSPY(hp, event=None, only_fm_picks=False): """ Make an Event which includes the current focal mechanism information from HASH Use the 'only_fm_picks' flag to only include the picks HASH used for the FocalMechanism. This flag will replace the 'picks' and 'arrivals' lists of existing events with new ones. Inputs ------- hp : hashpy.HashPype instance event : obspy.core.event.Event only_fm_picks : bool of whether to overwrite the picks/arrivals lists Returns ------- obspy.core.event.Event Event will be new if no event was input, FocalMech added to existing event """ # Returns new (or updates existing) Event with HASH solution n = hp.npol if event is None: event = Event(focal_mechanisms=[], picks=[], origins=[]) origin = Origin(arrivals=[]) origin.time = UTCDateTime(hp.tstamp) origin.latitude = hp.qlat origin.longitude = hp.qlon origin.depth = hp.qdep origin.creation_info = CreationInfo(version=hp.icusp) origin.resource_id = ResourceIdentifier("smi:hash/Origin/{0}".format( hp.icusp)) for _i in range(n): p = Pick() p.creation_info = CreationInfo(version=hp.arid[_i]) p.resource_id = ResourceIdentifier("smi:hash/Pick/{0}".format( p.creation_info.version)) p.waveform_id = WaveformStreamID( network_code=hp.snet[_i], station_code=hp.sname[_i], channel_code=hp.scomp[_i], ) if hp.p_pol[_i] > 0: p.polarity = "positive" else: p.polarity = "negative" a = Arrival() a.creation_info = CreationInfo(version=hp.arid[_i]) a.resource_id = ResourceIdentifier("smi:hash/Arrival/{0}".format( p.creation_info.version)) a.azimuth = hp.p_azi_mc[_i, 0] a.takeoff_angle = 180.0 - hp.p_the_mc[_i, 0] a.pick_id = p.resource_id origin.arrivals.append(a) event.picks.append(p) event.origins.append(origin) event.preferred_origin_id = str(origin.resource_id) else: # just update the changes origin = event.preferred_origin() picks = [] arrivals = [] for _i in range(n): ind = hp.p_index[_i] a = origin.arrivals[ind] p = a.pick_id.getReferredObject() a.takeoff_angle = hp.p_the_mc[_i, 0] picks.append(p) arrivals.append(a) if only_fm_picks: origin.arrivals = arrivals event.picks = picks # Use me double couple calculator and populate planes/axes etc x = hp._best_quality_index # Put all the mechanisms into the 'focal_mechanisms' list, mark "best" as preferred for s in range(hp.nmult): dc = DoubleCouple([hp.str_avg[s], hp.dip_avg[s], hp.rak_avg[s]]) ax = dc.axis focal_mech = FocalMechanism() focal_mech.creation_info = CreationInfo(creation_time=UTCDateTime(), author=hp.author) focal_mech.triggering_origin_id = origin.resource_id focal_mech.resource_id = ResourceIdentifier( "smi:hash/FocalMechanism/{0}/{1}".format(hp.icusp, s + 1)) focal_mech.method_id = ResourceIdentifier("HASH") focal_mech.nodal_planes = NodalPlanes() focal_mech.nodal_planes.nodal_plane_1 = NodalPlane(*dc.plane1) focal_mech.nodal_planes.nodal_plane_2 = NodalPlane(*dc.plane2) focal_mech.principal_axes = PrincipalAxes() focal_mech.principal_axes.t_axis = Axis(azimuth=ax["T"]["azimuth"], plunge=ax["T"]["dip"]) focal_mech.principal_axes.p_axis = Axis(azimuth=ax["P"]["azimuth"], plunge=ax["P"]["dip"]) focal_mech.station_polarity_count = n focal_mech.azimuthal_gap = hp.magap focal_mech.misfit = hp.mfrac[s] focal_mech.station_distribution_ratio = hp.stdr[s] focal_mech.comments.append( Comment( hp.qual[s], resource_id=ResourceIdentifier( str(focal_mech.resource_id) + "/comment/quality"), )) # ---------------------------------------- event.focal_mechanisms.append(focal_mech) if s == x: event.preferred_focal_mechanism_id = str(focal_mech.resource_id) return event
def _read_evt(filename, inventory=None, id_map=None, id_default='.{}..{}', encoding='utf-8'): """ Read a SeismicHandler EVT file and returns an ObsPy Catalog object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.event.read_events` function, call this instead. :type filename: str :param filename: File or file-like object in text mode. :type inventory: :class:`~obspy.core.inventory.inventory.Inventory` :param inventory: Inventory used to retrieve network code, location code and channel code of stations (SEED id). :type id_map: dict :param id_map: If channel information was not found in inventory, it will be looked up in this dictionary (example: `id_map={'MOX': 'GR.{}..HH{}'`). The values must contain three dots and two `{}` which are substituted by station code and component. :type id_default: str :param id_default: Default SEED id expression. The value must contain three dots and two `{}` which are substituted by station code and component. :param str encoding: encoding used (default: utf-8) :rtype: :class:`~obspy.core.event.Catalog` :return: An ObsPy Catalog object. .. note:: The following fields are supported by this function: %s. Compare with http://www.seismic-handler.org/wiki/ShmDocFileEvt """ seed_map = _seed_id_map(inventory, id_map) with io.open(filename, 'r', encoding=encoding) as f: temp = f.read() # first create phases and phases_o dictionaries for different phases # and phases with origin information phases = defaultdict(list) phases_o = {} phase = {} evid = None for line in temp.splitlines(): if 'End of Phase' in line: if 'origin time' in phase.keys(): if evid in phases_o: # found more than one origin pass phases_o[evid] = phase phases[evid].append(phase) phase = {} evid = None elif line.strip() != '': try: key, value = line.split(':', 1) except ValueError: continue key = key.strip().lower() value = value.strip() if key == 'event id': evid = value elif value != '': phase[key] = value assert evid is None # now create obspy Events from phases and phases_o dictionaries events = [] for evid in phases: picks = [] arrivals = [] stamags = [] origins = [] po = None magnitudes = [] pm = None for p in phases[evid]: sta = p.get('station code', '') comp = p.get('component', '') wid = seed_map.get(sta, id_default) wid = WaveformStreamID(seed_string=wid.format(sta, comp)) pick = Pick(waveform_id=wid, **_kw(p, 'pick')) arrival = Arrival(pick_id=pick.resource_id, **_kw(p, 'arrival')) picks.append(pick) arrivals.append(arrival) stamags_temp, _ = _mags(p, evid, stamag=True, wid=wid) stamags.extend(stamags_temp) if evid in phases_o: o = phases_o[evid] uncertainty = OriginUncertainty(**_kw(o, 'origin_uncertainty')) origin = Origin(arrivals=arrivals, origin_uncertainty=uncertainty, **_kw(o, 'origin')) if origin.latitude is None or origin.longitude is None: warn('latitude or longitude not set for event %s' % evid) else: if origin.longitude_errors.uncertainty is not None: origin.longitude_errors.uncertainty *= cos( origin.latitude / 180 * pi) origins = [origin] po = origin.resource_id magnitudes, pm = _mags(o, evid) else: o = p event = Event(resource_id=ResourceIdentifier(evid), picks=picks, origins=origins, magnitudes=magnitudes, station_magnitudes=stamags, preferred_origin_id=po, preferred_magnitude_id=pm, **_kw(o, 'event') ) events.append(event) return Catalog(events, description='Created from SeismicHandler EVT format')
def amp_pick_event(event, st, inventory, chans=['Z'], var_wintype=True, winlen=0.9, pre_pick=0.2, pre_filt=True, lowcut=1.0, highcut=20.0, corners=4, min_snr=1.0, plot=False, remove_old=False, ps_multiplier=0.34, velocity=False, water_level=0): """ Pick amplitudes for local magnitude for a single event. Looks for maximum peak-to-trough amplitude for a channel in a stream, and picks this amplitude and period. There are a few things it does internally to stabilise the result: 1. Applies a given filter to the data using obspy's bandpass filter. The filter applied is a time-domain digital SOS filter. This is often necessary for small magnitude earthquakes. To correct for this filter later the gain of the filter at the period of the maximum amplitude is retrieved using scipy's sosfreqz, and used to divide the resulting picked amplitude. 2. Picks the peak-to-trough amplitude, but records half of this to cope with possible DC offsets. 3. Despite the original definition of local magnitude requiring the use of a horizontal channel, more recent work has shown that the vertical channels give more consistent magnitude estimations between stations, due to a reduction in site-amplification effects, we therefore use the vertical channels by default, but allow the user to chose which channels they deem appropriate; 4. The maximum amplitude within the given window is picked. Care must be taken to avoid including surface waves in the window; 6. A variable window-length is used by default that takes into account P-S times if available, this is in an effort to include only the body waves. When P-S times are not available the ps_multiplier variable is used, which defaults to 0.34 x hypocentral distance. :type event: obspy.core.event.event.Event :param event: Event to pick :type st: obspy.core.stream.Stream :param st: Stream associated with event :type inventory: obspy.core.inventory.Inventory :param inventory: Inventory containing response information for the stations in st. :type chans: list :param chans: List of the channels to pick on, defaults to ['Z'] - should just be the orientations, e.g. Z, 1, 2, N, E :type var_wintype: bool :param var_wintype: If True, the winlen will be multiplied by the P-S time if both P and S picks are available, otherwise it will be multiplied by the hypocentral distance*ps_multiplier, defaults to True :type winlen: float :param winlen: Length of window, see above parameter, if var_wintype is False then this will be in seconds, otherwise it is the multiplier to the p-s time, defaults to 0.9. :type pre_pick: float :param pre_pick: Time before the s-pick to start the cut window, defaults to 0.2. :type pre_filt: bool :param pre_filt: To apply a pre-filter or not, defaults to True :type lowcut: float :param lowcut: Lowcut in Hz for the pre-filter, defaults to 1.0 :type highcut: float :param highcut: Highcut in Hz for the pre-filter, defaults to 20.0 :type corners: int :param corners: Number of corners to use in the pre-filter :type min_snr: float :param min_snr: Minimum signal-to-noise ratio to allow a pick - see note below on signal-to-noise ratio calculation. :type plot: bool :param plot: Turn plotting on or off. :type remove_old: bool :param remove_old: If True, will remove old amplitudes and associated picks from event and overwrite with new picks. Defaults to False. :type ps_multiplier: float :param ps_multiplier: A p-s time multiplier of hypocentral distance - defaults to 0.34, based on p-s ratio of 1.68 and an S-velocity 0f 1.5km/s, deliberately chosen to be quite slow. :type velocity: bool :param velocity: Whether to make the pick in velocity space or not. Original definition of local magnitude used displacement of Wood-Anderson, MLv in seiscomp and Antelope uses a velocity measurement. :type water_level: float :param water_level: Water-level for seismometer simulation, see :returns: Picked event :rtype: :class:`obspy.core.event.Event` .. Note:: Signal-to-noise ratio is calculated using the filtered data by dividing the maximum amplitude in the signal window (pick window) by the normalized noise amplitude (taken from the whole window supplied). """ try: event_origin = event.preferred_origin() or event.origins[0] except IndexError: event_origin = Origin() depth = event_origin.depth if depth is None: Logger.warning("No depth for the event, setting to 0 km") depth = 0 # Remove amplitudes and picks for those amplitudes - this is not always # safe: picks may not be exclusively linked to amplitudes - hence the # default is *not* to do this. if remove_old and event.amplitudes: removal_ids = {amp.pick_id for amp in event.amplitudes} event.picks = [ p for p in event.picks if p.resource_id not in removal_ids ] event.amplitudes = [] # We just want to look at P and S picks. picks = [ p for p in event.picks if p.phase_hint and p.phase_hint[0].upper() in ("P", "S") ] if len(picks) == 0: Logger.warning('No P or S picks found') return event st = st.copy().merge() # merge the data, just in case! Work on a copy. # For each station cut the window for sta in {p.waveform_id.station_code for p in picks}: for chan in chans: Logger.info(f'Working on {sta} {chan}') tr = st.select(station=sta, component=chan) if not tr: Logger.warning(f'{sta} {chan} not found in the stream.') continue tr = tr.merge()[0] # Apply the pre-filter if pre_filt: tr = tr.split().detrend('simple').merge(fill_value=0)[0] tr.filter('bandpass', freqmin=lowcut, freqmax=highcut, corners=corners) tr = _sim_WA(tr, inventory, water_level=water_level, velocity=velocity) if tr is None: # None returned when no matching response is found continue # Get the distance from an appropriate arrival sta_picks = [p for p in picks if p.waveform_id.station_code == sta] distances = [] for pick in sta_picks: distances += [ a.distance for a in event_origin.arrivals if a.pick_id == pick.resource_id and a.distance is not None ] if len(distances) == 0: Logger.error(f"Arrivals for station: {sta} do not contain " "distances. Have you located this event?") hypo_dist = None else: # They should all be the same, but take the mean to be sure... distance = np.mean(distances) hypo_dist = np.sqrt( np.square(degrees2kilometers(distance)) + np.square(depth / 1000)) # Get the earliest P and S picks on this station phase_picks = {"P": None, "S": None} for _hint in phase_picks.keys(): _picks = sorted( [p for p in sta_picks if p.phase_hint[0].upper() == _hint], key=lambda p: p.time) if len(_picks) > 0: phase_picks[_hint] = _picks[0] p_pick = phase_picks["P"] s_pick = phase_picks["S"] # Get the window size. if var_wintype: if p_pick and s_pick: p_time, s_time = p_pick.time, s_pick.time elif s_pick and hypo_dist: s_time = s_pick.time p_time = s_time - (hypo_dist * ps_multiplier) elif p_pick and hypo_dist: p_time = p_pick.time s_time = p_time + (hypo_dist * ps_multiplier) elif (s_pick or p_pick) and hypo_dist is None: Logger.error( "No hypocentral distance and no matching P and S " f"picks for {sta}, skipping.") continue else: raise NotImplementedError( "No p or s picks - you should not have been able to " "get here") trim_start = s_time - pre_pick trim_end = s_time + (s_time - p_time) * winlen # Work out the window length based on p-s time or distance else: # Fixed window-length if s_pick: s_time = s_pick.time elif p_pick and hypo_dist: # In this case, there is no S-pick and the window length is # fixed we need to calculate an expected S_pick based on # the hypocentral distance, this will be quite hand-wavey # as we are not using any kind of velocity model. s_time = p_pick.time + hypo_dist * ps_multiplier else: Logger.warning( "No s-pick or hypocentral distance to predict " f"s-arrival for station {sta}, skipping") continue trim_start = s_time - pre_pick trim_end = s_time + winlen tr = tr.trim(trim_start, trim_end) if len(tr.data) <= 10: Logger.warning(f'Insufficient data for {sta}') continue # Get the amplitude try: amplitude, period, delay, peak, trough = _max_p2t( tr.data, tr.stats.delta, return_peak_trough=True) except ValueError as e: Logger.error(e) Logger.error(f'No amplitude picked for tr {tr.id}') continue # Calculate the normalized noise amplitude snr = amplitude / np.sqrt(np.mean(np.square(tr.data))) if amplitude == 0.0: continue if snr < min_snr: Logger.info( f'Signal to noise ratio of {snr} is below threshold.') continue if plot: plt.plot(np.arange(len(tr.data)), tr.data, 'k') plt.scatter(tr.stats.sampling_rate * delay, peak) plt.scatter(tr.stats.sampling_rate * (delay + period / 2), trough) plt.show() Logger.info(f'Amplitude picked: {amplitude}') Logger.info(f'Signal-to-noise ratio is: {snr}') # Note, amplitude should be in meters at the moment! # Remove the pre-filter response if pre_filt: # Generate poles and zeros for the filter we used earlier. # We need to get the gain for the digital SOS filter used by # obspy. sos = iirfilter(corners, [ lowcut / (0.5 * tr.stats.sampling_rate), highcut / (0.5 * tr.stats.sampling_rate) ], btype='band', ftype='butter', output='sos') _, gain = sosfreqz(sos, worN=[1 / period], fs=tr.stats.sampling_rate) gain = np.abs(gain[0]) # Convert from complex to real. amplitude /= gain Logger.debug(f"Removed filter gain: {gain}") # Write out the half amplitude, approximately the peak amplitude as # used directly in magnitude calculations amplitude *= 0.5 # Append an amplitude reading to the event _waveform_id = WaveformStreamID(station_code=tr.stats.station, channel_code=tr.stats.channel, network_code=tr.stats.network) pick = Pick(waveform_id=_waveform_id, phase_hint='IAML', polarity='undecidable', time=tr.stats.starttime + delay, evaluation_mode='automatic') event.picks.append(pick) if not velocity: event.amplitudes.append( Amplitude(generic_amplitude=amplitude, period=period, pick_id=pick.resource_id, waveform_id=pick.waveform_id, unit='m', magnitude_hint='ML', type='AML', category='point')) else: event.amplitudes.append( Amplitude(generic_amplitude=amplitude, period=period, pick_id=pick.resource_id, waveform_id=pick.waveform_id, unit='m/s', magnitude_hint='ML', type='AML', category='point')) return event
def _dbs_associator(start_time, end_time, moving_window, tbl, pair_n, save_dir, station_list, consider_combination=False): if consider_combination == True: Y2000_writer = open(save_dir + "/" + "Y2000.phs", "w") traceNmae_dic = dict() st = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S.%f') et = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S.%f') total_t = et - st evid = 0 tt = st #pbar = tqdm(total= int(np.ceil(total_t.total_seconds()/moving_window)), ncols=100) while tt < et: detections = tbl[(tbl.event_start_time >= tt) & ( tbl.event_start_time < tt + timedelta(seconds=moving_window))] # pbar.update() if len(detections) >= pair_n: evid += 1 yr = "{:>4}".format( str(detections.iloc[0]['event_start_time']).split(' ') [0].split('-')[0]) mo = "{:>2}".format( str(detections.iloc[0]['event_start_time']).split(' ') [0].split('-')[1]) dy = "{:>2}".format( str(detections.iloc[0]['event_start_time']).split(' ') [0].split('-')[2]) hr = "{:>2}".format( str(detections.iloc[0]['event_start_time']).split(' ') [1].split(':')[0]) mi = "{:>2}".format( str(detections.iloc[0]['event_start_time']).split(' ') [1].split(':')[1]) sec = "{:>4}".format( str(detections.iloc[0]['event_start_time']).split(' ') [1].split(':')[2]) st_lat_DMS = _decimalDegrees2DMS( float(detections.iloc[0]['stlat']), "Latitude") st_lon_DMS = _decimalDegrees2DMS( float(detections.iloc[0]['stlon']), "Longitude") depth = 5.0 mag = 0.0 if len(detections) / pair_n <= 2: ch = pair_n else: ch = int(len(detections) - pair_n) picks = [] for ns in range(ch, len(detections) + 1): comb = 0 for ind in list(combinations(detections.index, ns)): comb += 1 selected_detections = detections.loc[ind, :] sorted_detections = selected_detections.sort_values( 'p_arrival_time') Y2000_writer.write( "%4d%2d%2d%2d%2d%4.2f%2.0f%1s%4.2f%3.0f%1s%4.2f%5.2f%3.2f\n" % (int(yr), int(mo), int(dy), int(hr), int(mi), float(sec), float(st_lat_DMS[0]), str(st_lat_DMS[1]), float(st_lat_DMS[2]), float(st_lon_DMS[0]), str(st_lon_DMS[1]), float(st_lon_DMS[2]), float(depth), float(mag))) station_buffer = [] row_buffer = [] tr_names = [] tr_names2 = [] for _, row in sorted_detections.iterrows(): trace_name = row['traceID'] + '*' + row[ 'station'] + '*' + str(row['event_start_time']) p_unc = row['p_unc'] p_prob = row['p_prob'] s_unc = row['s_unc'] s_prob = row['s_prob'] station = "{:<5}".format(row['station']) network = "{:<2}".format(row['network']) try: yrp = "{:>4}".format( str(row['p_arrival_time']).split(' ') [0].split('-')[0]) mop = "{:>2}".format( str(row['p_arrival_time']).split(' ') [0].split('-')[1]) dyp = "{:>2}".format( str(row['p_arrival_time']).split(' ') [0].split('-')[2]) hrp = "{:>2}".format( str(row['p_arrival_time']).split(' ') [1].split(':')[0]) mip = "{:>2}".format( str(row['p_arrival_time']).split(' ') [1].split(':')[1]) sec_p = "{:>4}".format( str(row['p_arrival_time']).split(' ') [1].split(':')[2]) p = Pick(time=UTCDateTime( row['p_arrival_time']), waveform_id=WaveformStreamID( network_code=network, station_code=station.rstrip()), phase_hint="P") picks.append(p) if p_unc: Pweihgt = _weighcalculator_prob( p_prob * (1 - p_unc)) else: Pweihgt = _weighcalculator_prob(p_prob) try: Pweihgt = int(Pweihgt) except Exception: Pweihgt = 4 except Exception: sec_p = None try: yrs = "{:>4}".format( str(row['s_arrival_time']).split(' ') [0].split('-')[0]) mos = "{:>2}".format( str(row['s_arrival_time']).split(' ') [0].split('-')[1]) dys = "{:>2}".format( str(row['s_arrival_time']).split(' ') [0].split('-')[2]) hrs = "{:>2}".format( str(row['s_arrival_time']).split(' ') [1].split(':')[0]) mis = "{:>2}".format( str(row['s_arrival_time']).split(' ') [1].split(':')[1]) sec_s = "{:>4}".format( str(row['s_arrival_time']).split(' ') [1].split(':')[2]) p = Pick(time=UTCDateTime( row['p_arrival_time']), waveform_id=WaveformStreamID( network_code=network, station_code=station.rstrip()), phase_hint="S") picks.append(p) if s_unc: Sweihgt = _weighcalculator_prob( s_prob * (1 - s_unc)) else: Sweihgt = _weighcalculator_prob(s_prob) try: Sweihgt = int(Sweihgt) except Exception: Sweihgt = 4 except Exception: sec_s = None if row['station'] not in station_buffer: tr_names.append(trace_name) station_buffer.append(row['station']) if sec_s: Y2000_writer.write( "%5s%2s HHE %4d%2d%2d%2d%2d%5.2f %5.2fES %1d\n" % (station, network, int(yrs), int(mos), int(dys), int(hrs), int(mis), float(0.0), float(sec_s), Sweihgt)) if sec_p: Y2000_writer.write( "%5s%2s HHZ IP %1d%4d%2d%2d%2d%2d%5.2f %5.2f 0\n" % (station, network, Pweihgt, int(yrp), int(mop), int(dyp), int(hrp), int(mip), float(sec_p), float(0.0))) else: tr_names2.append(trace_name) if sec_s: row_buffer.append( "%5s%2s HHE %4d%2d%2d%2d%2d%5.2f %5.2fES %1d\n" % (station, network, int(yrs), int(mos), int(dys), int(hrs), int(mis), 0.0, float(sec_s), Sweihgt)) if sec_p: row_buffer.append( "%5s%2s HHZ IP %1d%4d%2d%2d%2d%2d%5.2f %5.2f 0\n" % (station, network, Pweihgt, int(yrp), int(mop), int(dyp), int(hrp), int(mip), float(sec_p), float(0.0))) Y2000_writer.write("{:<62}".format(' ') + "%10d" % (evid) + '\n') traceNmae_dic[str(evid)] = tr_names if len(row_buffer) >= 2 * pair_n: Y2000_writer.write( "%4d%2d%2d%2d%2d%4.2f%2.0f%1s%4.2f%3.0f%1s%4.2f%5.2f%3.2f\n" % (int(yr), int(mo), int(dy), int(hr), int(mi), float(sec), float(st_lat_DMS[0]), str( st_lat_DMS[1]), float(st_lat_DMS[2]), float(st_lon_DMS[0]), str(st_lon_DMS[1]), float(st_lon_DMS[2]), float(depth), float(mag))) for rr in row_buffer: Y2000_writer.write(rr) Y2000_writer.write("{:<62}".format(' ') + "%10d" % (evid) + '\n') traceNmae_dic[str(evid)] = tr_names2 tt += timedelta(seconds=moving_window) # plt.scatter(LTTP, TTP, s=10, marker='o', c='b', alpha=0.4, label='P') # plt.scatter(LTTS, TTS, s=10, marker='o', c='r', alpha=0.4, label='S') # plt.legend('upper right') # plt.show() print('The Number of Realizations: ' + str(evid) + '\n', flush=True) jj = json.dumps(traceNmae_dic) if platform.system() == 'Windows': f = open(save_dir + "\\" + "traceNmae_dic.json", "w") else: f = open(save_dir + "/" + "traceNmae_dic.json", "w") f.write(jj) f.close() else: if platform.system() == 'Windows': Y2000_writer = open(save_dir + "\\" + "Y2000.phs", "w") else: Y2000_writer = open(save_dir + "/" + "Y2000.phs", "w") cat = Catalog() traceNmae_dic = dict() st = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S.%f') et = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S.%f') total_t = et - st evid = 200000 evidd = 100000 tt = st #pbar = tqdm(total= int(np.ceil(total_t.total_seconds()/moving_window))) while tt < et: detections = tbl[(tbl.event_start_time >= tt) & ( tbl.event_start_time < tt + timedelta(seconds=moving_window))] pair_nt = pair_n for _, row in detections.iterrows(): station = "{:<5}".format(row['station']) if station[0] is "R": pair_nt = 3 #pbar.update() if len(detections) >= pair_nt: yr = "{:>4}".format( str(detections.iloc[0]['event_start_time']).split(' ') [0].split('-')[0]) mo = "{:>2}".format( str(detections.iloc[0]['event_start_time']).split(' ') [0].split('-')[1]) dy = "{:>2}".format( str(detections.iloc[0]['event_start_time']).split(' ') [0].split('-')[2]) hr = "{:>2}".format( str(detections.iloc[0]['event_start_time']).split(' ') [1].split(':')[0]) mi = "{:>2}".format( str(detections.iloc[0]['event_start_time']).split(' ') [1].split(':')[1]) sec = "{:>4}".format( str(detections.iloc[0]['event_start_time']).split(' ') [1].split(':')[2]) st_lat_DMS = _decimalDegrees2DMS( float(detections.iloc[0]['stlat']), "Latitude") st_lon_DMS = _decimalDegrees2DMS( float(detections.iloc[0]['stlon']), "Longitude") depth = 5.0 mag = 0.0 Y2000_writer.write( "%4d%2d%2d%2d%2d%4.2f%2.0f%1s%4.2f%3.0f%1s%4.2f%5.2f%3.2f\n" % (int(yr), int(mo), int(dy), int(hr), int(mi), float(sec), float(st_lat_DMS[0]), str(st_lat_DMS[1]), float(st_lat_DMS[2]), float(st_lon_DMS[0]), str(st_lon_DMS[1]), float( st_lon_DMS[2]), float(depth), float(mag))) event = Event() origin = Origin(time=UTCDateTime( detections.iloc[0]['event_start_time']), longitude=detections.iloc[0]['stlon'], latitude=detections.iloc[0]['stlat'], method="EqTransformer") event.origins.append(origin) station_buffer = [] row_buffer = [] sorted_detections = detections.sort_values('p_arrival_time') tr_names = [] tr_names2 = [] picks = [] for _, row in sorted_detections.iterrows(): trace_name = row['traceID'] + '*' + row[ 'station'] + '*' + str(row['event_start_time']) p_unc = row['p_unc'] p_prob = row['p_prob'] s_unc = row['s_unc'] s_prob = row['s_prob'] station = "{:<5}".format(row['station']) network = "{:<2}".format(row['network']) try: yrp = "{:>4}".format( str(row['p_arrival_time']).split(' ')[0].split('-') [0]) mop = "{:>2}".format( str(row['p_arrival_time']).split(' ')[0].split('-') [1]) dyp = "{:>2}".format( str(row['p_arrival_time']).split(' ')[0].split('-') [2]) hrp = "{:>2}".format( str(row['p_arrival_time']).split(' ')[1].split(':') [0]) mip = "{:>2}".format( str(row['p_arrival_time']).split(' ')[1].split(':') [1]) sec_p = "{:>4}".format( str(row['p_arrival_time']).split(' ')[1].split(':') [2]) p = Pick(time=UTCDateTime(row['p_arrival_time']), waveform_id=WaveformStreamID( network_code=network, station_code=station.rstrip()), phase_hint="P", method_id="Transformer") picks.append(p) if p_unc: Pweihgt = _weighcalculator_prob(p_prob * (1 - p_unc)) else: Pweihgt = _weighcalculator_prob(p_prob) try: Pweihgt = int(Pweihgt) except Exception: Pweihgt = 4 except Exception: sec_p = None try: yrs = "{:>4}".format( str(row['s_arrival_time']).split(' ')[0].split('-') [0]) mos = "{:>2}".format( str(row['s_arrival_time']).split(' ')[0].split('-') [1]) dys = "{:>2}".format( str(row['s_arrival_time']).split(' ')[0].split('-') [2]) hrs = "{:>2}".format( str(row['s_arrival_time']).split(' ')[1].split(':') [0]) mis = "{:>2}".format( str(row['s_arrival_time']).split(' ')[1].split(':') [1]) sec_s = "{:>4}".format( str(row['s_arrival_time']).split(' ')[1].split(':') [2]) p = Pick(time=UTCDateTime(row['s_arrival_time']), waveform_id=WaveformStreamID( network_code=network, station_code=station.rstrip()), phase_hint="S", method_id="Transformer") picks.append(p) if s_unc: Sweihgt = _weighcalculator_prob(s_prob * (1 - s_unc)) else: Sweihgt = _weighcalculator_prob(s_prob) try: Sweihgt = int(Sweihgt) except Exception: Sweihgt = 4 except Exception: sec_s = None if row['station'] not in station_buffer: tr_names.append(trace_name) station_buffer.append(row['station']) if sec_s: Y2000_writer.write( "%5s%2s HHE %4d%2d%2d%2d%2d%5.2f %5.2fES %1d\n" % (station, network, int(yrs), int(mos), int(dys), int(hrs), int(mis), float(0.0), float(sec_s), Sweihgt)) if sec_p: Y2000_writer.write( "%5s%2s HHZ IP %1d%4d%2d%2d%2d%2d%5.2f %5.2f 0\n" % (station, network, Pweihgt, int(yrp), int(mop), int(dyp), int(hrp), int(mip), float(sec_p), float(0.0))) else: tr_names2.append(trace_name) if sec_s: row_buffer.append( "%5s%2s HHE %4d%2d%2d%2d%2d%5.2f %5.2fES %1d\n" % (station, network, int(yrs), int(mos), int(dys), int(hrs), int(mis), 0.0, float(sec_s), Sweihgt)) if sec_p: row_buffer.append( "%5s%2s HHZ IP %1d%4d%2d%2d%2d%2d%5.2f %5.2f 0\n" % (station, network, Pweihgt, int(yrp), int(mop), int(dyp), int(hrp), int(mip), float(sec_p), float(0.0))) event.picks = picks event.preferred_origin_id = event.origins[0].resource_id cat.append(event) evid += 1 Y2000_writer.write("{:<62}".format(' ') + "%10d" % (evid) + '\n') traceNmae_dic[str(evid)] = tr_names if len(row_buffer) >= 2 * pair_nt: Y2000_writer.write( "%4d%2d%2d%2d%2d%4.2f%2.0f%1s%4.2f%3.0f%1s%4.2f%5.2f%3.2f\n" % (int(yr), int(mo), int(dy), int(hr), int(mi), float(sec), float(st_lat_DMS[0]), str( st_lat_DMS[1]), float(st_lat_DMS[2]), float(st_lon_DMS[0]), str(st_lon_DMS[1]), float(st_lon_DMS[2]), float(depth), float(mag))) for rr in row_buffer: Y2000_writer.write(rr) evid += 1 Y2000_writer.write("{:<62}".format(' ') + "%10d" % (evid) + '\n') traceNmae_dic[str(evid)] = tr_names2 elif len(row_buffer) < pair_nt and len(row_buffer) != 0: evidd += 1 traceNmae_dic[str(evidd)] = tr_names2 elif len(detections) < pair_nt and len(detections) != 0: tr_names = [] for _, row in detections.iterrows(): trace_name = row['traceID'] tr_names.append(trace_name) evidd += 1 traceNmae_dic[str(evidd)] = tr_names tt += timedelta(seconds=moving_window) print('The Number of Associated Events: ' + str(evid - 200000) + '\n', flush=True) jj = json.dumps(traceNmae_dic) if platform.system() == 'Windows': f = open(save_dir + "\\" + "traceNmae_dic.json", "w") else: f = open(save_dir + "/" + "traceNmae_dic.json", "w") f.write(jj) f.close() cat.write(save_dir + "/associations.xml", format="QUAKEML") qml = quakeml.QuakeML.load_xml(filename=save_dir + "/associations.xml") events = qml.get_pyrocko_events() model.event.dump_events(events, filename=save_dir + '/events.pf')
def test_write_with_extra_tags_and_read(self): """ Tests that a QuakeML file with additional custom "extra" tags gets written correctly and that when reading it again the extra tags are parsed correctly. """ filename = os.path.join(self.path, "quakeml_1.2_origin.xml") with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") cat = readQuakeML(filename) self.assertEqual(len(w), 0) # add some custom tags to first event: # - tag with explicit namespace but no explicit ns abbreviation # - tag without explicit namespace (gets obspy default ns) # - tag with explicit namespace and namespace abbreviation my_extra = AttribDict( {'public': {'value': False, 'namespace': r"http://some-page.de/xmlns/1.0", 'attrib': {u"some_attrib": u"some_value", u"another_attrib": u"another_value"}}, 'custom': {'value': u"True", 'namespace': r'http://test.org/xmlns/0.1'}, 'new_tag': {'value': 1234, 'namespace': r"http://test.org/xmlns/0.1"}, 'tX': {'value': UTCDateTime('2013-01-02T13:12:14.600000Z'), 'namespace': r'http://test.org/xmlns/0.1'}, 'dataid': {'namespace': r'http://anss.org/xmlns/catalog/0.1', 'type': 'attribute', 'value': '00999999'}}) nsmap = {"ns0": r"http://test.org/xmlns/0.1", "catalog": r'http://anss.org/xmlns/catalog/0.1'} cat[0].extra = my_extra.copy() # insert a pick with an extra field p = Pick() p.extra = {'weight': {'value': 2, 'namespace': r"http://test.org/xmlns/0.1"}} cat[0].picks.append(p) with NamedTemporaryFile() as tf: tmpfile = tf.name # write file cat.write(tmpfile, format="QUAKEML", nsmap=nsmap) # check contents with open(tmpfile, "r") as fh: content = fh.read() # check namespace definitions in root element expected = ['<q:quakeml', 'xmlns:catalog="http://anss.org/xmlns/catalog/0.1"', 'xmlns:ns0="http://test.org/xmlns/0.1"', 'xmlns:ns1="http://some-page.de/xmlns/1.0"', 'xmlns:q="http://quakeml.org/xmlns/quakeml/1.2"', 'xmlns="http://quakeml.org/xmlns/bed/1.2"'] for line in expected: self.assertTrue(line in content) # check additional tags expected = [ '<ns0:custom>True</ns0:custom>', '<ns0:new_tag>1234</ns0:new_tag>', '<ns0:tX>2013-01-02T13:12:14.600000Z</ns0:tX>', '<ns1:public ' 'another_attrib="another_value" ' 'some_attrib="some_value">false</ns1:public>' ] for lines in expected: self.assertTrue(line in content) # now, read again to test if its parsed correctly.. cat = readQuakeML(tmpfile) # when reading.. # - namespace abbreviations should be disregarded # - we always end up with a namespace definition, even if it was # omitted when originally setting the custom tag # - custom namespace abbreviations should attached to Catalog self.assertTrue(hasattr(cat[0], "extra")) def _tostr(x): if isinstance(x, bool): if x: return str("true") else: return str("false") return str(x) for key, value in my_extra.items(): my_extra[key]['value'] = _tostr(value['value']) self.assertEqual(cat[0].extra, my_extra) self.assertTrue(hasattr(cat[0].picks[0], "extra")) self.assertEqual( cat[0].picks[0].extra, {'weight': {'value': '2', 'namespace': r'http://test.org/xmlns/0.1'}}) self.assertTrue(hasattr(cat, "nsmap")) self.assertTrue(getattr(cat, "nsmap")['ns0'] == nsmap['ns0'])
def __toPick(parser, pick_el, evaluation_mode): """ """ pick = Pick() pick.resource_id = ResourceIdentifier( prefix="/".join([RESOURCE_ROOT, "pick"])) # Raise a warnings if there is a phase delay phase_delay = parser.xpath2obj("phase_delay", pick_el, float) if phase_delay is not None: msg = "The pick has a phase_delay!" raise Exception(msg) waveform = pick_el.xpath("waveform")[0] network = waveform.get("networkCode") station = fix_station_name(waveform.get("stationCode")) # Map some station names. if station in STATION_DICT: station = STATION_DICT[station] if not network: network = NETWORK_DICT[station] location = waveform.get("locationCode") or "" channel = waveform.get("channelCode") or "" pick.waveform_id = WaveformStreamID(network_code=network, station_code=station, channel_code=channel, location_code=location) pick.time, pick.time_errors = __toTimeQuantity(parser, pick_el, "time") # Picks without time are not quakeml conform if pick.time is None: print "Pick has no time and is ignored: %s" % station return None pick.phase_hint = parser.xpath2obj('phaseHint', pick_el, str) onset = parser.xpath2obj('onset', pick_el) # Fixing bad and old typo ... if onset == "implusive": onset = "impulsive" if onset: pick.onset = onset.lower() # Evaluation mode of a pick is global in the SeisHub Event file format. #pick.evaluation_mode = evaluation_mode # The polarity needs to be mapped. polarity = parser.xpath2obj('polarity', pick_el) pol_map_dict = { 'up': 'positive', 'positive': 'positive', 'forward': 'positive', 'forwards': 'positive', 'right': 'positive', 'backward': 'negative', 'backwards': 'negative', 'left': 'negative', 'down': 'negative', 'negative': 'negative', 'undecidable': 'undecidable', 'poorup': 'positive', 'poordown': 'negative' } if polarity: if polarity.lower() in pol_map_dict: pick.polarity = pol_map_dict[polarity.lower()] else: pick.polarity = polarity.lower() pick_weight = parser.xpath2obj('weight', pick_el, int) if pick_weight is not None: pick.extra = AttribDict() pick.extra.weight = {'value': pick_weight, 'namespace': NAMESPACE} return pick
def _read_picks(f, new_event): """ Internal pick reader. Use read_nordic instead. :type f: file :param f: File open in read mode :type wav_names: list :param wav_names: List of waveform files in the sfile :type new_event: :class:`~obspy.core.event.event.Event` :param new_event: event to associate picks with. :returns: :class:`~obspy.core.event.event.Event` """ f.seek(0) evtime = new_event.origins[0].time pickline = [] # Set a default, ignored later unless overwritten snr = None for lineno, line in enumerate(f): if line[79] == '7': header = line break for lineno, line in enumerate(f): if len(line.rstrip('\n').rstrip('\r')) in [80, 79] and \ line[79] in ' 4\n': pickline += [line] for line in pickline: if line[18:28].strip() == '': # If line is empty miss it continue weight = line[14] if weight == '_': phase = line[10:17] weight = 0 polarity = '' else: phase = line[10:14].strip() polarity = line[16] if weight == ' ': weight = 0 polarity_maps = {"": "undecidable", "C": "positive", "D": "negative"} try: polarity = polarity_maps[polarity] except KeyError: polarity = "undecidable" # It is valid nordic for the origin to be hour 23 and picks to be hour # 00 or 24: this signifies a pick over a day boundary. if int(line[18:20]) == 0 and evtime.hour == 23: day_add = 86400 pick_hour = 0 elif int(line[18:20]) == 24: day_add = 86400 pick_hour = 0 else: day_add = 0 pick_hour = int(line[18:20]) try: time = UTCDateTime(evtime.year, evtime.month, evtime.day, pick_hour, int(line[20:22]), float(line[23:28])) + day_add except ValueError: time = UTCDateTime(evtime.year, evtime.month, evtime.day, int(line[18:20]), pick_hour, float("0." + line[23:38].split('.')[1])) +\ 60 + day_add # Add 60 seconds on to the time, this copes with s-file # preference to write seconds in 1-60 rather than 0-59 which # datetime objects accept if header[57:60] == 'AIN': ain = _float_conv(line[57:60]) warnings.warn('AIN: %s in header, currently unsupported' % ain) elif header[57:60] == 'SNR': snr = _float_conv(line[57:60]) else: warnings.warn('%s is not currently supported' % header[57:60]) # finalweight = _int_conv(line[68:70]) # Create a new obspy.event.Pick class for this pick _waveform_id = WaveformStreamID(station_code=line[1:6].strip(), channel_code=line[6:8].strip(), network_code='NA') pick = Pick(waveform_id=_waveform_id, phase_hint=phase, polarity=polarity, time=time) try: pick.onset = onsets[line[9]] except KeyError: pass if line[15] == 'A': pick.evaluation_mode = 'automatic' else: pick.evaluation_mode = 'manual' # Note these two are not always filled - velocity conversion not yet # implemented, needs to be converted from km/s to s/deg # if not velocity == 999.0: # new_event.picks[pick_index].horizontal_slowness = 1.0 / velocity if _float_conv(line[46:51]) is not None: pick.backazimuth = _float_conv(line[46:51]) # Create new obspy.event.Amplitude class which references above Pick # only if there is an amplitude picked. if _float_conv(line[33:40]) is not None: _amplitude = Amplitude(generic_amplitude=_float_conv(line[33:40]), period=_float_conv(line[41:45]), pick_id=pick.resource_id, waveform_id=pick.waveform_id) if pick.phase_hint == 'IAML': # Amplitude for local magnitude _amplitude.type = 'AML' # Set to be evaluating a point in the trace _amplitude.category = 'point' # Default AML unit in seisan is nm (Page 139 of seisan # documentation, version 10.0) _amplitude.generic_amplitude /= 1e9 _amplitude.unit = 'm' _amplitude.magnitude_hint = 'ML' else: # Generic amplitude type _amplitude.type = 'A' if snr: _amplitude.snr = snr new_event.amplitudes.append(_amplitude) elif _int_conv(line[28:33]) is not None: # Create an amplitude instance for code duration also _amplitude = Amplitude(generic_amplitude=_int_conv(line[28:33]), pick_id=pick.resource_id, waveform_id=pick.waveform_id) # Amplitude for coda magnitude _amplitude.type = 'END' # Set to be evaluating a point in the trace _amplitude.category = 'duration' _amplitude.unit = 's' _amplitude.magnitude_hint = 'Mc' if snr is not None: _amplitude.snr = snr new_event.amplitudes.append(_amplitude) # Create new obspy.event.Arrival class referencing above Pick if _float_conv(line[33:40]) is None: arrival = Arrival(phase=pick.phase_hint, pick_id=pick.resource_id) if weight is not None: arrival.time_weight = weight if _int_conv(line[60:63]) is not None: arrival.backazimuth_residual = _int_conv(line[60:63]) if _float_conv(line[63:68]) is not None: arrival.time_residual = _float_conv(line[63:68]) if _float_conv(line[70:75]) is not None: arrival.distance = kilometers2degrees(_float_conv(line[70:75])) if _int_conv(line[76:79]) is not None: arrival.azimuth = _int_conv(line[76:79]) new_event.origins[0].arrivals.append(arrival) new_event.picks.append(pick) return new_event
def _channel_loop(detection, template, min_cc, detection_id, interpolate, i, pre_lag_ccsum=None, detect_chans=0, horizontal_chans=['E', 'N', '1', '2'], vertical_chans=['Z'], debug=0): """ Inner loop for correlating and assigning picks. Utility function to take a stream of data for the detected event and write maximum correlation to absolute time as picks in an obspy.core.event.Event object. Only outputs picks for picks above min_cc. :type detection: obspy.core.stream.Stream :param detection: Stream of data for the slave event detected using template. :type template: obspy.core.stream.Stream :param template: Stream of data as the template for the detection. :type min_cc: float :param min_cc: Minimum cross-correlation value to allow a pick to be made. :type detection_id: str :param detection_id: Detection ID to associate the event with. :type interpolate: bool :param interpolate: Interpolate the correlation function to achieve sub-sample precision. :type i: int :param i: Used to track which process has occurred when running in parallel. :type pre_lag_ccsum: float :param pre_lag_ccsum: Cross-correlation sum before lag-calc, will check that the cross-correlation sum is increased by lag-calc (using all channels, ignoring min_cc) :type detect_chans: int :param detect_chans: Number of channels originally used in detections, must match the number used here to allow for cccsum checking. :type horizontal_chans: list :param horizontal_chans: List of channel endings for horizontal-channels, on which S-picks will be made. :type vertical_chans: list :param vertical_chans: List of channel endings for vertical-channels, on which P-picks will be made. :type debug: int :param debug: Debug output level 0-5. :returns: Event object containing network, station, channel and pick information. :rtype: :class:`obspy.core.event.Event` """ from eqcorrscan.core.match_filter import normxcorr2 import math event = Event() s_stachans = {} cccsum = 0 checksum = 0 used_chans = 0 for tr in template: temp_net = tr.stats.network temp_sta = tr.stats.station temp_chan = tr.stats.channel debug_print('Working on: %s.%s.%s' % (temp_net, temp_sta, temp_chan), 3, debug) image = detection.select(station=temp_sta, channel=temp_chan) if len(image) == 0 or sum(image[0].data) == 0: print('No match in image.') continue if interpolate: try: ccc = normxcorr2(tr.data, image[0].data) except Exception: print('Could not calculate cc') print('Image is %i long' % len(image[0].data)) print('Template is %i long' % len(tr.data)) continue try: shift, cc_max = _xcorr_interp(ccc=ccc, dt=image[0].stats.delta) except IndexError: print('Could not interpolate ccc, not smooth') ccc = normxcorr2(tr.data, image[0].data) cc_max = np.amax(ccc) shift = np.argmax(ccc) * image[0].stats.delta # Convert the maximum cross-correlation time to an actual time if math.isnan(cc_max): print('Problematic trace, no cross correlation possible') continue else: picktime = image[0].stats.starttime + shift else: # Convert the maximum cross-correlation time to an actual time try: ccc = normxcorr2(tr.data, image[0].data) except Exception: print('Could not calculate cc') print('Image is %i long' % len(image[0].data)) print('Template is %i long' % len(tr.data)) continue cc_max = np.amax(ccc) if math.isnan(cc_max): print('Problematic trace, no cross correlation possible') continue else: picktime = image[0].stats.starttime + (np.argmax(ccc) * image[0].stats.delta) debug_print('Maximum cross-corr=%s' % cc_max, 3, debug) checksum += cc_max used_chans += 1 if cc_max < min_cc: debug_print('Correlation below threshold, not used', 3, debug) continue cccsum += cc_max # Perhaps weight each pick by the cc val or cc val^2? # weight = np.amax(ccc) ** 2 if temp_chan[-1] in vertical_chans: phase = 'P' # Only take the S-pick with the best correlation elif temp_chan[-1] in horizontal_chans: phase = 'S' debug_print( 'Making S-pick on: %s.%s.%s' % (temp_net, temp_sta, temp_chan), 4, debug) if temp_sta not in s_stachans.keys(): s_stachans[temp_sta] = ((temp_chan, np.amax(ccc), picktime)) elif temp_sta in s_stachans.keys(): if np.amax(ccc) > s_stachans[temp_sta][1]: picktime = picktime else: continue else: phase = None _waveform_id = WaveformStreamID(network_code=temp_net, station_code=temp_sta, channel_code=temp_chan) event.picks.append( Pick(waveform_id=_waveform_id, time=picktime, method_id=ResourceIdentifier('EQcorrscan'), phase_hint=phase, creation_info='eqcorrscan.core.lag_calc', evaluation_mode='automatic', comments=[Comment(text='cc_max=%s' % cc_max)])) event.resource_id = detection_id ccc_str = ("detect_val=%s" % cccsum) event.comments.append(Comment(text=ccc_str)) if used_chans == detect_chans: if pre_lag_ccsum is not None and\ checksum - pre_lag_ccsum < -(0.3 * pre_lag_ccsum): msg = ('lag-calc has decreased cccsum from %f to %f - ' % (pre_lag_ccsum, checksum)) raise LagCalcError(msg) else: warnings.warn('Cannot check if cccsum is better, used %i channels ' 'for detection, but %i are used here' % (detect_chans, used_chans)) return i, event
def read_nlloc_hyp(filename, coordinate_converter=None, picks=None, **kwargs): """ Reads a NonLinLoc Hypocenter-Phase file to a :class:`~obspy.core.event.Catalog` object. .. note:: Coordinate conversion from coordinate frame of NonLinLoc model files / location run to WGS84 has to be specified explicitly by the user if necessary. .. note:: An example can be found on the :mod:`~obspy.nlloc` submodule front page in the documentation pages. :param filename: File or file-like object in text mode. :type coordinate_converter: func :param coordinate_converter: Function to convert (x, y, z) coordinates of NonLinLoc output to geographical coordinates and depth in meters (longitude, latitude, depth in kilometers). If left `None` NonLinLoc (x, y, z) output is left unchanged (e.g. if it is in geographical coordinates already like for NonLinLoc in global mode). The function should accept three arguments x, y, z and return a tuple of three values (lon, lat, depth in kilometers). :type picks: list of :class:`~obspy.core.event.Pick` :param picks: Original picks used to generate the NonLinLoc location. If provided, the output event will include the original picks and the arrivals in the output origin will link to them correctly (with their `pick_id` attribute). If not provided, the output event will include (the rather basic) pick information that can be reconstructed from the NonLinLoc hypocenter-phase file. :rtype: :class:`~obspy.core.event.Catalog` """ if not hasattr(filename, "read"): # Check if it exists, otherwise assume its a string. try: with open(filename, "rt") as fh: data = fh.read() except: try: data = filename.decode() except: data = str(filename) data = data.strip() else: data = filename.read() if hasattr(data, "decode"): data = data.decode() lines = data.splitlines() # remember picks originally used in location, if provided original_picks = picks if original_picks is None: original_picks = [] # determine indices of block start/end of the NLLOC output file indices_hyp = [None, None] indices_phases = [None, None] for i, line in enumerate(lines): if line.startswith("NLLOC "): indices_hyp[0] = i elif line.startswith("END_NLLOC"): indices_hyp[1] = i elif line.startswith("PHASE "): indices_phases[0] = i elif line.startswith("END_PHASE"): indices_phases[1] = i if any([i is None for i in indices_hyp]): msg = ("NLLOC HYP file seems corrupt," " could not detect 'NLLOC' and 'END_NLLOC' lines.") raise RuntimeError(msg) # strip any other lines around NLLOC block lines = lines[indices_hyp[0]:indices_hyp[1]] # extract PHASES lines (if any) if any(indices_phases): if not all(indices_phases): msg = ("NLLOC HYP file seems corrupt, 'PHASE' block is corrupt.") raise RuntimeError(msg) i1, i2 = indices_phases lines, phases_lines = lines[:i1] + lines[i2 + 1:], lines[i1 + 1:i2] else: phases_lines = [] lines = dict([line.split(None, 1) for line in lines]) line = lines["SIGNATURE"] line = line.rstrip().split('"')[1] signature, version, date, time = line.rsplit(" ", 3) creation_time = UTCDateTime().strptime(date + time, str("%d%b%Y%Hh%Mm%S")) # maximum likelihood origin location info line line = lines["HYPOCENTER"] x, y, z = map(float, line.split()[1:7:2]) if coordinate_converter: x, y, z = coordinate_converter(x, y, z) # origin time info line line = lines["GEOGRAPHIC"] year, month, day, hour, minute = map(int, line.split()[1:6]) seconds = float(line.split()[6]) time = UTCDateTime(year, month, day, hour, minute, seconds) # distribution statistics line line = lines["STATISTICS"] covariance_XX = float(line.split()[7]) covariance_YY = float(line.split()[13]) covariance_ZZ = float(line.split()[17]) stats_info_string = str( "Note: Depth/Latitude/Longitude errors are calculated from covariance " "matrix as 1D marginal (Lon/Lat errors as great circle degrees) " "while OriginUncertainty min/max horizontal errors are calculated " "from 2D error ellipsoid and are therefore seemingly higher compared " "to 1D errors. Error estimates can be reconstructed from the " "following original NonLinLoc error statistics line:\nSTATISTICS " + lines["STATISTICS"]) # goto location quality info line line = lines["QML_OriginQuality"].split() (assoc_phase_count, used_phase_count, assoc_station_count, used_station_count, depth_phase_count) = map(int, line[1:11:2]) stderr, az_gap, sec_az_gap = map(float, line[11:17:2]) gt_level = line[17] min_dist, max_dist, med_dist = map(float, line[19:25:2]) # goto location quality info line line = lines["QML_OriginUncertainty"] hor_unc, min_hor_unc, max_hor_unc, hor_unc_azim = \ map(float, line.split()[1:9:2]) # assign origin info event = Event() cat = Catalog(events=[event]) o = Origin() event.origins = [o] o.origin_uncertainty = OriginUncertainty() o.quality = OriginQuality() ou = o.origin_uncertainty oq = o.quality o.comments.append(Comment(text=stats_info_string)) cat.creation_info.creation_time = UTCDateTime() cat.creation_info.version = "ObsPy %s" % __version__ event.creation_info = CreationInfo(creation_time=creation_time, version=version) event.creation_info.version = version o.creation_info = CreationInfo(creation_time=creation_time, version=version) # negative values can appear on diagonal of covariance matrix due to a # precision problem in NLLoc implementation when location coordinates are # large compared to the covariances. o.longitude = x try: o.longitude_errors.uncertainty = kilometer2degrees(sqrt(covariance_XX)) except ValueError: if covariance_XX < 0: msg = ("Negative value in XX value of covariance matrix, not " "setting longitude error (epicentral uncertainties will " "still be set in origin uncertainty).") warnings.warn(msg) else: raise o.latitude = y try: o.latitude_errors.uncertainty = kilometer2degrees(sqrt(covariance_YY)) except ValueError: if covariance_YY < 0: msg = ("Negative value in YY value of covariance matrix, not " "setting longitude error (epicentral uncertainties will " "still be set in origin uncertainty).") warnings.warn(msg) else: raise o.depth = z * 1e3 # meters! o.depth_errors.uncertainty = sqrt(covariance_ZZ) * 1e3 # meters! o.depth_errors.confidence_level = 68 o.depth_type = str("from location") o.time = time ou.horizontal_uncertainty = hor_unc ou.min_horizontal_uncertainty = min_hor_unc ou.max_horizontal_uncertainty = max_hor_unc # values of -1 seem to be used for unset values, set to None for field in ("horizontal_uncertainty", "min_horizontal_uncertainty", "max_horizontal_uncertainty"): if ou.get(field, -1) == -1: ou[field] = None else: ou[field] *= 1e3 # meters! ou.azimuth_max_horizontal_uncertainty = hor_unc_azim ou.preferred_description = str("uncertainty ellipse") ou.confidence_level = 68 # NonLinLoc in general uses 1-sigma (68%) level oq.standard_error = stderr oq.azimuthal_gap = az_gap oq.secondary_azimuthal_gap = sec_az_gap oq.used_phase_count = used_phase_count oq.used_station_count = used_station_count oq.associated_phase_count = assoc_phase_count oq.associated_station_count = assoc_station_count oq.depth_phase_count = depth_phase_count oq.ground_truth_level = gt_level oq.minimum_distance = kilometer2degrees(min_dist) oq.maximum_distance = kilometer2degrees(max_dist) oq.median_distance = kilometer2degrees(med_dist) # go through all phase info lines for line in phases_lines: line = line.split() arrival = Arrival() o.arrivals.append(arrival) station = str(line[0]) phase = str(line[4]) arrival.phase = phase arrival.distance = kilometer2degrees(float(line[21])) arrival.azimuth = float(line[23]) arrival.takeoff_angle = float(line[24]) arrival.time_residual = float(line[16]) arrival.time_weight = float(line[17]) pick = Pick() wid = WaveformStreamID(station_code=station) date, hourmin, sec = map(str, line[6:9]) t = UTCDateTime().strptime(date + hourmin, "%Y%m%d%H%M") + float(sec) pick.waveform_id = wid pick.time = t pick.time_errors.uncertainty = float(line[10]) pick.phase_hint = phase pick.onset = ONSETS.get(line[3].lower(), None) pick.polarity = POLARITIES.get(line[5].lower(), None) # try to determine original pick for each arrival for pick_ in original_picks: wid = pick_.waveform_id if station == wid.station_code and phase == pick_.phase_hint: pick = pick_ break else: # warn if original picks were specified and we could not associate # the arrival correctly if original_picks: msg = ("Could not determine corresponding original pick for " "arrival. " "Falling back to pick information in NonLinLoc " "hypocenter-phase file.") warnings.warn(msg) event.picks.append(pick) arrival.pick_id = pick.resource_id return cat
def full_test_event(): """ Function to generate a basic, full test event """ test_event = Event() test_event.origins.append( Origin(time=UTCDateTime("2012-03-26") + 1.2, latitude=45.0, longitude=25.0, depth=15000)) test_event.event_descriptions.append(EventDescription()) test_event.event_descriptions[0].text = 'LE' test_event.creation_info = CreationInfo(agency_id='TES') test_event.magnitudes.append( Magnitude(mag=0.1, magnitude_type='ML', creation_info=CreationInfo('TES'), origin_id=test_event.origins[0].resource_id)) test_event.magnitudes.append( Magnitude(mag=0.5, magnitude_type='Mc', creation_info=CreationInfo('TES'), origin_id=test_event.origins[0].resource_id)) test_event.magnitudes.append( Magnitude(mag=1.3, magnitude_type='Ms', creation_info=CreationInfo('TES'), origin_id=test_event.origins[0].resource_id)) # Define the test pick _waveform_id_1 = WaveformStreamID(station_code='FOZ', channel_code='SHZ', network_code='NZ') _waveform_id_2 = WaveformStreamID(station_code='WTSZ', channel_code='BH1', network_code=' ') # Pick to associate with amplitude - 0 test_event.picks = [ Pick(waveform_id=_waveform_id_1, phase_hint='IAML', polarity='undecidable', time=UTCDateTime("2012-03-26") + 1.68, evaluation_mode="manual"), Pick(waveform_id=_waveform_id_1, onset='impulsive', phase_hint='PN', polarity='positive', time=UTCDateTime("2012-03-26") + 1.68, evaluation_mode="manual"), Pick(waveform_id=_waveform_id_1, phase_hint='IAML', polarity='undecidable', time=UTCDateTime("2012-03-26") + 1.68, evaluation_mode="manual"), Pick(waveform_id=_waveform_id_2, onset='impulsive', phase_hint='SG', polarity='undecidable', time=UTCDateTime("2012-03-26") + 1.72, evaluation_mode="manual"), Pick(waveform_id=_waveform_id_2, onset='impulsive', phase_hint='PN', polarity='undecidable', time=UTCDateTime("2012-03-26") + 1.62, evaluation_mode="automatic") ] # Test a generic local magnitude amplitude pick test_event.amplitudes = [ Amplitude(generic_amplitude=2.0, period=0.4, pick_id=test_event.picks[0].resource_id, waveform_id=test_event.picks[0].waveform_id, unit='m', magnitude_hint='ML', category='point', type='AML'), Amplitude(generic_amplitude=10, pick_id=test_event.picks[1].resource_id, waveform_id=test_event.picks[1].waveform_id, type='END', category='duration', unit='s', magnitude_hint='Mc', snr=2.3), Amplitude(generic_amplitude=5.0, period=0.6, pick_id=test_event.picks[2].resource_id, waveform_id=test_event.picks[0].waveform_id, unit='m', category='point', type='AML') ] test_event.origins[0].arrivals = [ Arrival(time_weight=0, phase=test_event.picks[1].phase_hint, pick_id=test_event.picks[1].resource_id), Arrival(time_weight=2, phase=test_event.picks[3].phase_hint, pick_id=test_event.picks[3].resource_id, backazimuth_residual=5, time_residual=0.2, distance=15, azimuth=25), Arrival(time_weight=2, phase=test_event.picks[4].phase_hint, pick_id=test_event.picks[4].resource_id, backazimuth_residual=5, time_residual=0.2, distance=15, azimuth=25) ] # Add in error info (line E) test_event.origins[0].quality = OriginQuality(standard_error=0.01, azimuthal_gap=36) # Origin uncertainty in Seisan is output as long-lat-depth, quakeML has # semi-major and semi-minor test_event.origins[0].origin_uncertainty = OriginUncertainty( confidence_ellipsoid=ConfidenceEllipsoid( semi_major_axis_length=3000, semi_minor_axis_length=1000, semi_intermediate_axis_length=2000, major_axis_plunge=20, major_axis_azimuth=100, major_axis_rotation=4)) test_event.origins[0].time_errors = QuantityError(uncertainty=0.5) # Add in fault-plane solution info (line F) - Note have to check program # used to determine which fields are filled.... test_event.focal_mechanisms.append( FocalMechanism(nodal_planes=NodalPlanes( nodal_plane_1=NodalPlane(strike=180, dip=20, rake=30, strike_errors=QuantityError(10), dip_errors=QuantityError(10), rake_errors=QuantityError(20))), method_id=ResourceIdentifier( "smi:nc.anss.org/focalMechanism/FPFIT"), creation_info=CreationInfo(agency_id="NC"), misfit=0.5, station_distribution_ratio=0.8)) # Need to test high-precision origin and that it is preferred origin. # Moment tensor includes another origin test_event.origins.append( Origin(time=UTCDateTime("2012-03-26") + 1.2, latitude=45.1, longitude=25.2, depth=14500)) test_event.magnitudes.append( Magnitude(mag=0.1, magnitude_type='MW', creation_info=CreationInfo('TES'), origin_id=test_event.origins[-1].resource_id)) # Moment tensors go with focal-mechanisms test_event.focal_mechanisms.append( FocalMechanism(moment_tensor=MomentTensor( derived_origin_id=test_event.origins[-1].resource_id, moment_magnitude_id=test_event.magnitudes[-1].resource_id, scalar_moment=100, tensor=Tensor( m_rr=100, m_tt=100, m_pp=10, m_rt=1, m_rp=20, m_tp=15), method_id=ResourceIdentifier( 'smi:nc.anss.org/momentTensor/BLAH')))) return test_event
def brightness(stations, nodes, lags, stream, threshold, thresh_type, template_length, template_saveloc, coherence_thresh, coherence_stations=['all'], coherence_clip=False, gap=2.0, clip_level=100, instance=0, pre_pick=0.2, plotvar=False, plotsave=True, cores=1, debug=0, mem_issue=False): """ Calculate the brightness function for a single day. Written to calculate the brightness function for a single day of data, using moveouts from a 3D travel-time grid. .. Note:: Data in stream must be all of the same length and have the same sampling rates, see :func:`eqcorrscan.utils.pre_processing.dayproc` :type stations: list :param stations: List of station names from in the form where stations[i] refers to nodes[i][:] and lags[i][:] :type nodes: list :param nodes: List of node points where nodes[i] refers to stations[i] and nodes[:][:][0] is latitude in degrees, nodes[:][:][1] is longitude in degrees, nodes[:][:][2] is depth in km. :type lags: numpy.ndarray :param lags: Array of arrays where lags[i][:] refers to stations[i]. lags[i][j] should be the delay to the nodes[i][j] for stations[i] in seconds. :type stream: obspy.core.stream.Stream :param stream: Data through which to look for detections. :type threshold: float :param threshold: Threshold value for detection of template within the brightness function. :type thresh_type: str :param thresh_type: Either MAD or abs where MAD is the Median Absolute Deviation and abs is an absolute brightness. :type template_length: float :param template_length: Length of template to extract in seconds :type template_saveloc: str :param template_saveloc: Path of where to save the templates. :type coherence_thresh: tuple :param coherence_thresh: Threshold for removing incoherent peaks in the network response, those below this will not be used as templates. Must be in the form of (a,b) where the coherence is given by: :math:`a-kchan/b` where kchan is the number of channels used to compute the coherence. :type coherence_stations: list :param coherence_stations: List of stations to use in the coherence thresholding - defaults to `all` which uses all the stations. :type coherence_clip: tuple :param coherence_clip: Start and end in seconds of data to window around, defaults to False, which uses all the data given. :type gap: float :param gap: Minimum inter-event time in seconds for detections. :type clip_level: float :param clip_level: Multiplier applied to the mean deviation of the energy as an upper limit, used to remove spikes (earthquakes, lightning, electrical spikes) from the energy stack. :type instance: int :param instance: Optional, used for tracking when using a distributed computing system. :type pre_pick: float :param pre_pick: Seconds before the detection time to include in template :type plotvar: bool :param plotvar: Turn plotting on or off :type plotsave: bool :param plotsave: Save or show plots, if `False` will try and show the plots on screen - as this is designed for bulk use this is set to `True` to save any plots rather than show them if you create them - changes the backend of matplotlib, so if is set to `False` you will see NO PLOTS! :type cores: int :param cores: Number of cores to use, defaults to 1. :type debug: int :param debug: Debug level from 0-5, higher is more output. :type mem_issue: bool :param mem_issue: Set to True to write temporary variables to disk rather than store in memory, slow. :return: list of templates as :class:`obspy.core.stream.Stream` objects :rtype: list """ if plotsave: import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt plt.ioff() from eqcorrscan.utils import plotting from eqcorrscan.utils.debug_log import debug_print # Check that we actually have the correct stations realstations = [] for station in stations: st = stream.select(station=station) if st: realstations += station del st stream_copy = stream.copy() # Force convert to int16 for tr in stream_copy: # int16 max range is +/- 32767 if max(abs(tr.data)) > 32767: tr.data = 32767 * (tr.data / max(abs(tr.data))) # Make sure that the data aren't clipped it they are high gain # scale the data tr.data = tr.data.astype(np.int16) # The internal _node_loop converts energy to int16 too to conserve memory, # to do this it forces the maximum of a single energy trace to be 500 and # normalises to this level - this only works for fewer than 65 channels of # data if len(stream_copy) > 130: raise BrightnessError( 'Too many streams, either re-code and cope with either more memory' ' usage, or less precision, or reduce data volume') # Loop through each node in the input # Linear run print('Computing the energy stacks') # Parallel run num_cores = cores if num_cores > len(nodes): num_cores = len(nodes) if num_cores > cpu_count(): num_cores = cpu_count() if mem_issue and not os.path.isdir('tmp' + str(instance)): os.makedirs('tmp' + str(instance)) pool = Pool(processes=num_cores) results = [ pool.apply_async( _node_loop, (stations, ), { 'lags': lags[:, i], 'stream': stream, 'i': i, 'clip_level': clip_level, 'mem_issue': mem_issue, 'instance': instance }) for i in range(len(nodes)) ] pool.close() if not mem_issue: print('Computing the cumulative network response from memory') energy = [p.get() for p in results] pool.join() energy.sort(key=lambda tup: tup[0]) energy = [node[1] for node in energy] energy = np.concatenate(energy, axis=0) print(energy.shape) else: pool.join() del results # Now compute the cumulative network response and then detect possible # events if not mem_issue: print(energy.shape) indices = np.argmax(energy, axis=0) # Indices of maximum energy print(indices.shape) cum_net_resp = np.array([np.nan] * len(indices)) cum_net_resp[0] = energy[indices[0]][0] peak_nodes = [nodes[indices[0]]] for i in range(1, len(indices)): cum_net_resp[i] = energy[indices[i]][i] peak_nodes.append(nodes[indices[i]]) del energy, indices else: print('Reading the temp files and computing network response') node_splits = int(len(nodes) // num_cores) print(node_splits) indices = [] for i in range(num_cores): indices.append( list(np.arange(node_splits * i, node_splits * (i + 1)))) indices[-1] += list(np.arange(node_splits * (i + 1), len(nodes))) # results = [_cum_net_resp(node_lis=indices[i], instance=instance) # for i in range(num_cores)] pool = Pool(processes=num_cores) results = [ pool.apply_async(_cum_net_resp, args=(indices[i], instance)) for i in range(num_cores) ] pool.close() results = [p.get() for p in results] pool.join() responses = [result[0] for result in results] print(np.shape(responses)) node_indices = [result[1] for result in results] cum_net_resp = np.array(responses) indices = np.argmax(cum_net_resp, axis=0) print(indices.shape) print(cum_net_resp.shape) cum_net_resp = np.array( [cum_net_resp[indices[i]][i] for i in range(len(indices))]) peak_nodes = [ nodes[node_indices[indices[i]][i]] for i in range(len(indices)) ] del indices, node_indices if plotvar: cum_net_trace = Stream( Trace(data=cum_net_resp, header=Stats({ 'station': 'NR', 'channel': '', 'network': 'Z', 'location': '', 'starttime': stream[0].stats.starttime, 'sampling_rate': stream[0].stats.sampling_rate }))) cum_net_trace += stream.select(channel='*N') cum_net_trace += stream.select(channel='*1') cum_net_trace.sort(['network', 'station', 'channel']) # Find detection within this network response print('Finding detections in the cumulative network response') detections = _find_detections(cum_net_resp, peak_nodes, threshold, thresh_type, stream[0].stats.sampling_rate, realstations, gap) del cum_net_resp templates = [] nodesout = [] good_detections = [] if detections: print('Converting detections into templates') # Generate a catalog of detections # detections_cat = Catalog() for j, detection in enumerate(detections): debug_print( 'Converting for detection %i of %i' % (j, len(detections)), 3, debug) # Create an event for each detection event = Event() # Set up some header info for the event event.event_descriptions.append(EventDescription()) event.event_descriptions[0].text = 'Brightness detection' event.creation_info = CreationInfo(agency_id='EQcorrscan') copy_of_stream = deepcopy(stream_copy) # Convert detections to obspy.core.event type - # name of detection template is the node. node = (detection.template_name.split('_')[0], detection.template_name.split('_')[1], detection.template_name.split('_')[2]) # Look up node in nodes and find the associated lags index = nodes.index( (float(node[0]), float(node[1]), float(node[2]))) detect_lags = lags[:, index] ksta = Comment(text='Number of stations=' + str(len(detect_lags))) event.origins.append(Origin()) event.origins[0].comments.append(ksta) event.origins[0].time = copy_of_stream[0].stats.starttime +\ detect_lags[0] + detection.detect_time event.origins[0].latitude = node[0] event.origins[0].longitude = node[1] event.origins[0].depth = node[2] for i, detect_lag in enumerate(detect_lags): station = stations[i] st = copy_of_stream.select(station=station) if len(st) != 0: for tr in st: _waveform_id = WaveformStreamID( station_code=tr.stats.station, channel_code=tr.stats.channel, network_code=tr.stats.network) event.picks.append( Pick(waveform_id=_waveform_id, time=tr.stats.starttime + detect_lag + detection.detect_time + pre_pick, onset='emergent', evalutation_mode='automatic')) debug_print('Generating template for detection: %i' % j, 0, debug) template = _template_gen(picks=event.picks, st=copy_of_stream, length=template_length, swin='all') template_name = template_saveloc + '/' +\ str(template[0].stats.starttime) + '.ms' # In the interests of RAM conservation we write then read # Check coherency here! temp_coher, kchan = coherence(template, coherence_stations, coherence_clip) coh_thresh = float(coherence_thresh[0]) - kchan / \ float(coherence_thresh[1]) coherent = False if temp_coher > coh_thresh: template.write(template_name, format="MSEED") print('Written template as: ' + template_name) print('---------------------------------coherence LEVEL: ' + str(temp_coher)) coherent = True debug_print( 'Template was incoherent, coherence level: ' + str(temp_coher), 0, debug) coherent = False del copy_of_stream, tr, template if coherent: templates.append(obsread(template_name)) nodesout += [node] good_detections.append(detection) debug_print('No template for you', 0, debug) # detections_cat += event if plotvar: good_detections = [(cum_net_trace[-1].stats.starttime + detection.detect_time).datetime for detection in good_detections] if not plotsave: plotting.NR_plot(cum_net_trace[0:-1], Stream(cum_net_trace[-1]), detections=good_detections, size=(18.5, 10), title='Network response') # cum_net_trace.plot(size=(800,600), equal_scale=False) else: savefile = 'plots/' +\ cum_net_trace[0].stats.starttime.datetime.strftime('%Y%m%d') +\ '_NR_timeseries.pdf' plotting.NR_plot(cum_net_trace[0:-1], Stream(cum_net_trace[-1]), detections=good_detections, size=(18.5, 10), save=True, savefile=savefile, title='Network response') nodesout = list(set(nodesout)) return templates, nodesout
def readpicks(sfile): """ Read all pick information from the s-file to an obspy.event.Catalog type. .. note:: This was changed for version 0.1.0 from using the inbuilt \ PICK class. :type sfile: str :param sfile: Path to sfile :return: obspy.core.event.Event .. warning:: Currently finalweight is unsupported, nor is velocity, \ or angle of incidence. This is because obspy.event stores slowness \ in s/deg and takeoff angle, which would require computation from the \ values stored in seisan. Multiple weights are also not supported in \ Obspy.event. .. rubric:: Example >>> event = readpicks('eqcorrscan/tests/test_data/REA/TEST_/' + ... '01-0411-15L.S201309') >>> print(event.origins[0].time) 2013-09-01T04:11:15.700000Z >>> print(event.picks[0].time) 2013-09-01T04:11:17.240000Z """ from obspy.core.event import Pick, WaveformStreamID, Arrival, Amplitude # Get wavefile name for use in resource_ids wav_names = readwavename(sfile) # First we need to read the header to get the timing info new_event = readheader(sfile) evtime = new_event.origins[0].time f = open(sfile, 'r') pickline = [] # Set a default, ignored later unless overwritten SNR = 999 if 'headerend' in locals(): del headerend for lineno, line in enumerate(f): if 'headerend' in locals(): if len(line.rstrip('\n').rstrip('\r')) in [80, 79] and \ (line[79] == ' ' or line[79] == '4' or line[79] == '\n'): pickline += [line] elif line[79] == '7': header = line headerend = lineno amplitude_index = 0 for pick_index, line in enumerate(pickline): if line[18:28].strip() == '': # If line is empty miss it continue station = line[1:6].strip() channel = line[6:8].strip() network = 'NA' # No network information provided in Sfile. weight = line[14] if weight == '_': phase = line[10:17] weight = 0 polarity = '' else: phase = line[10:14].strip() polarity = line[16] if weight == ' ': weight = 0 if polarity == '': polarity = "undecidable" elif polarity == 'C': polarity = "positive" elif polarity == 'D': polarity = 'negative' else: polarity = "undecidable" try: time = UTCDateTime(evtime.year, evtime.month, evtime.day, int(line[18:20]), int(line[20:22]), int(line[23:28].split('.')[0]), int(line[23:28].split('.')[1]) * 10000) except (ValueError): time = UTCDateTime(evtime.year, evtime.month, evtime.day, int(line[18:20]), int(line[20:22]), 0, 0) time += 60 # Add 60 seconds on to the time, this copes with s-file # preference to write seconds in 1-60 rather than 0-59 which # datetime objects accept coda = _int_conv(line[28:33]) amplitude = _float_conv(line[33:40]) peri = _float_conv(line[41:45]) azimuth = _float_conv(line[46:51]) velocity = _float_conv(line[52:56]) if header[57:60] == 'AIN': AIN = _float_conv(line[57:60]) elif header[57:60] == 'SNR': SNR = _float_conv(line[57:60]) azimuthres = _int_conv(line[60:63]) timeres = _float_conv(line[63:68]) finalweight = _int_conv(line[68:70]) distance = _float_conv(line[70:75]) CAZ = _int_conv(line[76:79]) # Create a new obspy.event.Pick class for this pick _waveform_id = WaveformStreamID(station_code=station, channel_code=channel, network_code=network) new_event.picks.append( Pick(waveform_id=_waveform_id, phase_hint=phase, polarity=polarity, time=time)) if line[9] == 'I': new_event.picks[pick_index].onset = 'impulsive' elif line[9] == 'E': new_event.picks[pick_index].onset = 'emergent' if line[15] == 'A': new_event.picks[pick_index].evaluation_mode = 'automatic' else: new_event.picks[pick_index].evaluation_mode = 'manual' # Note these two are not always filled - velocity conversion not yet # implimented, needs to be converted from km/s to s/deg # if not velocity == 999.0: # new_event.picks[pick_index].horizontal_slowness = 1.0 / velocity if not azimuth == 999: new_event.picks[pick_index].backazimuth = azimuth del _waveform_id # Create new obspy.event.Amplitude class which references above Pick # only if there is an amplitude picked. if not amplitude == 999.0: new_event.amplitudes.append( Amplitude(generic_amplitude=amplitude, period=peri, pick_id=new_event.picks[pick_index].resource_id, waveform_id=new_event.picks[pick_index].waveform_id)) if new_event.picks[pick_index].phase_hint == 'IAML': # Amplitude for local magnitude new_event.amplitudes[amplitude_index].type = 'AML' # Set to be evaluating a point in the trace new_event.amplitudes[amplitude_index].category = 'point' # Default AML unit in seisan is nm (Page 139 of seisan # documentation, version 10.0) new_event.amplitudes[amplitude_index].generic_amplitude /=\ 10**9 new_event.amplitudes[amplitude_index].unit = 'm' new_event.amplitudes[amplitude_index].magnitude_hint = 'ML' else: # Generic amplitude type new_event.amplitudes[amplitude_index].type = 'A' if not SNR == 999.0: new_event.amplitudes[amplitude_index].snr = SNR amplitude_index += 1 elif not coda == 999: # Create an amplitude instance for code duration also new_event.amplitudes.append( Amplitude(generic_amplitude=coda, pick_id=new_event.picks[pick_index].resource_id, waveform_id=new_event.picks[pick_index].waveform_id)) # Amplitude for coda magnitude new_event.amplitudes[amplitude_index].type = 'END' # Set to be evaluating a point in the trace new_event.amplitudes[amplitude_index].category = 'duration' new_event.amplitudes[amplitude_index].unit = 's' new_event.amplitudes[amplitude_index].magnitude_hint = 'Mc' if SNR and not SNR == 999.0: new_event.amplitudes[amplitude_index].snr = SNR amplitude_index += 1 # Create new obspy.event.Arrival class referencing above Pick new_event.origins[0].arrivals.append( Arrival(phase=new_event.picks[pick_index].phase_hint, pick_id=new_event.picks[pick_index].resource_id)) if weight != 999: new_event.origins[0].arrivals[pick_index].time_weight =\ weight if azimuthres != 999: new_event.origins[0].arrivals[pick_index].backazimuth_residual =\ azimuthres if timeres != 999: new_event.origins[0].arrivals[pick_index].time_residual =\ timeres if distance != 999: new_event.origins[0].arrivals[pick_index].distance =\ distance if CAZ != 999: new_event.origins[0].arrivals[pick_index].azimuth =\ CAZ f.close() # Write event to catalog object for ease of .write() method return new_event
def stalta_pick(stream, stalen, ltalen, trig_on, trig_off, freqmin=False, freqmax=False, debug=0, show=False): """ Basic sta/lta picker, suggest using alternative in obspy. Simple sta/lta (short-term average/long-term average) picker, using obspy's :func:`obspy.signal.trigger.classic_sta_lta` routine to generate the characteristic function. Currently very basic quick wrapper, there are many other (better) options in obspy in the :mod:`obspy.signal.trigger` module. :type stream: obspy.core.stream.Stream :param stream: The stream to pick on, can be any number of channels. :type stalen: float :param stalen: Length of the short-term average window in seconds. :type ltalen: float :param ltalen: Length of the long-term average window in seconds. :type trig_on: float :param trig_on: sta/lta ratio to trigger a detection/pick :type trig_off: float :param trig_off: sta/lta ratio to turn the trigger off - no further picks\ will be made between exceeding trig_on until trig_off is reached. :type freqmin: float :param freqmin: Low-cut frequency in Hz for bandpass filter :type freqmax: float :param freqmax: High-cut frequency in Hz for bandpass filter :type debug: int :param debug: Debug output level from 0-5. :type show: bool :param show: Show picks on waveform. :returns: :class:`obspy.core.event.event.Event` .. rubric:: Example >>> from obspy import read >>> from eqcorrscan.utils.picker import stalta_pick >>> st = read() >>> event = stalta_pick(st, stalen=0.2, ltalen=4, trig_on=10, ... trig_off=1, freqmin=3.0, freqmax=20.0) >>> print(event.creation_info.author) EQcorrscan .. warning:: This function is not designed for accurate picking, rather it can give a first idea of whether picks may be possible. Proceed with caution. """ event = Event() event.origins.append(Origin()) event.creation_info = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) event.comments.append(Comment(text='stalta')) picks = [] for tr in stream: # We are going to assume, for now, that if the pick is made on the # horizontal channel then it is an S, otherwise we will assume it is # a P-phase: obviously a bad assumption... if tr.stats.channel[-1] == 'Z': phase = 'P' else: phase = 'S' if freqmin and freqmax: tr.detrend('simple') tr.filter('bandpass', freqmin=freqmin, freqmax=freqmax, corners=3, zerophase=True) df = tr.stats.sampling_rate cft = classic_sta_lta(tr.data, int(stalen * df), int(ltalen * df)) if debug > 3: plot_trigger(tr, cft, trig_on, trig_off) triggers = trigger_onset(cft, trig_on, trig_off) for trigger in triggers: on = tr.stats.starttime + (trigger[0] / df) # off = tr.stats.starttime + (trigger[1] / df) wav_id = WaveformStreamID(station_code=tr.stats.station, channel_code=tr.stats.channel, network_code=tr.stats.network) pick = Pick(waveform_id=wav_id, phase_hint=phase, time=on) if debug > 2: print('Pick made:') print(pick) picks.append(pick) # QC picks del pick pick_stations = list(set([pick.waveform_id.station_code for pick in picks])) for pick_station in pick_stations: station_picks = [pick for pick in picks if pick.waveform_id.station_code == pick_station] # If P-pick is after S-picks, remove it. p_time = [pick.time for pick in station_picks if pick.phase_hint == 'P'] s_time = [pick.time for pick in station_picks if pick.phase_hint == 'S'] if p_time > s_time: p_pick = [pick for pick in station_picks if pick.phase_hint == 'P'] for pick in p_pick: print('P pick after S pick, removing P pick') picks.remove(pick) if show: plotting.pretty_template_plot(stream, picks=picks, title='Autopicks', size=(8, 9)) event.picks = picks event.origins[0].time = min([pick.time for pick in event.picks]) - 1 event.origins[0].latitude = float('nan') event.origins[0].longitude = float('nan') # Set arbitrary origin time return event
def outputOBSPY(hp, event=None, only_fm_picks=False): """ Make an Event which includes the current focal mechanism information from HASH Use the 'only_fm_picks' flag to only include the picks HASH used for the FocalMechanism. This flag will replace the 'picks' and 'arrivals' lists of existing events with new ones. Inputs ------- hp : hashpy.HashPype instance event : obspy.core.event.Event only_fm_picks : bool of whether to overwrite the picks/arrivals lists Returns ------- obspy.core.event.Event Event will be new if no event was input, FocalMech added to existing event """ # Returns new (or updates existing) Event with HASH solution n = hp.npol if event is None: event = Event(focal_mechanisms=[], picks=[], origins=[]) origin = Origin(arrivals=[]) origin.time = UTCDateTime(hp.tstamp) origin.latitude = hp.qlat origin.longitude = hp.qlon origin.depth = hp.qdep origin.creation_info = CreationInfo(version=hp.icusp) origin.resource_id = ResourceIdentifier('smi:hash/Origin/{0}'.format(hp.icusp)) for _i in range(n): p = Pick() p.creation_info = CreationInfo(version=hp.arid[_i]) p.resource_id = ResourceIdentifier('smi:hash/Pick/{0}'.format(p.creation_info.version)) p.waveform_id = WaveformStreamID(network_code=hp.snet[_i], station_code=hp.sname[_i], channel_code=hp.scomp[_i]) if hp.p_pol[_i] > 0: p.polarity = 'positive' else: p.polarity = 'negative' a = Arrival() a.creation_info = CreationInfo(version=hp.arid[_i]) a.resource_id = ResourceIdentifier('smi:hash/Arrival/{0}'.format(p.creation_info.version)) a.azimuth = hp.p_azi_mc[_i,0] a.takeoff_angle = 180. - hp.p_the_mc[_i,0] a.pick_id = p.resource_id origin.arrivals.append(a) event.picks.append(p) event.origins.append(origin) event.preferred_origin_id = str(origin.resource_id) else: # just update the changes origin = event.preferred_origin() picks = [] arrivals = [] for _i in range(n): ind = hp.p_index[_i] a = origin.arrivals[ind] p = a.pick_id.getReferredObject() a.takeoff_angle = hp.p_the_mc[_i,0] picks.append(p) arrivals.append(a) if only_fm_picks: origin.arrivals = arrivals event.picks = picks # Use me double couple calculator and populate planes/axes etc x = hp._best_quality_index # Put all the mechanisms into the 'focal_mechanisms' list, mark "best" as preferred for s in range(hp.nmult): dc = DoubleCouple([hp.str_avg[s], hp.dip_avg[s], hp.rak_avg[s]]) ax = dc.axis focal_mech = FocalMechanism() focal_mech.creation_info = CreationInfo(creation_time=UTCDateTime(), author=hp.author) focal_mech.triggering_origin_id = origin.resource_id focal_mech.resource_id = ResourceIdentifier('smi:hash/FocalMechanism/{0}/{1}'.format(hp.icusp, s+1)) focal_mech.method_id = ResourceIdentifier('HASH') focal_mech.nodal_planes = NodalPlanes() focal_mech.nodal_planes.nodal_plane_1 = NodalPlane(*dc.plane1) focal_mech.nodal_planes.nodal_plane_2 = NodalPlane(*dc.plane2) focal_mech.principal_axes = PrincipalAxes() focal_mech.principal_axes.t_axis = Axis(azimuth=ax['T']['azimuth'], plunge=ax['T']['dip']) focal_mech.principal_axes.p_axis = Axis(azimuth=ax['P']['azimuth'], plunge=ax['P']['dip']) focal_mech.station_polarity_count = n focal_mech.azimuthal_gap = hp.magap focal_mech.misfit = hp.mfrac[s] focal_mech.station_distribution_ratio = hp.stdr[s] focal_mech.comments.append( Comment(hp.qual[s], resource_id=ResourceIdentifier(str(focal_mech.resource_id) + '/comment/quality')) ) #---------------------------------------- event.focal_mechanisms.append(focal_mech) if s == x: event.preferred_focal_mechanism_id = str(focal_mech.resource_id) return event
def _parse_record_s(self, line, event, p_pick, p_arrival): """ Parses the 'secondary phases' record S Secondary phases are following phases of the reading, and can be P-type or S-type. """ arrivals = [] phase = line[7:15].strip() arrival_time = line[15:24] if phase: arrivals.append((phase, arrival_time)) phase = line[25:33].strip() arrival_time = line[33:42] if phase: arrivals.append((phase, arrival_time)) phase = line[43:51].strip() arrival_time = line[51:60] if phase: arrivals.append((phase, arrival_time)) evid = event.resource_id.id.split('/')[-1] station_string = \ p_pick.waveform_id.get_seed_string()\ .replace(' ', '-').replace('.', '_').lower() origin = event.origins[0] for phase, arrival_time in arrivals: if phase[0:2] == 'D=': # unused: depth = self._float(phase[2:7]) try: depth_usage_flag = phase[7] except IndexError: # usage flag is not defined depth_usage_flag = None # FIXME: I'm not sure that 'X' actually # means 'used' if depth_usage_flag == 'X': # FIXME: is this enough to say that # the event is constrained by depth phases? origin.depth_type = 'constrained by depth phases' origin.quality.depth_phase_count += 1 else: pick = Pick() prefix = '/'.join( (res_id_prefix, 'pick', evid, station_string)) pick.resource_id = ResourceIdentifier(prefix=prefix) date = origin.time.strftime('%Y%m%d') pick.time = UTCDateTime(date + arrival_time) # Check if pick is on the next day: if pick.time < origin.time: pick.time += timedelta(days=1) pick.waveform_id = p_pick.waveform_id pick.backazimuth = p_pick.backazimuth onset = phase[0] if onset == 'e': pick.onset = 'emergent' phase = phase[1:] elif onset == 'i': pick.onset = 'impulsive' phase = phase[1:] elif onset == 'q': pick.onset = 'questionable' phase = phase[1:] pick.phase_hint = phase.strip() event.picks.append(pick) arrival = Arrival() prefix = '/'.join( (res_id_prefix, 'arrival', evid, station_string)) arrival.resource_id = ResourceIdentifier(prefix=prefix) arrival.pick_id = pick.resource_id arrival.phase = pick.phase_hint arrival.azimuth = p_arrival.azimuth arrival.distance = p_arrival.distance origin.quality.associated_phase_count += 1 origin.arrivals.append(arrival)
def _parse_record_s(self, line, event, p_pick, p_arrival): """ Parses the 'secondary phases' record S Secondary phases are following phases of the reading, and can be P-type or S-type. """ arrivals = [] phase = line[7:15].strip() arrival_time = line[15:24] if phase: arrivals.append((phase, arrival_time)) phase = line[25:33].strip() arrival_time = line[33:42] if phase: arrivals.append((phase, arrival_time)) phase = line[43:51].strip() arrival_time = line[51:60] if phase: arrivals.append((phase, arrival_time)) evid = event.resource_id.id.split('/')[-1] station_string = \ p_pick.waveform_id.get_seed_string()\ .replace(' ', '-').replace('.', '_').lower() origin = event.origins[0] for phase, arrival_time in arrivals: if phase[0:2] == 'D=': # unused: depth = self._float(phase[2:7]) try: depth_usage_flag = phase[7] except IndexError: # usage flag is not defined depth_usage_flag = None # FIXME: I'm not sure that 'X' actually # means 'used' if depth_usage_flag == 'X': # FIXME: is this enough to say that # the event is constrained by depth phases? origin.depth_type = 'constrained by depth phases' origin.quality.depth_phase_count += 1 else: pick = Pick() prefix = '/'.join((res_id_prefix, 'pick', evid, station_string)) pick.resource_id = ResourceIdentifier(prefix=prefix) date = origin.time.strftime('%Y%m%d') pick.time = UTCDateTime(date + arrival_time) # Check if pick is on the next day: if pick.time < origin.time: pick.time += timedelta(days=1) pick.waveform_id = p_pick.waveform_id pick.backazimuth = p_pick.backazimuth onset = phase[0] if onset == 'e': pick.onset = 'emergent' phase = phase[1:] elif onset == 'i': pick.onset = 'impulsive' phase = phase[1:] elif onset == 'q': pick.onset = 'questionable' phase = phase[1:] pick.phase_hint = phase.strip() event.picks.append(pick) arrival = Arrival() prefix = '/'.join((res_id_prefix, 'arrival', evid, station_string)) arrival.resource_id = ResourceIdentifier(prefix=prefix) arrival.pick_id = pick.resource_id arrival.phase = pick.phase_hint arrival.azimuth = p_arrival.azimuth arrival.distance = p_arrival.distance origin.quality.associated_phase_count += 1 origin.arrivals.append(arrival)
def __toPick(parser, pick_el, evaluation_mode): """ """ pick = Pick() pick.resource_id = ResourceIdentifier(prefix="/".join([RESOURCE_ROOT, "pick"])) # Raise a warnings if there is a phase delay phase_delay = parser.xpath2obj("phase_delay", pick_el, float) if phase_delay is not None: msg = "The pick has a phase_delay!" raise Exception(msg) waveform = pick_el.xpath("waveform")[0] network = waveform.get("networkCode") station = fix_station_name(waveform.get("stationCode")) # Map some station names. if station in STATION_DICT: station = STATION_DICT[station] if not network: network = NETWORK_DICT[station] location = waveform.get("locationCode") or "" channel = waveform.get("channelCode") or "" pick.waveform_id = WaveformStreamID( network_code=network, station_code=station, channel_code=channel, location_code=location) pick.time, pick.time_errors = __toTimeQuantity(parser, pick_el, "time") # Picks without time are not quakeml conform if pick.time is None: print "Pick has no time and is ignored: %s" % station return None pick.phase_hint = parser.xpath2obj('phaseHint', pick_el, str) onset = parser.xpath2obj('onset', pick_el) # Fixing bad and old typo ... if onset == "implusive": onset = "impulsive" if onset: pick.onset = onset.lower() # Evaluation mode of a pick is global in the SeisHub Event file format. #pick.evaluation_mode = evaluation_mode # The polarity needs to be mapped. polarity = parser.xpath2obj('polarity', pick_el) pol_map_dict = {'up': 'positive', 'positive': 'positive', 'forward': 'positive', 'forwards': 'positive', 'right': 'positive', 'backward': 'negative', 'backwards': 'negative', 'left': 'negative', 'down': 'negative', 'negative': 'negative', 'undecidable': 'undecidable', 'poorup': 'positive', 'poordown': 'negative'} if polarity: if polarity.lower() in pol_map_dict: pick.polarity = pol_map_dict[polarity.lower()] else: pick.polarity = polarity.lower() pick_weight = parser.xpath2obj('weight', pick_el, int) if pick_weight is not None: pick.extra = AttribDict() pick.extra.weight = {'value': pick_weight, 'namespace': NAMESPACE} return pick
def brightness(stations, nodes, lags, stream, threshold, thresh_type, template_length, template_saveloc, coherence_thresh, coherence_stations=['all'], coherence_clip=False, gap=2.0, clip_level=100, instance=0, pre_pick=0.2, plotsave=True, cores=1): r"""Function to calculate the brightness function in terms of energy for \ a day of data over the entire network for a given grid of nodes. Note data in stream must be all of the same length and have the same sampling rates. :type stations: list :param stations: List of station names from in the form where stations[i] \ refers to nodes[i][:] and lags[i][:] :type nodes: list, tuple :param nodes: List of node points where nodes[i] referes to stations[i] \ and nodes[:][:][0] is latitude in degrees, nodes[:][:][1] is \ longitude in degrees, nodes[:][:][2] is depth in km. :type lags: :class: 'numpy.array' :param lags: Array of arrays where lags[i][:] refers to stations[i]. \ lags[i][j] should be the delay to the nodes[i][j] for stations[i] in \ seconds. :type stream: :class: `obspy.Stream` :param data: Data through which to look for detections. :type threshold: float :param threshold: Threshold value for detection of template within the \ brightness function :type thresh_type: str :param thresh_type: Either MAD or abs where MAD is the Median Absolute \ Deviation and abs is an absoulte brightness. :type template_length: float :param template_length: Length of template to extract in seconds :type template_saveloc: str :param template_saveloc: Path of where to save the templates. :type coherence_thresh: tuple of floats :param coherence_thresh: Threshold for removing incoherant peaks in the \ network response, those below this will not be used as templates. \ Must be in the form of (a,b) where the coherence is given by: \ a-kchan/b where kchan is the number of channels used to compute \ the coherence :type coherence_stations: list :param coherence_stations: List of stations to use in the coherance \ thresholding - defaults to 'all' which uses all the stations. :type coherence_clip: float :param coherence_clip: tuple :type coherence_clip: Start and end in seconds of data to window around, \ defaults to False, which uses all the data given. :type pre_pick: float :param pre_pick: Seconds before the detection time to include in template :type plotsave: bool :param plotsave: Save or show plots, if False will try and show the plots \ on screen - as this is designed for bulk use this is set to \ True to save any plots rather than show them if you create \ them - changes the backend of matplotlib, so if is set to \ False you will see NO PLOTS! :type cores: int :param core: Number of cores to use, defaults to 1. :type clip_level: float :param clip_level: Multiplier applied to the mean deviation of the energy \ as an upper limit, used to remove spikes (earthquakes, \ lightning, electircal spikes) from the energy stack. :type gap: float :param gap: Minimum inter-event time in seconds for detections :return: list of templates as :class: `obspy.Stream` objects """ from eqcorrscan.core.template_gen import _template_gen if plotsave: import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt plt.ioff() # from joblib import Parallel, delayed from multiprocessing import Pool, cpu_count from copy import deepcopy from obspy import read as obsread from obspy.core.event import Catalog, Event, Pick, WaveformStreamID, Origin from obspy.core.event import EventDescription, CreationInfo, Comment import obspy.Stream import matplotlib.pyplot as plt from eqcorrscan.utils import plotting # Check that we actually have the correct stations realstations = [] for station in stations: st = stream.select(station=station) if st: realstations += station del st stream_copy = stream.copy() # Force convert to int16 for tr in stream_copy: # int16 max range is +/- 32767 if max(abs(tr.data)) > 32767: tr.data = 32767 * (tr.data / max(abs(tr.data))) # Make sure that the data aren't clipped it they are high gain # scale the data tr.data = tr.data.astype(np.int16) # The internal _node_loop converts energy to int16 too to converse memory, # to do this it forces the maximum of a single energy trace to be 500 and # normalises to this level - this only works for fewer than 65 channels of # data if len(stream_copy) > 130: raise OverflowError('Too many streams, either re-code and cope with' + 'either more memory usage, or less precision, or' + 'reduce data volume') detections = [] detect_lags = [] parallel = True plotvar = True mem_issue = False # Loop through each node in the input # Linear run print('Computing the energy stacks') if not parallel: for i in range(0, len(nodes)): print(i) if not mem_issue: j, a = _node_loop(stations, lags[:, i], stream, plot=True) if 'energy' not in locals(): energy = a else: energy = np.concatenate((energy, a), axis=0) print('energy: ' + str(np.shape(energy))) else: j, filename = _node_loop(stations, lags[:, i], stream, i, mem_issue) energy = np.array(energy) print(np.shape(energy)) else: # Parallel run num_cores = cores if num_cores > len(nodes): num_cores = len(nodes) if num_cores > cpu_count(): num_cores = cpu_count() pool = Pool(processes=num_cores) results = [ pool.apply_async(_node_loop, args=(stations, lags[:, i], stream, i, clip_level, mem_issue, instance)) for i in range(len(nodes)) ] pool.close() if not mem_issue: print('Computing the cumulative network response from memory') energy = [p.get() for p in results] pool.join() energy.sort(key=lambda tup: tup[0]) energy = [node[1] for node in energy] energy = np.concatenate(energy, axis=0) print(energy.shape) else: pool.join() # Now compute the cumulative network response and then detect possible # events if not mem_issue: print(energy.shape) indeces = np.argmax(energy, axis=0) # Indeces of maximum energy print(indeces.shape) cum_net_resp = np.array([np.nan] * len(indeces)) cum_net_resp[0] = energy[indeces[0]][0] peak_nodes = [nodes[indeces[0]]] for i in range(1, len(indeces)): cum_net_resp[i] = energy[indeces[i]][i] peak_nodes.append(nodes[indeces[i]]) del energy, indeces else: print('Reading the temp files and computing network response') node_splits = int(len(nodes) // num_cores) indeces = [range(node_splits)] for i in range(1, num_cores - 1): indeces.append(range(node_splits * i, node_splits * (i + 1))) indeces.append(range(node_splits * (i + 1), len(nodes))) pool = Pool(processes=num_cores) results = [ pool.apply_async(_cum_net_resp, args=(indeces[i], instance)) for i in range(num_cores) ] pool.close() results = [p.get() for p in results] pool.join() responses = [result[0] for result in results] print(np.shape(responses)) node_indeces = [result[1] for result in results] cum_net_resp = np.array(responses) indeces = np.argmax(cum_net_resp, axis=0) print(indeces.shape) print(cum_net_resp.shape) cum_net_resp = np.array( [cum_net_resp[indeces[i]][i] for i in range(len(indeces))]) peak_nodes = [ nodes[node_indeces[indeces[i]][i]] for i in range(len(indeces)) ] del indeces, node_indeces if plotvar: cum_net_trace = deepcopy(stream[0]) cum_net_trace.data = cum_net_resp cum_net_trace.stats.station = 'NR' cum_net_trace.stats.channel = '' cum_net_trace.stats.network = 'Z' cum_net_trace.stats.location = '' cum_net_trace.stats.starttime = stream[0].stats.starttime cum_net_trace = obspy.Stream(cum_net_trace) cum_net_trace += stream.select(channel='*N') cum_net_trace += stream.select(channel='*1') cum_net_trace.sort(['network', 'station', 'channel']) # np.save('cum_net_resp.npy',cum_net_resp) # cum_net_trace.plot(size=(800,600), equal_scale=False,\ # outfile='NR_timeseries.eps') # Find detection within this network response print('Finding detections in the cumulatve network response') detections = _find_detections(cum_net_resp, peak_nodes, threshold, thresh_type, stream[0].stats.sampling_rate, realstations, gap) del cum_net_resp templates = [] nodesout = [] good_detections = [] if detections: print('Converting detections in to templates') # Generate a catalog of detections detections_cat = Catalog() for j, detection in enumerate(detections): print('Converting for detection ' + str(j) + ' of ' + str(len(detections))) # Create an event for each detection event = Event() # Set up some header info for the event event.event_descriptions.append(EventDescription()) event.event_descriptions[0].text = 'Brightness detection' event.creation_info = CreationInfo(agency_id='EQcorrscan') copy_of_stream = deepcopy(stream_copy) # Convert detections to obspy.core.event type - # name of detection template is the node. node = (detection.template_name.split('_')[0], detection.template_name.split('_')[1], detection.template_name.split('_')[2]) print(node) # Look up node in nodes and find the associated lags index = nodes.index(node) detect_lags = lags[:, index] ksta = Comment(text='Number of stations=' + len(detect_lags)) event.origins.append(Origin()) event.origins[0].comments.append(ksta) event.origins[0].time = copy_of_stream[0].stats.starttime +\ detect_lags[0] + detection.detect_time event.origins[0].latitude = node[0] event.origins[0].longitude = node[1] event.origins[0].depth = node[2] for i, detect_lag in enumerate(detect_lags): station = stations[i] st = copy_of_stream.select(station=station) if len(st) != 0: for tr in st: _waveform_id = WaveformStreamID( station_code=tr.stats.station, channel_code=tr.stats.channel, network_code='NA') event.picks.append( Pick(waveform_id=_waveform_id, time=tr.stats.starttime + detect_lag + detection.detect_time + pre_pick, onset='emergent', evalutation_mode='automatic')) print('Generating template for detection: ' + str(j)) template = (_template_gen(event.picks, copy_of_stream, template_length, 'all')) template_name = template_saveloc + '/' +\ str(template[0].stats.starttime) + '.ms' # In the interests of RAM conservation we write then read # Check coherancy here! temp_coher, kchan = coherence(template, coherence_stations, coherence_clip) coh_thresh = float(coherence_thresh[0]) - kchan / \ float(coherence_thresh[1]) if temp_coher > coh_thresh: template.write(template_name, format="MSEED") print('Written template as: ' + template_name) print('---------------------------------coherence LEVEL: ' + str(temp_coher)) coherant = True else: print('Template was incoherant, coherence level: ' + str(temp_coher)) coherant = False del copy_of_stream, tr, template if coherant: templates.append(obsread(template_name)) nodesout += [node] good_detections.append(detection) else: print('No template for you') if plotvar: all_detections = [(cum_net_trace[-1].stats.starttime + detection.detect_time).datetime for detection in detections] good_detections = [(cum_net_trace[-1].stats.starttime + detection.detect_time).datetime for detection in good_detections] if not plotsave: plotting.NR_plot(cum_net_trace[0:-1], obspy.Stream(cum_net_trace[-1]), detections=good_detections, size=(18.5, 10), title='Network response') # cum_net_trace.plot(size=(800,600), equal_scale=False) else: savefile = 'plots/' +\ cum_net_trace[0].stats.starttime.datetime.strftime('%Y%m%d') +\ '_NR_timeseries.pdf' plotting.NR_plot(cum_net_trace[0:-1], obspy.Stream(cum_net_trace[-1]), detections=good_detections, size=(18.5, 10), save=savefile, title='Network response') nodesout = list(set(nodesout)) return templates, nodesout
def test_write_with_extra_tags_and_read(self): """ Tests that a QuakeML file with additional custom "extra" tags gets written correctly and that when reading it again the extra tags are parsed correctly. """ filename = os.path.join(self.path, "quakeml_1.2_origin.xml") with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") cat = _read_quakeml(filename) self.assertEqual(len(w), 0) # add some custom tags to first event: # - tag with explicit namespace but no explicit ns abbreviation # - tag without explicit namespace (gets obspy default ns) # - tag with explicit namespace and namespace abbreviation my_extra = AttribDict( {'public': {'value': False, 'namespace': r"http://some-page.de/xmlns/1.0", 'attrib': {u"some_attrib": u"some_value", u"another_attrib": u"another_value"}}, 'custom': {'value': u"True", 'namespace': r'http://test.org/xmlns/0.1'}, 'new_tag': {'value': 1234, 'namespace': r"http://test.org/xmlns/0.1"}, 'tX': {'value': UTCDateTime('2013-01-02T13:12:14.600000Z'), 'namespace': r'http://test.org/xmlns/0.1'}, 'dataid': {'namespace': r'http://anss.org/xmlns/catalog/0.1', 'type': 'attribute', 'value': '00999999'}}) nsmap = {"ns0": r"http://test.org/xmlns/0.1", "catalog": r'http://anss.org/xmlns/catalog/0.1'} cat[0].extra = my_extra.copy() # insert a pick with an extra field p = Pick() p.extra = {'weight': {'value': 2, 'namespace': r"http://test.org/xmlns/0.1"}} cat[0].picks.append(p) with NamedTemporaryFile() as tf: tmpfile = tf.name # write file cat.write(tmpfile, format="QUAKEML", nsmap=nsmap) # check contents with open(tmpfile, "rb") as fh: # enforce reproducible attribute orders through write_c14n obj = etree.fromstring(fh.read()).getroottree() buf = io.BytesIO() obj.write_c14n(buf) buf.seek(0, 0) content = buf.read() # check namespace definitions in root element expected = [b'<q:quakeml', b'xmlns:catalog="http://anss.org/xmlns/catalog/0.1"', b'xmlns:ns0="http://test.org/xmlns/0.1"', b'xmlns:ns1="http://some-page.de/xmlns/1.0"', b'xmlns:q="http://quakeml.org/xmlns/quakeml/1.2"', b'xmlns="http://quakeml.org/xmlns/bed/1.2"'] for line in expected: self.assertIn(line, content) # check additional tags expected = [ b'<ns0:custom>True</ns0:custom>', b'<ns0:new_tag>1234</ns0:new_tag>', b'<ns0:tX>2013-01-02T13:12:14.600000Z</ns0:tX>', b'<ns1:public ' b'another_attrib="another_value" ' b'some_attrib="some_value">false</ns1:public>' ] for line in expected: self.assertIn(line, content) # now, read again to test if it's parsed correctly.. cat = _read_quakeml(tmpfile) # when reading.. # - namespace abbreviations should be disregarded # - we always end up with a namespace definition, even if it was # omitted when originally setting the custom tag # - custom namespace abbreviations should attached to Catalog self.assertTrue(hasattr(cat[0], "extra")) def _tostr(x): if isinstance(x, bool): if x: return str("true") else: return str("false") return str(x) for key, value in my_extra.items(): my_extra[key]['value'] = _tostr(value['value']) self.assertEqual(cat[0].extra, my_extra) self.assertTrue(hasattr(cat[0].picks[0], "extra")) self.assertEqual( cat[0].picks[0].extra, {'weight': {'value': '2', 'namespace': r'http://test.org/xmlns/0.1'}}) self.assertTrue(hasattr(cat, "nsmap")) self.assertEqual(getattr(cat, "nsmap")['ns0'], nsmap['ns0'])
def _parse_record_p(self, line, event): """ Parses the 'primary phase record' P The primary phase is the first phase of the reading, regardless its type. """ station = line[2:7].strip() phase = line[7:15] arrival_time = line[15:24] residual = self._float(line[25:30]) # unused: residual_flag = line[30] distance = self._float(line[32:38]) # degrees azimuth = self._float(line[39:44]) backazimuth = round(azimuth % -360 + 180, 1) mb_period = self._float(line[44:48]) mb_amplitude = self._float(line[48:55]) # nanometers mb_magnitude = self._float(line[56:59]) # unused: mb_usage_flag = line[59] origin = event.origins[0] evid = event.resource_id.id.split('/')[-1] waveform_id = WaveformStreamID() waveform_id.station_code = station # network_code is required for QuakeML validation waveform_id.network_code = ' ' station_string = \ waveform_id.get_seed_string()\ .replace(' ', '-').replace('.', '_').lower() prefix = '/'.join((res_id_prefix, 'waveformstream', evid, station_string)) waveform_id.resource_uri = ResourceIdentifier(prefix=prefix) pick = Pick() prefix = '/'.join((res_id_prefix, 'pick', evid, station_string)) pick.resource_id = ResourceIdentifier(prefix=prefix) date = origin.time.strftime('%Y%m%d') pick.time = UTCDateTime(date + arrival_time) # Check if pick is on the next day: if pick.time < origin.time: pick.time += timedelta(days=1) pick.waveform_id = waveform_id pick.backazimuth = backazimuth onset = phase[0] if onset == 'e': pick.onset = 'emergent' phase = phase[1:] elif onset == 'i': pick.onset = 'impulsive' phase = phase[1:] elif onset == 'q': pick.onset = 'questionable' phase = phase[1:] pick.phase_hint = phase.strip() event.picks.append(pick) if mb_amplitude is not None: amplitude = Amplitude() prefix = '/'.join((res_id_prefix, 'amp', evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = mb_amplitude * 1E-9 amplitude.unit = 'm' amplitude.period = mb_period amplitude.type = 'AB' amplitude.magnitude_hint = 'Mb' amplitude.pick_id = pick.resource_id amplitude.waveform_id = pick.waveform_id event.amplitudes.append(amplitude) station_magnitude = StationMagnitude() prefix = '/'.join((res_id_prefix, 'stationmagntiude', evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = origin.resource_id station_magnitude.mag = mb_magnitude # station_magnitude.mag_errors['uncertainty'] = 0.0 station_magnitude.station_magnitude_type = 'Mb' station_magnitude.amplitude_id = amplitude.resource_id station_magnitude.waveform_id = pick.waveform_id res_id = '/'.join( (res_id_prefix, 'magnitude/generic/body_wave_magnitude')) station_magnitude.method_id = \ ResourceIdentifier(id=res_id) event.station_magnitudes.append(station_magnitude) arrival = Arrival() prefix = '/'.join((res_id_prefix, 'arrival', evid, station_string)) arrival.resource_id = ResourceIdentifier(prefix=prefix) arrival.pick_id = pick.resource_id arrival.phase = pick.phase_hint arrival.azimuth = azimuth arrival.distance = distance arrival.time_residual = residual res_id = '/'.join((res_id_prefix, 'earthmodel/ak135')) arrival.earth_model_id = ResourceIdentifier(id=res_id) origin.arrivals.append(arrival) origin.quality.minimum_distance = min( d for d in (arrival.distance, origin.quality.minimum_distance) if d is not None) origin.quality.maximum_distance = \ max(arrival.distance, origin.quality.minimum_distance) origin.quality.associated_phase_count += 1 return pick, arrival
def match_filter(template_names, template_list, st, threshold, threshold_type, trig_int, plotvar, plotdir='.', cores=1, tempdir=False, debug=0, plot_format='png', output_cat=False, extract_detections=False, arg_check=True): """ Main matched-filter detection function. Over-arching code to run the correlations of given templates with a \ day of seismic data and output the detections based on a given threshold. For a functional example see the tutorials. :type template_names: list :param template_names: List of template names in the same order as \ template_list :type template_list: list :param template_list: A list of templates of which each template is a \ Stream of obspy traces containing seismic data and header information. :type st: obspy.core.stream.Stream :param st: An obspy.Stream object containing all the data available and \ required for the correlations with templates given. For efficiency \ this should contain no excess traces which are not in one or more of \ the templates. This will now remove excess traces internally, but \ will copy the stream and work on the copy, leaving your input stream \ untouched. :type threshold: float :param threshold: A threshold value set based on the threshold_type :type threshold_type: str :param threshold_type: The type of threshold to be used, can be MAD, \ absolute or av_chan_corr. MAD threshold is calculated as the \ threshold*(median(abs(cccsum))) where cccsum is the cross-correlation \ sum for a given template. absolute threhsold is a true absolute \ threshold based on the cccsum value av_chan_corr is based on the mean \ values of single-channel cross-correlations assuming all data are \ present as required for the template, \ e.g. av_chan_corr_thresh=threshold*(cccsum/len(template)) where \ template is a single template from the input and the length is the \ number of channels within this template. :type trig_int: float :param trig_int: Minimum gap between detections in seconds. :type plotvar: bool :param plotvar: Turn plotting on or off :type plotdir: str :param plotdir: Path to plotting folder, plots will be output here, \ defaults to run location. :type tempdir: str :param tempdir: Directory to put temporary files, or False :type cores: int :param cores: Number of cores to use :type debug: int :param debug: Debug output level, the bigger the number, the more the \ output. :type plot_format: str :param plot_format: Specify format of output plots if saved :type output_cat: bool :param output_cat: Specifies if matched_filter will output an \ obspy.Catalog class containing events for each detection. Default \ is False, in which case matched_filter will output a list of \ detection classes, as normal. :type extract_detections: bool :param extract_detections: Specifies whether or not to return a list of \ streams, one stream per detection. :type arg_check: bool :param arg_check: Check arguments, defaults to True, but if running in \ bulk, and you are certain of your arguments, then set to False. :return: :class: 'DETECTIONS' detections for each channel formatted as \ :class: 'obspy.UTCDateTime' objects. :return: :class: obspy.Catalog containing events for each detection. :return: list of :class: obspy.Stream objects for each detection. .. note:: Plotting within the match-filter routine uses the Agg backend \ with interactive plotting turned off. This is because the function \ is designed to work in bulk. If you wish to turn interactive \ plotting on you must import matplotlib in your script first, when you \ them import match_filter you will get the warning that this call to \ matplotlib has no effect, which will mean that match_filter has not \ changed the plotting behaviour. .. note:: The output_cat flag will create an :class: obspy.Catalog \ containing one event for each :class: 'DETECTIONS' generated by \ match_filter. Each event will contain a number of comments dealing \ with correlation values and channels used for the detection. Each \ channel used for the detection will have a corresponding :class: Pick \ which will contain time and waveform information. HOWEVER, the user \ should note that, at present, the pick times do not account for the \ prepick times inherent in each template. For example, if a template \ trace starts 0.1 seconds before the actual arrival of that phase, \ then the pick time generated by match_filter for that phase will be \ 0.1 seconds early. We are looking towards a solution which will \ involve saving templates alongside associated metadata. """ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt plt.ioff() import copy from eqcorrscan.utils import plotting from eqcorrscan.utils import findpeaks from obspy import Trace, Catalog, UTCDateTime, Stream from obspy.core.event import Event, Pick, CreationInfo, ResourceIdentifier from obspy.core.event import Comment, WaveformStreamID import time if arg_check: # Check the arguments to be nice - if arguments wrong type the parallel # output for the error won't be useful if not type(template_names) == list: raise IOError('template_names must be of type: list') if not type(template_list) == list: raise IOError('templates must be of type: list') for template in template_list: if not type(template) == Stream: msg = 'template in template_list must be of type: ' +\ 'obspy.core.stream.Stream' raise IOError(msg) if not type(st) == Stream: msg = 'st must be of type: obspy.core.stream.Stream' raise IOError(msg) if str(threshold_type) not in [str('MAD'), str('absolute'), str('av_chan_corr')]: msg = 'threshold_type must be one of: MAD, absolute, av_chan_corr' raise IOError(msg) # Copy the stream here because we will muck about with it stream = st.copy() templates = copy.deepcopy(template_list) # Debug option to confirm that the channel names match those in the # templates if debug >= 2: template_stachan = [] data_stachan = [] for template in templates: for tr in template: template_stachan.append(tr.stats.station + '.' + tr.stats.channel) for tr in stream: data_stachan.append(tr.stats.station + '.' + tr.stats.channel) template_stachan = list(set(template_stachan)) data_stachan = list(set(data_stachan)) if debug >= 3: print('I have template info for these stations:') print(template_stachan) print('I have daylong data for these stations:') print(data_stachan) # Perform a check that the daylong vectors are all the same length min_start_time = min([tr.stats.starttime for tr in stream]) max_end_time = max([tr.stats.endtime for tr in stream]) longest_trace_length = stream[0].stats.sampling_rate * (max_end_time - min_start_time) for tr in stream: if not tr.stats.npts == longest_trace_length: msg = 'Data are not equal length, padding short traces' warnings.warn(msg) start_pad = np.zeros(int(tr.stats.sampling_rate * (tr.stats.starttime - min_start_time))) end_pad = np.zeros(int(tr.stats.sampling_rate * (max_end_time - tr.stats.endtime))) tr.data = np.concatenate([start_pad, tr.data, end_pad]) # Perform check that all template lengths are internally consistent for i, temp in enumerate(template_list): if len(set([tr.stats.npts for tr in temp])) > 1: msg = 'Template %s contains traces of differing length!! THIS \ WILL CAUSE ISSUES' % template_names[i] raise ValueError(msg) # Call the _template_loop function to do all the correlation work outtic = time.clock() # Edit here from previous, stable, but slow match_filter # Would be worth testing without an if statement, but with every station in # the possible template stations having data, but for those without real # data make the data NaN to return NaN ccc_sum # Note: this works if debug >= 2: print('Ensuring all template channels have matches in long data') template_stachan = [] for template in templates: for tr in template: template_stachan += [(tr.stats.station, tr.stats.channel)] template_stachan = list(set(template_stachan)) # Copy this here to keep it safe for stachan in template_stachan: if not stream.select(station=stachan[0], channel=stachan[1]): # Remove template traces rather than adding NaN data for template in templates: if template.select(station=stachan[0], channel=stachan[1]): for tr in template.select(station=stachan[0], channel=stachan[1]): template.remove(tr) # Remove un-needed channels for tr in stream: if not (tr.stats.station, tr.stats.channel) in template_stachan: stream.remove(tr) # Also pad out templates to have all channels for template, template_name in zip(templates, template_names): if len(template) == 0: msg = ('No channels matching in continuous data for ' + 'template' + template_name) warnings.warn(msg) templates.remove(template) template_names.remove(template_name) continue for stachan in template_stachan: if not template.select(station=stachan[0], channel=stachan[1]): nulltrace = Trace() nulltrace.stats.station = stachan[0] nulltrace.stats.channel = stachan[1] nulltrace.stats.sampling_rate = template[0].stats.sampling_rate nulltrace.stats.starttime = template[0].stats.starttime nulltrace.data = np.array([np.NaN] * len(template[0].data), dtype=np.float32) template += nulltrace if debug >= 2: print('Starting the correlation run for this day') [cccsums, no_chans, chans] = _channel_loop(templates=templates, stream=stream, cores=cores, debug=debug) if len(cccsums[0]) == 0: raise ValueError('Correlation has not run, zero length cccsum') outtoc = time.clock() print(' '.join(['Looping over templates and streams took:', str(outtoc - outtic), 's'])) if debug >= 2: print(' '.join(['The shape of the returned cccsums is:', str(np.shape(cccsums))])) print(' '.join(['This is from', str(len(templates)), 'templates'])) print(' '.join(['Correlated with', str(len(stream)), 'channels of data'])) detections = [] if output_cat: det_cat = Catalog() for i, cccsum in enumerate(cccsums): template = templates[i] if str(threshold_type) == str('MAD'): rawthresh = threshold * np.median(np.abs(cccsum)) elif str(threshold_type) == str('absolute'): rawthresh = threshold elif str(threshold_type) == str('av_chan_corr'): rawthresh = threshold * no_chans[i] # Findpeaks returns a list of tuples in the form [(cccsum, sample)] print(' '.join(['Threshold is set at:', str(rawthresh)])) print(' '.join(['Max of data is:', str(max(cccsum))])) print(' '.join(['Mean of data is:', str(np.mean(cccsum))])) if np.abs(np.mean(cccsum)) > 0.05: warnings.warn('Mean is not zero! Check this!') # Set up a trace object for the cccsum as this is easier to plot and # maintains timing if plotvar: stream_plot = copy.deepcopy(stream[0]) # Downsample for plotting stream_plot.decimate(int(stream[0].stats.sampling_rate / 10)) cccsum_plot = Trace(cccsum) cccsum_plot.stats.sampling_rate = stream[0].stats.sampling_rate # Resample here to maintain shape better cccsum_hist = cccsum_plot.copy() cccsum_hist = cccsum_hist.decimate(int(stream[0].stats. sampling_rate / 10)).data cccsum_plot = plotting.chunk_data(cccsum_plot, 10, 'Maxabs').data # Enforce same length stream_plot.data = stream_plot.data[0:len(cccsum_plot)] cccsum_plot = cccsum_plot[0:len(stream_plot.data)] cccsum_hist = cccsum_hist[0:len(stream_plot.data)] plotting.triple_plot(cccsum_plot, cccsum_hist, stream_plot, rawthresh, True, plotdir + '/cccsum_plot_' + template_names[i] + '_' + stream[0].stats.starttime. datetime.strftime('%Y-%m-%d') + '.' + plot_format) if debug >= 4: print(' '.join(['Saved the cccsum to:', template_names[i], stream[0].stats.starttime.datetime. strftime('%Y%j')])) np.save(template_names[i] + stream[0].stats.starttime.datetime.strftime('%Y%j'), cccsum) tic = time.clock() if debug >= 4: np.save('cccsum_' + str(i) + '.npy', cccsum) if debug >= 3 and max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats. sampling_rate, debug, stream[0].stats.starttime, stream[0].stats.sampling_rate) elif max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short(cccsum, rawthresh, trig_int * stream[0].stats. sampling_rate, debug) else: print('No peaks found above threshold') peaks = False toc = time.clock() if debug >= 1: print(' '.join(['Finding peaks took:', str(toc - tic), 's'])) if peaks: for peak in peaks: detecttime = stream[0].stats.starttime +\ peak[1] / stream[0].stats.sampling_rate # Detect time must be valid QuakeML uri within resource_id. # This will write a formatted string which is still # readable by UTCDateTime rid = ResourceIdentifier(id=template_names[i] + '_' + str(detecttime. strftime('%Y%m%dT%H%M%S.%f')), prefix='smi:local') ev = Event(resource_id=rid) cr_i = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) ev.creation_info = cr_i # All detection info in Comments for lack of a better idea thresh_str = 'threshold=' + str(rawthresh) ccc_str = 'detect_val=' + str(peak[0]) used_chans = 'channels used: ' +\ ' '.join([str(pair) for pair in chans[i]]) ev.comments.append(Comment(text=thresh_str)) ev.comments.append(Comment(text=ccc_str)) ev.comments.append(Comment(text=used_chans)) min_template_tm = min([tr.stats.starttime for tr in template]) for tr in template: if (tr.stats.station, tr.stats.channel) not in chans[i]: continue else: pick_tm = detecttime + (tr.stats.starttime - min_template_tm) wv_id = WaveformStreamID(network_code=tr.stats.network, station_code=tr.stats.station, channel_code=tr.stats.channel) ev.picks.append(Pick(time=pick_tm, waveform_id=wv_id)) detections.append(DETECTION(template_names[i], detecttime, no_chans[i], peak[0], rawthresh, 'corr', chans[i], event=ev)) if output_cat: det_cat.append(ev) if extract_detections: detection_streams = extract_from_stream(stream, detections) del stream, templates if output_cat and not extract_detections: return detections, det_cat elif not extract_detections: return detections elif extract_detections and not output_cat: return detections, detection_streams else: return detections, det_cat, detection_streams
def _parse_arrivals(self, event, origin, origin_res_id): # Skip header of arrivals next(self.lines) # Stop the loop after 2 empty lines (according to the standard). previous_line_empty = False for line in self.lines: line_empty = not line or line.isspace() if not self.event_point_separator: # Event are separated by two empty lines if line_empty and previous_line_empty: break else: # Event are separated by '.' if line.startswith('.'): break previous_line_empty = line_empty if line_empty: # Skip empty lines when the loop should be stopped by # point continue magnitude_types = [] magnitude_values = [] fields = self.fields['arrival'] station = line[fields['sta']].strip() distance = line[fields['dist']].strip() event_azimuth = line[fields['ev_az']].strip() evaluation_mode = line[fields['picktype']].strip() direction = line[fields['direction']].strip() onset = line[fields['detchar']].strip() phase = line[fields['phase']].strip() time = line[fields['time']].strip().replace('/', '-') time_residual = line[fields['t_res']].strip() arrival_azimuth = line[fields['azim']].strip() azimuth_residual = line[fields['az_res']].strip() slowness = line[fields['slow']].strip() slowness_residual = line[fields['s_res']].strip() time_defining_flag = line[fields['t_def']].strip() azimuth_defining_flag = line[fields['a_def']].strip() slowness_defining_flag = line[fields['s_def']].strip() snr = line[fields['snr']].strip() amplitude_value = line[fields['amp']].strip() period = line[fields['per']].strip() magnitude_types.append(line[fields['mag_type_1']].strip()) magnitude_values.append(line[fields['mag_1']].strip()) magnitude_types.append(line[fields['mag_type_2']].strip()) magnitude_values.append(line[fields['mag_2']].strip()) line_id = line[fields['id']].strip() # Don't take pick and arrival with wrong time residual if '*' in time_residual: continue try: pick = Pick() pick.creation_info = self._get_creation_info() pick.waveform_id = WaveformStreamID() pick.waveform_id.station_code = station pick.time = UTCDateTime(time) network_code = self.default_network_code location_code = self.default_location_code channel_code = self.default_channel_code try: network_code, channel = self._get_channel(station, pick.time) if channel: channel_code = channel.code location_code = channel.location_code except TypeError: pass pick.waveform_id.network_code = network_code pick.waveform_id.channel_code = channel_code if location_code: pick.waveform_id.location_code = location_code try: ev_mode = EVALUATION_MODES[evaluation_mode] pick.evaluation_mode = ev_mode except KeyError: pass try: pick.polarity = PICK_POLARITIES[direction] except KeyError: pass try: pick.onset = PICK_ONSETS[onset] except KeyError: pass pick.phase_hint = phase try: pick.backazimuth = float(arrival_azimuth) except ValueError: pass try: pick.horizontal_slowness = float(slowness) except ValueError: pass public_id = "pick/%s" % line_id pick.resource_id = self._get_res_id(public_id) event.picks.append(pick) except (TypeError, ValueError, AttributeError): # Can't parse pick, skip arrival and amplitude parsing continue arrival = Arrival() arrival.creation_info = self._get_creation_info() try: arrival.pick_id = pick.resource_id.id except AttributeError: pass arrival.phase = phase try: arrival.azimuth = float(event_azimuth) except ValueError: pass try: arrival.distance = float(distance) except ValueError: pass try: arrival.time_residual = float(time_residual) except ValueError: pass try: arrival.backazimuth_residual = float(azimuth_residual) except ValueError: pass try: arrival.horizontal_slowness_residual = float(slowness_residual) except ValueError: pass if time_defining_flag == 'T': arrival.time_weight = 1 if azimuth_defining_flag == 'A': arrival.backazimuth_weight = 1 if slowness_defining_flag == 'S': arrival.horizontal_slowness_weight = 1 public_id = "arrival/%s" % line_id arrival.resource_id = self._get_res_id(public_id, parent_res_id=origin_res_id) origin.arrivals.append(arrival) try: amplitude = Amplitude() amplitude.creation_info = self._get_creation_info() amplitude.generic_amplitude = float(amplitude_value) try: amplitude.pick_id = pick.resource_id amplitude.waveform_id = pick.waveform_id except AttributeError: pass try: amplitude.period = float(period) except ValueError: pass try: amplitude.snr = float(snr) except ValueError: pass for i in [0, 1]: if magnitude_types[i] and not magnitude_types[i].isspace(): amplitude.magnitude_hint = magnitude_types[i] public_id = "amplitude/%s" % line_id amplitude.resource_id = self._get_res_id(public_id) event.amplitudes.append(amplitude) for i in [0, 1]: sta_mag = StationMagnitude() sta_mag.creation_info = self._get_creation_info() sta_mag.origin_id = origin_res_id sta_mag.amplitude_id = amplitude.resource_id sta_mag.station_magnitude_type = magnitude_types[i] sta_mag.mag = magnitude_values[i] public_id = "magnitude/station/%s/%s" % (line_id, i) sta_mag.resource_id = self._get_res_id(public_id) event.station_magnitudes.append(sta_mag) except ValueError: pass
def cross_net(stream, env=False, debug=0, master=False): """ Generate picks using a simple envelope cross-correlation. Picks are made for each channel based on optimal moveout defined by maximum cross-correlation with master trace. Master trace will be the first trace in the stream if not set. Requires good inter-station coherance. :type stream: obspy.core.stream.Stream :param stream: Stream to pick :type env: bool :param env: To compute cross-correlations on the envelope or not. :type debug: int :param debug: Debug level from 0-5 :type master: obspy.core.trace.Trace :param master: Trace to use as master, if False, will use the first trace in stream. :returns: :class:`obspy.core.event.event.Event` .. rubric:: Example >>> from obspy import read >>> from eqcorrscan.utils.picker import cross_net >>> st = read() >>> event = cross_net(st, env=True) >>> print(event.creation_info.author) EQcorrscan .. warning:: This routine is not designed for accurate picking, rather it can be used for a first-pass at picks to obtain simple locations. Based on the waveform-envelope cross-correlation method. """ event = Event() event.origins.append(Origin()) event.creation_info = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) event.comments.append(Comment(text='cross_net')) samp_rate = stream[0].stats.sampling_rate if not env: if debug > 2: print('Using the raw data') st = stream.copy() st.resample(samp_rate) else: st = stream.copy() if debug > 2: print('Computing envelope') for tr in st: tr.resample(samp_rate) tr.data = envelope(tr.data) if not master: master = st[0] else: master = master master.data = np.nan_to_num(master.data) for i, tr in enumerate(st): tr.data = np.nan_to_num(tr.data) if debug > 2: msg = ' '.join(['Comparing', tr.stats.station, tr.stats.channel, 'with the master']) print(msg) shift_len = int(0.3 * len(tr)) if debug > 2: print('Shift length is set to ' + str(shift_len) + ' samples') index, cc = xcorr(master, tr, shift_len) wav_id = WaveformStreamID(station_code=tr.stats.station, channel_code=tr.stats.channel, network_code=tr.stats.network) event.picks.append(Pick(time=tr.stats.starttime + (index / tr.stats.sampling_rate), waveform_id=wav_id, phase_hint='S', onset='emergent')) if debug > 2: print(event.picks[i]) event.origins[0].time = min([pick.time for pick in event.picks]) - 1 # event.origins[0].latitude = float('nan') # event.origins[0].longitude = float('nan') # Set arbitrary origin time del st return event
def test_real_time_plotting(self): """Test the real-time plotter - must be run interactively.""" seed_list = [ "NZ.INZ.10.HHZ", "NZ.JCZ.10.HHZ", "NZ.FOZ.11.HHZ", "NZ.MSZ.10.HHZ", "NZ.PYZ.10.HHZ", "NZ.DCZ.10.HHZ", "NZ.WVZ.10.HHZ" ] client = Client("GEONET") inv = client.get_stations(network=seed_list[0].split(".")[0], station=seed_list[0].split(".")[1], location=seed_list[0].split(".")[2], channel=seed_list[0].split(".")[3]) for seed_id in seed_list[1:]: net, sta, loc, chan = seed_id.split('.') inv += client.get_stations(network=net, station=sta, channel=chan, location=loc) now = UTCDateTime.now() template_cat = client.get_events(starttime=now - 3600, endtime=now) tribe = Tribe(templates=[ Template(event=event, name=event.resource_id.id.split("/")[-1]) for event in template_cat ]) template_names = cycle([t.name for t in tribe]) buffer_capacity = 1200 rt_client = RealTimeClient(server_url="link.geonet.org.nz", buffer_capacity=buffer_capacity) for seed_id in seed_list: net, station, _, selector = seed_id.split(".") rt_client.select_stream(net=net, station=station, selector=selector) rt_client.background_run() while len(rt_client.buffer) < 7: # Wait until we have some data time.sleep(SLEEP_STEP) detections = [] plotter = EQcorrscanPlot(rt_client=rt_client, plot_length=60, tribe=tribe, inventory=inv, update_interval=1000, detections=detections) plotter.background_run() duration = 0 step = 5 while duration < MAX_DURATION: detections.append( Detection( template_name=next(template_names), detect_time=UTCDateTime.now(), no_chans=999, detect_val=999, threshold=999, threshold_type="MAD", threshold_input=999, typeofdet="unreal", event=Event(picks=[ Pick(time=UTCDateTime.now(), waveform_id=WaveformStreamID(seed_string=seed_id)) for seed_id in seed_list ]))) time.sleep(step) duration += step rt_client.background_stop()
def _detect(detector, st, threshold, trig_int, moveout=0, min_trig=0, process=True, extract_detections=False, debug=0): """ Detect within continuous data using the subspace method. Not to be called directly, use the detector.detect method. :type detector: eqcorrscan.core.subspace.Detector :param detector: Detector to use. :type st: obspy.core.stream.Stream :param st: Un-processed stream to detect within using the subspace \ detector :type threshold: float :param threshold: Threshold value for detections between 0-1 :type trig_int: float :param trig_int: Minimum trigger interval in seconds. :type moveout: float :param moveout: Maximum allowable moveout window for non-multiplexed, network detection. See note. :type min_trig: int :param min_trig: Minimum number of stations exceeding threshold for \ non-multiplexed, network detection. See note. :type process: bool :param process: Whether or not to process the stream according to the \ parameters defined by the detector. Default is to process the \ data (True). :type extract_detections: bool :param extract_detections: Whether to extract waveforms for each \ detection or not, if true will return detections and streams. :type debug: int :param debug: Debug output level from 0-5. :return: list of detections :rtype: list of eqcorrscan.core.match_filter.DETECTION """ from eqcorrscan.core import subspace_statistic detections = [] # First process the stream if process: if debug > 0: print('Processing Stream') stream, stachans = _subspace_process( streams=[st.copy()], lowcut=detector.lowcut, highcut=detector.highcut, filt_order=detector.filt_order, sampling_rate=detector.sampling_rate, multiplex=detector.multiplex, stachans=detector.stachans, parallel=True, align=False, shift_len=None, reject=False) else: # Check the sampling rate at the very least for tr in st: if not tr.stats.sampling_rate == detector.sampling_rate: raise ValueError('Sampling rates do not match.') stream = [st] stachans = detector.stachans outtic = time.clock() if debug > 0: print('Computing detection statistics') stats = np.zeros( (len(stream[0]), len(stream[0][0]) - len(detector.data[0][0]) + 1), dtype=np.float32) for det_channel, in_channel, i in zip(detector.data, stream[0], np.arange(len(stream[0]))): stats[i] = subspace_statistic.\ det_statistic(detector=det_channel.astype(np.float32), data=in_channel.data.astype(np.float32)) if debug > 0: print(stats[i].shape) if debug > 3: plt.plot(stats[i]) plt.show() # Hard typing in Cython loop requires float32 type. # statistics if detector.multiplex: trig_int_samples = (len(detector.stachans) * detector.sampling_rate * trig_int) else: trig_int_samples = detector.sampling_rate * trig_int if debug > 0: print('Finding peaks') peaks = [] for i in range(len(stream[0])): peaks.append( findpeaks.find_peaks2_short(arr=stats[i], thresh=threshold, trig_int=trig_int_samples, debug=debug)) if not detector.multiplex: # Conduct network coincidence triggering peaks = findpeaks.coin_trig(peaks=peaks, samp_rate=detector.sampling_rate, moveout=moveout, min_trig=min_trig, stachans=stachans, trig_int=trig_int) else: peaks = peaks[0] if len(peaks) > 0: for peak in peaks: if detector.multiplex: detecttime = st[0].stats.starttime + ( peak[1] / (detector.sampling_rate * len(detector.stachans))) else: detecttime = st[0].stats.starttime + (peak[1] / detector.sampling_rate) rid = ResourceIdentifier(id=detector.name + '_' + str(detecttime), prefix='smi:local') ev = Event(resource_id=rid) cr_i = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) ev.creation_info = cr_i # All detection info in Comments for lack of a better idea thresh_str = 'threshold=' + str(threshold) ccc_str = 'detect_val=' + str(peak[0]) used_chans = 'channels used: ' +\ ' '.join([str(pair) for pair in detector.stachans]) ev.comments.append(Comment(text=thresh_str)) ev.comments.append(Comment(text=ccc_str)) ev.comments.append(Comment(text=used_chans)) for stachan in detector.stachans: tr = st.select(station=stachan[0], channel=stachan[1]) if tr: net_code = tr[0].stats.network else: net_code = '' pick_tm = detecttime wv_id = WaveformStreamID(network_code=net_code, station_code=stachan[0], channel_code=stachan[1]) ev.picks.append(Pick(time=pick_tm, waveform_id=wv_id)) detections.append( DETECTION(detector.name, detecttime, len(detector.stachans), peak[0], threshold, 'subspace', detector.stachans, event=ev)) outtoc = time.clock() print('Detection took %s seconds' % str(outtoc - outtic)) if extract_detections: detection_streams = extract_from_stream(st, detections) return detections, detection_streams return detections
def getobs(mseed_filename, client, event, phases, frq4, windows, stas, stalocs, picks=None, delta_T={ 'P': 1., 'SH': 1., 'R': 10., 'L': 10. }, taper=None, adjtime=None): # Connect to arclink server #st = read('../mseed/mini.seed') org = event.preferred_origin() if org is None: org = event.origins[0] st = read(mseed_filename) stobs = {'params': {'filter': frq4, 'windows': windows}} syn = {} torg = org.time trngmx = 3600. invout = None # First do any requried time adjustments if not adjtime is None: for tr in st: if not tr.stats.station in adjtime.keys(): continue print 'Adjusting time for station %s by %g secs' % \ (tr.stats.station,adjtime[tr.stats.station]) tr.stats.starttime -= adjtime[tr.stats.station] for phase in phases: if not phase in stas.keys(): continue stobs[phase] = Stream() for sta in stas[phase]: # If this is a body wave phase find the pick - skip if none found if phase == 'P' or phase == 'SH': sta_pick = None # If no picks supplied then get them from events if picks is None: for pick in event.picks: if pick.phase_hint == phase[0:1] and \ pick.waveform_id.station_code == sta: sta_pick = pick break else: # Get them from picks - e.g. returned by get_isctimes if sta in picks.keys() and phase[0:1] in picks[sta]: sta_pick = Pick() sta_pick.time = picks[sta][phase[0:1]] if sta_pick is None: print 'No %s pick found for station %s - skipping' % ( phase, sta) continue # Set location code if prescribed, otherwise use '00' (preferred) if sta in stalocs.keys(): loc = stalocs[sta] else: loc = '00' # Select the channels for this station - skip if none found chans = st.select(station=sta, location=loc) if len(chans) == 0: # if nothing for loc='00', try also with '' loc = '' chans = st.select(station=sta, location=loc) if len(chans) == 0: print 'No channels found for %s' % sta continue try: inv = client.get_stations(network=chans[0].stats.network, station=sta, location=loc, starttime=torg, endtime=torg + 100., level='response') except Exception as e: warnings.warn(str(e)) print 'FDSNWS request failed for trace id %s - skipping' % sta continue try: coordinates = inv[0].get_coordinates(chans[0].id) except: print 'No coordinates found for station %s, channel %s' % \ (sta,chans[0].id) continue dist, azm, bazm = gps2dist_azimuth(org['latitude'], org['longitude'], coordinates['latitude'], coordinates['longitude']) gcarc = locations2degrees(org['latitude'], org['longitude'], coordinates['latitude'], coordinates['longitude']) if phase == 'R' or phase == 'P': # Rayleigh or P wave try: tr = st.select(station=sta, component='Z', location=loc)[0] except IndexError: print 'No vertical for %s:%s' % (sta, loc) continue try: inv = client.get_stations(network=tr.stats.network, station=sta, channel=tr.stats.channel, location=loc, starttime=torg, endtime=torg + 100., level='response') except Exception as e: warnings.warn(str(e)) print 'FDSNWS request failed for trace id %s - skipping' % tr.id continue tr = tr.copy() tr.stats.response = inv[0].get_response(tr.id, torg) tr.stats.coordinates = inv[0].get_coordinates(tr.id) tr.remove_response(pre_filt=frq4[phase], output='DISP') tr.stats.gcarc = gcarc tr.stats.azimuth = azm #t1 = minv[0].get_responeax(tr.stats.starttime,t+dist/rvmax) #t2 = min(tr.stats.endtime ,t+dist/rvmin) t1 = max(torg, tr.stats.starttime) t2 = min(torg + trngmx, tr.stats.endtime) tr.trim(starttime=t1, endtime=t2) decim = int(0.01 + delta_T[phase] / tr.stats.delta) ch = inv.select(station=sta, channel=tr.stats.channel)[0][0][0] print tr.id,' ',tr.stats.sampling_rate,' decimated by ',decim,\ 'sensitivity=',ch.response.instrument_sensitivity.value if tr.stats.starttime - torg < 0.: tr.trim(starttime=torg) tr.decimate(factor=decim, no_filter=True) tr.data *= 1.e6 # Convert to microns elif phase == 'L' or phase == 'SH': # Love or SH wave if len(chans.select(component='E')) != 0: try: tr1a = st.select(station=sta, component='E', location=loc)[0] tr2a = st.select(station=sta, component='N', location=loc)[0] except: print 'Station %s does not have 2 horizontal componets -skipping' % sta continue elif len(chans.select(component='1')) != 0: try: tr1a = st.select(station=sta, component='1', location=loc)[0] tr2a = st.select(station=sta, component='2', location=loc)[0] except: print 'Station %s does not have 2 horizontal componets -skipping' % sta continue tr1 = tr1a.copy() tr1.data = tr1a.data.copy() tr2 = tr2a.copy() tr2.data = tr2a.data.copy() ch1 = inv.select(station=sta, channel=tr1.stats.channel)[0][0][0] ch2 = inv.select(station=sta, channel=tr2.stats.channel)[0][0][0] tr1.stats.response = ch1.response tr1.remove_response(pre_filt=frq4[phase], output='DISP') tr2.stats.response = ch2.response tr2.remove_response(pre_filt=frq4[phase], output='DISP') strt = max(tr1.stats.starttime, tr2.stats.starttime) endt = min(tr1.stats.endtime, tr2.stats.endtime) tr1.trim(starttime=strt, endtime=endt) tr2.trim(starttime=strt, endtime=endt) # Rotate components first to ZNE vert, north, east = rotate2zne(tr1.data, ch1.azimuth, 0., tr2.data, ch2.azimuth, 0., np.zeros(tr1.stats.npts), 0., 0.) radial, transverse = rotate_ne_rt(north, east, bazm) tr = Trace(header=tr1.stats, data=transverse) tr2 = Trace(header=tr2.stats, data=radial) tr.stats.channel = tr.stats.channel[:-1] + 'T' # Change one of the invout channels to end in 'T' net = inv[-1] stn = net[0] chn = stn[0] chn.code = chn.code[:-1] + 'T' # tr.stats.gcarc = gcarc tr.stats.azimuth = azm decim = int(0.01 + delta_T[phase] / tr.stats.delta) print tr.id, ' ', tr.stats.sampling_rate, ' decimated by ', decim print '%s: sensitivity=%g, azimuth=%g, dip=%g' % ( ch1.code, ch1.response.instrument_sensitivity.value, ch1.azimuth, ch1.dip) print '%s: sensitivity=%g, azimuth=%g, dip=%g' % ( ch2.code, ch2.response.instrument_sensitivity.value, ch2.azimuth, ch2.dip) if tr.stats.starttime - torg < 0.: tr.trim(starttime=torg) tr2.trim(starttime=torg) tr.decimate(factor=decim, no_filter=True) tr2.decimate(factor=decim, no_filter=True) tr.radial = 1.e6 * tr2.data tr.stats.coordinates = coordinates tr.data *= 1.e6 # Convert to microns if phase == 'R' or phase == 'L': # Window according to group velocity window gwin = windows[phase] tbeg, tend = (dist * .001 / gwin[1], dist * .001 / gwin[0]) tr.trim(torg + tbeg, torg + tend) elif phase == 'P' or phase == 'SH': # Window by times before and after pick tbef, taft = windows[phase] tr.trim(sta_pick.time - tbef, sta_pick.time + taft) idx = int(0.5 + tbef / tr.stats.delta) avg = tr.data[:idx].mean() tr.data -= avg if not taper is None: itp = int(taper * tr.stats.npts) for i in range(tr.stats.npts - itp, tr.stats.npts): tr.data[i] *= 0.5 * (1. + mt.cos( mt.pi * (i - (tr.stats.npts - itp)) / float(itp))) tr.stats.pick = sta_pick stobs[phase].append(tr) # Appen station inventory to invout if invout is None: invout = inv else: invout += inv # Pickle to file return stobs, invout
def _read_single_hypocenter(lines, coordinate_converter, original_picks): """ Given a list of lines (starting with a 'NLLOC' line and ending with a 'END_NLLOC' line), parse them into an Event. """ try: # some paranoid checks.. assert lines[0].startswith("NLLOC ") assert lines[-1].startswith("END_NLLOC") for line in lines[1:-1]: assert not line.startswith("NLLOC ") assert not line.startswith("END_NLLOC") except Exception: msg = ("This should not have happened, please report this as a bug at " "https://github.com/obspy/obspy/issues.") raise Exception(msg) indices_phases = [None, None] for i, line in enumerate(lines): if line.startswith("PHASE "): indices_phases[0] = i elif line.startswith("END_PHASE"): indices_phases[1] = i # extract PHASES lines (if any) if any(indices_phases): if not all(indices_phases): msg = ("NLLOC HYP file seems corrupt, 'PHASE' block is corrupt.") raise RuntimeError(msg) i1, i2 = indices_phases lines, phases_lines = lines[:i1] + lines[i2 + 1:], lines[i1 + 1:i2] else: phases_lines = [] lines = dict([line.split(None, 1) for line in lines[:-1]]) line = lines["SIGNATURE"] line = line.rstrip().split('"')[1] signature, version, date, time = line.rsplit(" ", 3) # new NLLoc > 6.0 seems to add prefix 'run:' before date if date.startswith('run:'): date = date[4:] signature = signature.strip() creation_time = UTCDateTime.strptime(date + time, str("%d%b%Y%Hh%Mm%S")) if coordinate_converter: # maximum likelihood origin location in km info line line = lines["HYPOCENTER"] x, y, z = coordinate_converter(*map(float, line.split()[1:7:2])) else: # maximum likelihood origin location lon lat info line line = lines["GEOGRAPHIC"] y, x, z = map(float, line.split()[8:13:2]) # maximum likelihood origin time info line line = lines["GEOGRAPHIC"] year, mon, day, hour, min = map(int, line.split()[1:6]) seconds = float(line.split()[6]) time = UTCDateTime(year, mon, day, hour, min, seconds, strict=False) # distribution statistics line line = lines["STATISTICS"] covariance_xx = float(line.split()[7]) covariance_yy = float(line.split()[13]) covariance_zz = float(line.split()[17]) stats_info_string = str( "Note: Depth/Latitude/Longitude errors are calculated from covariance " "matrix as 1D marginal (Lon/Lat errors as great circle degrees) " "while OriginUncertainty min/max horizontal errors are calculated " "from 2D error ellipsoid and are therefore seemingly higher compared " "to 1D errors. Error estimates can be reconstructed from the " "following original NonLinLoc error statistics line:\nSTATISTICS " + lines["STATISTICS"]) # goto location quality info line line = lines["QML_OriginQuality"].split() (assoc_phase_count, used_phase_count, assoc_station_count, used_station_count, depth_phase_count) = map(int, line[1:11:2]) stderr, az_gap, sec_az_gap = map(float, line[11:17:2]) gt_level = line[17] min_dist, max_dist, med_dist = map(float, line[19:25:2]) # goto location quality info line line = lines["QML_OriginUncertainty"] if "COMMENT" in lines: comment = lines["COMMENT"].strip() comment = comment.strip('\'"') comment = comment.strip() hor_unc, min_hor_unc, max_hor_unc, hor_unc_azim = \ map(float, line.split()[1:9:2]) # assign origin info event = Event() o = Origin() event.origins = [o] event.preferred_origin_id = o.resource_id o.origin_uncertainty = OriginUncertainty() o.quality = OriginQuality() ou = o.origin_uncertainty oq = o.quality o.comments.append(Comment(text=stats_info_string, force_resource_id=False)) event.comments.append(Comment(text=comment, force_resource_id=False)) # SIGNATURE field's first item is LOCSIG, which is supposed to be # 'Identification of an individual, institiution or other entity' # according to # http://alomax.free.fr/nlloc/soft6.00/control.html#_NLLoc_locsig_ # so use it as author in creation info event.creation_info = CreationInfo(creation_time=creation_time, version=version, author=signature) o.creation_info = CreationInfo(creation_time=creation_time, version=version, author=signature) # negative values can appear on diagonal of covariance matrix due to a # precision problem in NLLoc implementation when location coordinates are # large compared to the covariances. o.longitude = x try: o.longitude_errors.uncertainty = kilometer2degrees(sqrt(covariance_xx)) except ValueError: if covariance_xx < 0: msg = ("Negative value in XX value of covariance matrix, not " "setting longitude error (epicentral uncertainties will " "still be set in origin uncertainty).") warnings.warn(msg) else: raise o.latitude = y try: o.latitude_errors.uncertainty = kilometer2degrees(sqrt(covariance_yy)) except ValueError: if covariance_yy < 0: msg = ("Negative value in YY value of covariance matrix, not " "setting longitude error (epicentral uncertainties will " "still be set in origin uncertainty).") warnings.warn(msg) else: raise o.depth = z * 1e3 # meters! o.depth_errors.uncertainty = sqrt(covariance_zz) * 1e3 # meters! o.depth_errors.confidence_level = 68 o.depth_type = str("from location") o.time = time ou.horizontal_uncertainty = hor_unc ou.min_horizontal_uncertainty = min_hor_unc ou.max_horizontal_uncertainty = max_hor_unc # values of -1 seem to be used for unset values, set to None for field in ("horizontal_uncertainty", "min_horizontal_uncertainty", "max_horizontal_uncertainty"): if ou.get(field, -1) == -1: ou[field] = None else: ou[field] *= 1e3 # meters! ou.azimuth_max_horizontal_uncertainty = hor_unc_azim ou.preferred_description = str("uncertainty ellipse") ou.confidence_level = 68 # NonLinLoc in general uses 1-sigma (68%) level oq.standard_error = stderr oq.azimuthal_gap = az_gap oq.secondary_azimuthal_gap = sec_az_gap oq.used_phase_count = used_phase_count oq.used_station_count = used_station_count oq.associated_phase_count = assoc_phase_count oq.associated_station_count = assoc_station_count oq.depth_phase_count = depth_phase_count oq.ground_truth_level = gt_level oq.minimum_distance = kilometer2degrees(min_dist) oq.maximum_distance = kilometer2degrees(max_dist) oq.median_distance = kilometer2degrees(med_dist) # go through all phase info lines for line in phases_lines: line = line.split() arrival = Arrival() o.arrivals.append(arrival) station = str(line[0]) phase = str(line[4]) arrival.phase = phase arrival.distance = kilometer2degrees(float(line[21])) arrival.azimuth = float(line[23]) arrival.takeoff_angle = float(line[24]) arrival.time_residual = float(line[16]) arrival.time_weight = float(line[17]) pick = Pick() # network codes are not used by NonLinLoc, so they can not be known # when reading the .hyp file.. to conform with QuakeML standard set an # empty network code wid = WaveformStreamID(network_code="", station_code=station) # have to split this into ints for overflow to work correctly date, hourmin, sec = map(str, line[6:9]) ymd = [int(date[:4]), int(date[4:6]), int(date[6:8])] hm = [int(hourmin[:2]), int(hourmin[2:4])] t = UTCDateTime(*(ymd + hm), strict=False) + float(sec) pick.waveform_id = wid pick.time = t pick.time_errors.uncertainty = float(line[10]) pick.phase_hint = phase pick.onset = ONSETS.get(line[3].lower(), None) pick.polarity = POLARITIES.get(line[5].lower(), None) # try to determine original pick for each arrival for pick_ in original_picks: wid = pick_.waveform_id if station == wid.station_code and phase == pick_.phase_hint: pick = pick_ break else: # warn if original picks were specified and we could not associate # the arrival correctly if original_picks: msg = ("Could not determine corresponding original pick for " "arrival. " "Falling back to pick information in NonLinLoc " "hypocenter-phase file.") warnings.warn(msg) event.picks.append(pick) arrival.pick_id = pick.resource_id event.scope_resource_ids() return event
def _read_evt(filename, encoding='utf-8', **kwargs): """ Read a SeismicHandler EVT file and returns an ObsPy Catalog object. .. warning:: This function should NOT be called directly, it registers via the ObsPy :func:`~obspy.core.event.read_events` function, call this instead. :param str encoding: encoding used (default: utf-8) :rtype: :class:`~obspy.core.event.Catalog` :return: An ObsPy Catalog object. .. note:: The following fields are supported by this function: %s. Compare with http://www.seismic-handler.org/wiki/ShmDocFileEvt """ with io.open(filename, 'r', encoding=encoding) as f: temp = f.read() # first create phases and phases_o dictionaries for different phases # and phases with origin information phases = defaultdict(list) phases_o = {} phase = {} evid = None for line in temp.splitlines(): if 'End of Phase' in line: if 'origin time' in phase.keys(): if evid in phases_o: # found more than one origin pass phases_o[evid] = phase phases[evid].append(phase) phase = {} evid = None elif line.strip() != '': try: key, value = line.split(':', 1) except ValueError: continue key = key.strip().lower() value = value.strip() if key == 'event id': evid = value elif value != '': phase[key] = value assert evid is None # now create obspy Events from phases and phases_o dictionaries events = [] for evid in phases: picks = [] arrivals = [] stamags = [] origins = [] po = None magnitudes = [] pm = None for p in phases[evid]: sta = p.get('station code', '') comp = p.get('component', '') pick_kwargs = _kw(p, 'pick') widargs = _resolve_seedid(sta, comp, time=pick_kwargs['time'], **kwargs) wid = WaveformStreamID(*widargs) pick = Pick(waveform_id=wid, **pick_kwargs) arrival = Arrival(pick_id=pick.resource_id, **_kw(p, 'arrival')) picks.append(pick) arrivals.append(arrival) stamags_temp, _ = _mags(p, evid, stamag=True, wid=wid) stamags.extend(stamags_temp) if evid in phases_o: o = phases_o[evid] uncertainty = OriginUncertainty(**_kw(o, 'origin_uncertainty')) origin = Origin(arrivals=arrivals, origin_uncertainty=uncertainty, **_kw(o, 'origin')) if origin.latitude is None or origin.longitude is None: warn('latitude or longitude not set for event %s' % evid) else: if origin.longitude_errors.uncertainty is not None: origin.longitude_errors.uncertainty *= cos( origin.latitude / 180 * pi) origins = [origin] po = origin.resource_id magnitudes, pm = _mags(o, evid) else: o = p event = Event(resource_id=ResourceIdentifier(evid), picks=picks, origins=origins, magnitudes=magnitudes, station_magnitudes=stamags, preferred_origin_id=po, preferred_magnitude_id=pm, **_kw(o, 'event')) events.append(event) return Catalog(events, description='Created from SeismicHandler EVT format')
def test_write_with_extra_tags_and_read(self): """ Tests that a QuakeML file with additional custom "extra" tags gets written correctly and that when reading it again the extra tags are parsed correctly. """ filename = os.path.join(self.path, "quakeml_1.2_origin.xml") with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") cat = _read_quakeml(filename) self.assertEqual(len(w), 0) # add some custom tags to first event: # - tag with explicit namespace but no explicit ns abbreviation # - tag without explicit namespace (gets obspy default ns) # - tag with explicit namespace and namespace abbreviation my_extra = AttribDict( {'public': {'value': False, 'namespace': 'http://some-page.de/xmlns/1.0', 'attrib': {'some_attrib': 'some_value', 'another_attrib': 'another_value'}}, 'custom': {'value': 'True', 'namespace': 'http://test.org/xmlns/0.1'}, 'new_tag': {'value': 1234, 'namespace': 'http://test.org/xmlns/0.1'}, 'tX': {'value': UTCDateTime('2013-01-02T13:12:14.600000Z'), 'namespace': 'http://test.org/xmlns/0.1'}, 'dataid': {'namespace': 'http://anss.org/xmlns/catalog/0.1', 'type': 'attribute', 'value': '00999999'}, # some nested tags : 'quantity': {'namespace': 'http://some-page.de/xmlns/1.0', 'attrib': {'attrib1': 'attrib_value1', 'attrib2': 'attrib_value2'}, 'value': { 'my_nested_tag1': { 'namespace': 'http://some-page.de/xmlns/1.0', 'value': 1.23E10}, 'my_nested_tag2': { 'namespace': 'http://some-page.de/xmlns/1.0', 'value': False}}}}) nsmap = {'ns0': 'http://test.org/xmlns/0.1', 'catalog': 'http://anss.org/xmlns/catalog/0.1'} cat[0].extra = my_extra.copy() # insert a pick with an extra field p = Pick() p.extra = {'weight': {'value': 2, 'namespace': 'http://test.org/xmlns/0.1'}} cat[0].picks.append(p) with NamedTemporaryFile() as tf: tmpfile = tf.name # write file cat.write(tmpfile, format='QUAKEML', nsmap=nsmap) # check contents with open(tmpfile, 'rb') as fh: # enforce reproducible attribute orders through write_c14n obj = etree.fromstring(fh.read()).getroottree() buf = io.BytesIO() obj.write_c14n(buf) buf.seek(0, 0) content = buf.read() # check namespace definitions in root element expected = [b'<q:quakeml', b'xmlns:catalog="http://anss.org/xmlns/catalog/0.1"', b'xmlns:ns0="http://test.org/xmlns/0.1"', b'xmlns:ns1="http://some-page.de/xmlns/1.0"', b'xmlns:q="http://quakeml.org/xmlns/quakeml/1.2"', b'xmlns="http://quakeml.org/xmlns/bed/1.2"'] for line in expected: self.assertIn(line, content) # check additional tags expected = [ b'<ns0:custom>True</ns0:custom>', b'<ns0:new_tag>1234</ns0:new_tag>', b'<ns0:tX>2013-01-02T13:12:14.600000Z</ns0:tX>', b'<ns1:public ' b'another_attrib="another_value" ' b'some_attrib="some_value">false</ns1:public>' ] for line in expected: self.assertIn(line, content) # now, read again to test if it's parsed correctly.. cat = _read_quakeml(tmpfile) # when reading.. # - namespace abbreviations should be disregarded # - we always end up with a namespace definition, even if it was # omitted when originally setting the custom tag # - custom namespace abbreviations should attached to Catalog self.assertTrue(hasattr(cat[0], 'extra')) def _tostr(x): if isinstance(x, bool): if x: return str('true') else: return str('false') elif isinstance(x, AttribDict): for key, value in x.items(): x[key].value = _tostr(value['value']) return x else: return str(x) for key, value in my_extra.items(): my_extra[key]['value'] = _tostr(value['value']) self.assertEqual(cat[0].extra, my_extra) self.assertTrue(hasattr(cat[0].picks[0], 'extra')) self.assertEqual( cat[0].picks[0].extra, {'weight': {'value': '2', 'namespace': 'http://test.org/xmlns/0.1'}}) self.assertTrue(hasattr(cat, 'nsmap')) self.assertEqual(getattr(cat, 'nsmap')['ns0'], nsmap['ns0'])
def match_filter(template_names, template_list, st, threshold, threshold_type, trig_int, plotvar, plotdir='.', cores=1, debug=0, plot_format='png', output_cat=False, extract_detections=False, arg_check=True): """ Main matched-filter detection function. Over-arching code to run the correlations of given templates with a \ day of seismic data and output the detections based on a given threshold. For a functional example see the tutorials. :type template_names: list :param template_names: List of template names in the same order as \ template_list :type template_list: list :param template_list: A list of templates of which each template is a \ Stream of obspy traces containing seismic data and header information. :type st: obspy.core.stream.Stream :param st: A Stream object containing all the data available and \ required for the correlations with templates given. For efficiency \ this should contain no excess traces which are not in one or more of \ the templates. This will now remove excess traces internally, but \ will copy the stream and work on the copy, leaving your input stream \ untouched. :type threshold: float :param threshold: A threshold value set based on the threshold_type :type threshold_type: str :param threshold_type: The type of threshold to be used, can be MAD, \ absolute or av_chan_corr. See Note on thresholding below. :type trig_int: float :param trig_int: Minimum gap between detections in seconds. :type plotvar: bool :param plotvar: Turn plotting on or off :type plotdir: str :param plotdir: Path to plotting folder, plots will be output here, \ defaults to run location. :type cores: int :param cores: Number of cores to use :type debug: int :param debug: Debug output level, the bigger the number, the more the \ output. :type plot_format: str :param plot_format: Specify format of output plots if saved :type output_cat: bool :param output_cat: Specifies if matched_filter will output an \ obspy.Catalog class containing events for each detection. Default \ is False, in which case matched_filter will output a list of \ detection classes, as normal. :type extract_detections: bool :param extract_detections: Specifies whether or not to return a list of \ streams, one stream per detection. :type arg_check: bool :param arg_check: Check arguments, defaults to True, but if running in \ bulk, and you are certain of your arguments, then set to False.\n .. rubric:: If neither `output_cat` or `extract_detections` are set to `True`, then only the list of :class:`eqcorrscan.core.match_filter.DETECTION`'s will be output: :return: :class:`eqcorrscan.core.match_filter.DETECTION`'s detections for each detection made. :rtype: list .. rubric:: If `output_cat` is set to `True`, then the :class:`obspy.core.event.Catalog` will also be output: :return: Catalog containing events for each detection, see above. :rtype: :class:`obspy.core.event.Catalog` .. rubric:: If `extract_detections` is set to `True` then the list of :class:`obspy.core.stream.Stream`'s will also be output. :return: list of :class:`obspy.core.stream.Stream`'s for each detection, see above. :rtype: list .. warning:: Plotting within the match-filter routine uses the Agg backend with interactive plotting turned off. This is because the function is designed to work in bulk. If you wish to turn interactive plotting on you must import matplotlib in your script first, when you them import match_filter you will get the warning that this call to matplotlib has no effect, which will mean that match_filter has not changed the plotting behaviour. .. note:: **Thresholding:** **MAD** threshold is calculated as the: .. math:: threshold {\\times} (median(abs(cccsum))) where :math:`cccsum` is the cross-correlation sum for a given template. **absolute** threshold is a true absolute threshold based on the cccsum value. **av_chan_corr** is based on the mean values of single-channel cross-correlations assuming all data are present as required for the template, e.g: .. math:: av\_chan\_corr\_thresh=threshold \\times (cccsum / len(template)) where :math:`template` is a single template from the input and the length is the number of channels within this template. .. note:: The output_cat flag will create an :class:`obspy.core.eventCatalog` containing one event for each :class:`eqcorrscan.core.match_filter.DETECTION`'s generated by match_filter. Each event will contain a number of comments dealing with correlation values and channels used for the detection. Each channel used for the detection will have a corresponding :class:`obspy.core.event.Pick` which will contain time and waveform information. **HOWEVER**, the user should note that, at present, the pick times do not account for the prepick times inherent in each template. For example, if a template trace starts 0.1 seconds before the actual arrival of that phase, then the pick time generated by match_filter for that phase will be 0.1 seconds early. We are working on a solution that will involve saving templates alongside associated metadata. """ import matplotlib matplotlib.use('Agg') if arg_check: # Check the arguments to be nice - if arguments wrong type the parallel # output for the error won't be useful if not type(template_names) == list: raise MatchFilterError('template_names must be of type: list') if not type(template_list) == list: raise MatchFilterError('templates must be of type: list') if not len(template_list) == len(template_names): raise MatchFilterError('Not the same number of templates as names') for template in template_list: if not type(template) == Stream: msg = 'template in template_list must be of type: ' +\ 'obspy.core.stream.Stream' raise MatchFilterError(msg) if not type(st) == Stream: msg = 'st must be of type: obspy.core.stream.Stream' raise MatchFilterError(msg) if str(threshold_type) not in [str('MAD'), str('absolute'), str('av_chan_corr')]: msg = 'threshold_type must be one of: MAD, absolute, av_chan_corr' raise MatchFilterError(msg) # Copy the stream here because we will muck about with it stream = st.copy() templates = copy.deepcopy(template_list) _template_names = copy.deepcopy(template_names) # Debug option to confirm that the channel names match those in the # templates if debug >= 2: template_stachan = [] data_stachan = [] for template in templates: for tr in template: if isinstance(tr.data, np.ma.core.MaskedArray): raise MatchFilterError('Template contains masked array,' ' split first') template_stachan.append(tr.stats.station + '.' + tr.stats.channel) for tr in stream: data_stachan.append(tr.stats.station + '.' + tr.stats.channel) template_stachan = list(set(template_stachan)) data_stachan = list(set(data_stachan)) if debug >= 3: print('I have template info for these stations:') print(template_stachan) print('I have daylong data for these stations:') print(data_stachan) # Perform a check that the continuous data are all the same length min_start_time = min([tr.stats.starttime for tr in stream]) max_end_time = max([tr.stats.endtime for tr in stream]) longest_trace_length = stream[0].stats.sampling_rate * (max_end_time - min_start_time) for tr in stream: if not tr.stats.npts == longest_trace_length: msg = 'Data are not equal length, padding short traces' warnings.warn(msg) start_pad = np.zeros(int(tr.stats.sampling_rate * (tr.stats.starttime - min_start_time))) end_pad = np.zeros(int(tr.stats.sampling_rate * (max_end_time - tr.stats.endtime))) tr.data = np.concatenate([start_pad, tr.data, end_pad]) # Perform check that all template lengths are internally consistent for i, temp in enumerate(template_list): if len(set([tr.stats.npts for tr in temp])) > 1: msg = ('Template %s contains traces of differing length, this is ' 'not currently supported' % _template_names[i]) raise MatchFilterError(msg) outtic = time.clock() if debug >= 2: print('Ensuring all template channels have matches in long data') template_stachan = {} # Work out what station-channel pairs are in the templates, including # duplicate station-channel pairs. We will use this information to fill # all templates with the same station-channel pairs as required by # _template_loop. for template in templates: stachans_in_template = [] for tr in template: stachans_in_template.append((tr.stats.network, tr.stats.station, tr.stats.location, tr.stats.channel)) stachans_in_template = dict(Counter(stachans_in_template)) for stachan in stachans_in_template.keys(): if stachan not in template_stachan.keys(): template_stachan.update({stachan: stachans_in_template[stachan]}) elif stachans_in_template[stachan] > template_stachan[stachan]: template_stachan.update({stachan: stachans_in_template[stachan]}) # Remove un-matched channels from templates. _template_stachan = copy.deepcopy(template_stachan) for stachan in template_stachan.keys(): if not stream.select(network=stachan[0], station=stachan[1], location=stachan[2], channel=stachan[3]): # Remove stachan from list of dictionary of template_stachans _template_stachan.pop(stachan) # Remove template traces rather than adding NaN data for template in templates: if template.select(network=stachan[0], station=stachan[1], location=stachan[2], channel=stachan[3]): for tr in template.select(network=stachan[0], station=stachan[1], location=stachan[2], channel=stachan[3]): template.remove(tr) template_stachan = _template_stachan # Remove un-needed channels from continuous data. for tr in stream: if not (tr.stats.network, tr.stats.station, tr.stats.location, tr.stats.channel) in \ template_stachan.keys(): stream.remove(tr) # Check for duplicate channels stachans = [(tr.stats.network, tr.stats.station, tr.stats.location, tr.stats.channel) for tr in stream] c_stachans = Counter(stachans) for key in c_stachans.keys(): if c_stachans[key] > 1: msg = ('Multiple channels for %s.%s.%s.%s, likely a data issue' % (key[0], key[1], key[2], key[3])) raise MatchFilterError(msg) # Pad out templates to have all channels for template, template_name in zip(templates, _template_names): if len(template) == 0: msg = ('No channels matching in continuous data for ' + 'template' + template_name) warnings.warn(msg) templates.remove(template) _template_names.remove(template_name) continue for stachan in template_stachan.keys(): number_of_channels = len(template.select(network=stachan[0], station=stachan[1], location=stachan[2], channel=stachan[3])) if number_of_channels < template_stachan[stachan]: missed_channels = template_stachan[stachan] -\ number_of_channels nulltrace = Trace() nulltrace.stats.update( {'network': stachan[0], 'station': stachan[1], 'location': stachan[2], 'channel': stachan[3], 'sampling_rate': template[0].stats.sampling_rate, 'starttime': template[0].stats.starttime}) nulltrace.data = np.array([np.NaN] * len(template[0].data), dtype=np.float32) for dummy in range(missed_channels): template += nulltrace template.sort() # Quick check that this has all worked if len(template) != max([len(t) for t in templates]): raise MatchFilterError('Internal error forcing same template ' 'lengths, report this error.') if debug >= 2: print('Starting the correlation run for this day') if debug >= 4: for template in templates: print(template) print(stream) [cccsums, no_chans, chans] = _channel_loop(templates=templates, stream=stream, cores=cores, debug=debug) if len(cccsums[0]) == 0: raise MatchFilterError('Correlation has not run, zero length cccsum') outtoc = time.clock() print(' '.join(['Looping over templates and streams took:', str(outtoc - outtic), 's'])) if debug >= 2: print(' '.join(['The shape of the returned cccsums is:', str(np.shape(cccsums))])) print(' '.join(['This is from', str(len(templates)), 'templates'])) print(' '.join(['Correlated with', str(len(stream)), 'channels of data'])) detections = [] if output_cat: det_cat = Catalog() for i, cccsum in enumerate(cccsums): template = templates[i] if str(threshold_type) == str('MAD'): rawthresh = threshold * np.median(np.abs(cccsum)) elif str(threshold_type) == str('absolute'): rawthresh = threshold elif str(threshold_type) == str('av_chan_corr'): rawthresh = threshold * no_chans[i] # Findpeaks returns a list of tuples in the form [(cccsum, sample)] print(' '.join(['Threshold is set at:', str(rawthresh)])) print(' '.join(['Max of data is:', str(max(cccsum))])) print(' '.join(['Mean of data is:', str(np.mean(cccsum))])) if np.abs(np.mean(cccsum)) > 0.05: warnings.warn('Mean is not zero! Check this!') # Set up a trace object for the cccsum as this is easier to plot and # maintains timing if plotvar: _match_filter_plot(stream=stream, cccsum=cccsum, template_names=_template_names, rawthresh=rawthresh, plotdir=plotdir, plot_format=plot_format, i=i) if debug >= 4: print(' '.join(['Saved the cccsum to:', _template_names[i], stream[0].stats.starttime.datetime. strftime('%Y%j')])) np.save(_template_names[i] + stream[0].stats.starttime.datetime.strftime('%Y%j'), cccsum) tic = time.clock() if max(cccsum) > rawthresh: peaks = findpeaks.find_peaks2_short( arr=cccsum, thresh=rawthresh, trig_int=trig_int * stream[0].stats.sampling_rate, debug=debug, starttime=stream[0].stats.starttime, samp_rate=stream[0].stats.sampling_rate) else: print('No peaks found above threshold') peaks = False toc = time.clock() if debug >= 1: print(' '.join(['Finding peaks took:', str(toc - tic), 's'])) if peaks: for peak in peaks: detecttime = stream[0].stats.starttime +\ peak[1] / stream[0].stats.sampling_rate # Detect time must be valid QuakeML uri within resource_id. # This will write a formatted string which is still # readable by UTCDateTime rid = ResourceIdentifier(id=_template_names[i] + '_' + str(detecttime. strftime('%Y%m%dT%H%M%S.%f')), prefix='smi:local') ev = Event(resource_id=rid) cr_i = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) ev.creation_info = cr_i # All detection info in Comments for lack of a better idea thresh_str = 'threshold=' + str(rawthresh) ccc_str = 'detect_val=' + str(peak[0]) used_chans = 'channels used: ' +\ ' '.join([str(pair) for pair in chans[i]]) ev.comments.append(Comment(text=thresh_str)) ev.comments.append(Comment(text=ccc_str)) ev.comments.append(Comment(text=used_chans)) min_template_tm = min([tr.stats.starttime for tr in template]) for tr in template: if (tr.stats.station, tr.stats.channel) not in chans[i]: continue else: pick_tm = detecttime + (tr.stats.starttime - min_template_tm) wv_id = WaveformStreamID(network_code=tr.stats.network, station_code=tr.stats.station, channel_code=tr.stats.channel) ev.picks.append(Pick(time=pick_tm, waveform_id=wv_id)) detections.append(DETECTION(_template_names[i], detecttime, no_chans[i], peak[0], rawthresh, 'corr', chans[i], event=ev)) if output_cat: det_cat.append(ev) if extract_detections: detection_streams = extract_from_stream(stream, detections) del stream, templates if output_cat and not extract_detections: return detections, det_cat elif not extract_detections: return detections elif extract_detections and not output_cat: return detections, detection_streams else: return detections, det_cat, detection_streams