def _parse_record_hy(self, line): """ Parses the 'hypocenter' record HY """ date = line[2:10] time = line[11:20] # unused: location_quality = line[20] latitude = self._float(line[21:27]) lat_type = line[27] longitude = self._float(line[29:36]) lon_type = line[36] depth = self._float(line[38:43]) # unused: depth_quality = line[43] standard_dev = self._float(line[44:48]) station_number = self._int(line[48:51]) # unused: version_flag = line[51] fe_region_number = line[52:55] fe_region_name = self._decode_fe_region_number(fe_region_number) source_code = line[55:60].strip() event = Event() # FIXME: a smarter way to define evid? evid = date + time res_id = '/'.join((res_id_prefix, 'event', evid)) event.resource_id = ResourceIdentifier(id=res_id) description = EventDescription( type='region name', text=fe_region_name) event.event_descriptions.append(description) description = EventDescription( type='Flinn-Engdahl region', text=fe_region_number) event.event_descriptions.append(description) origin = Origin() res_id = '/'.join((res_id_prefix, 'origin', evid)) origin.resource_id = ResourceIdentifier(id=res_id) origin.creation_info = CreationInfo() if source_code: origin.creation_info.agency_id = source_code else: origin.creation_info.agency_id = 'USGS-NEIC' res_id = '/'.join((res_id_prefix, 'earthmodel/ak135')) origin.earth_model_id = ResourceIdentifier(id=res_id) origin.time = UTCDateTime(date + time) origin.latitude = latitude * self._coordinate_sign(lat_type) origin.longitude = longitude * self._coordinate_sign(lon_type) origin.depth = depth * 1000 origin.depth_type = 'from location' origin.quality = OriginQuality() origin.quality.associated_station_count = station_number origin.quality.standard_error = standard_dev # associated_phase_count can be incremented in records 'P ' and 'S ' origin.quality.associated_phase_count = 0 # depth_phase_count can be incremented in record 'S ' origin.quality.depth_phase_count = 0 origin.origin_type = 'hypocenter' origin.region = fe_region_name event.origins.append(origin) return event
def node_catalog_no_picks(node_catalog) -> Tuple[obspy.Catalog, Dict]: """return the node catalog with just origins""" eid_map = {} cat = Catalog() for num, eve in enumerate(node_catalog): eve_out = Event(origins=eve.origins) for o in eve_out.origins: o.arrivals = [] eve_out.resource_id = ResourceIdentifier(f"event_{num}") cat.append(eve_out) eid_map[eve.resource_id.id] = eve_out.resource_id.id return cat, eid_map
def _parse_event(self, first_line): """ Parse an event. :type first_line: str :param first_line: First line of an event block, which contains the event id. :rtype: :class:`~obspy.core.event.event.Event` :return: The parsed event or None. """ event_id = first_line[5:].strip() # Skip event without id if not event_id: self._warn('Missing event id') return None event = Event() origin, origin_res_id = self._parse_origin(event) # Skip event without origin if not origin: return None line = self._skip_empty_lines() self._parse_region_name(line, event) self._parse_arrivals(event, origin, origin_res_id) # Origin ResourceIdentifier should be set at the end, when # Arrivals are already set. origin.resource_id = origin_res_id event.origins.append(origin) event.preferred_origin_id = origin.resource_id.id # Must be done after the origin parsing event.creation_info = self._get_creation_info() public_id = "event/%s" % event_id event.resource_id = self._get_res_id(public_id) event.scope_resource_ids() return event
def event_to_quakeml(event, filename): """ Write one of those events to QuakeML. """ # Create all objects. cat = Catalog() ev = Event() org = Origin() mag = Magnitude() fm = FocalMechanism() mt = MomentTensor() t = Tensor() # Link them together. cat.append(ev) ev.origins.append(org) ev.magnitudes.append(mag) ev.focal_mechanisms.append(fm) fm.moment_tensor = mt mt.tensor = t # Fill values ev.resource_id = "smi:inversion/%s" % str(event["identifier"]) org.time = event["time"] org.longitude = event["longitude"] org.latitude = event["latitude"] org.depth = event["depth_in_km"] * 1000 mag.mag = event["Mw"] mag.magnitude_type = "Mw" t.m_rr = event["Mrr"] t.m_tt = event["Mpp"] t.m_pp = event["Mtt"] t.m_rt = event["Mrt"] t.m_rp = event["Mrp"] t.m_tp = event["Mtp"] cat.write(filename, format="quakeml")
def _read_ndk(filename, *args, **kwargs): # @UnusedVariable """ Reads an NDK file to a :class:`~obspy.core.event.Catalog` object. :param filename: File or file-like object in text mode. """ # Read the whole file at once. While an iterator would be more efficient # the largest NDK file out in the wild is 13.7 MB so it does not matter # much. 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() # Create iterator that yields lines. def lines_iter(): prev_line = -1 while True: next_line = data.find("\n", prev_line + 1) if next_line < 0: break yield data[prev_line + 1: next_line] prev_line = next_line if len(data) > prev_line + 1: yield data[prev_line + 1:] # Use one Flinn Engdahl object for all region determinations. fe = FlinnEngdahl() cat = Catalog(resource_id=_get_resource_id("catalog", str(uuid.uuid4()))) # Loop over 5 lines at once. for _i, lines in enumerate(itertools.zip_longest(*[lines_iter()] * 5)): if None in lines: msg = "Skipped last %i lines. Not a multiple of 5 lines." % ( lines.count(None)) warnings.warn(msg, ObsPyNDKWarning) continue # Parse the lines to a human readable dictionary. try: record = _read_lines(*lines) except (ValueError, ObsPyNDKException): exc = traceback.format_exc() msg = ( "Could not parse event %i (faulty file?). Will be " "skipped. Lines of the event:\n" "\t%s\n" "%s") % (_i + 1, "\n\t".join(lines), exc) warnings.warn(msg, ObsPyNDKWarning) continue # Use one creation info for essentially every item. creation_info = CreationInfo( agency_id="GCMT", version=record["version_code"] ) # Use the ObsPy Flinn Engdahl region determiner as the region in the # NDK files is oftentimes trimmed. region = fe.get_region(record["centroid_longitude"], record["centroid_latitude"]) # Create an event object. event = Event( force_resource_id=False, event_type="earthquake", event_type_certainty="known", event_descriptions=[ EventDescription(text=region, type="Flinn-Engdahl region"), EventDescription(text=record["cmt_event_name"], type="earthquake name") ] ) # Assemble the time for the reference origin. try: time = _parse_date_time(record["date"], record["time"]) except ObsPyNDKException: msg = ("Invalid time in event %i. '%s' and '%s' cannot be " "assembled to a valid time. Event will be skipped.") % \ (_i + 1, record["date"], record["time"]) warnings.warn(msg, ObsPyNDKWarning) continue # Create two origins, one with the reference latitude/longitude and # one with the centroidal values. ref_origin = Origin( force_resource_id=False, time=time, longitude=record["hypo_lng"], latitude=record["hypo_lat"], # Convert to m. depth=record["hypo_depth_in_km"] * 1000.0, origin_type="hypocenter", comments=[Comment(text="Hypocenter catalog: %s" % record["hypocenter_reference_catalog"], force_resource_id=False)] ) ref_origin.comments[0].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="ref_origin") ref_origin.resource_id = _get_resource_id(record["cmt_event_name"], "origin", tag="reforigin") cmt_origin = Origin( force_resource_id=False, longitude=record["centroid_longitude"], longitude_errors={ "uncertainty": record["centroid_longitude_error"]}, latitude=record["centroid_latitude"], latitude_errors={ "uncertainty": record["centroid_latitude_error"]}, # Convert to m. depth=record["centroid_depth_in_km"] * 1000.0, depth_errors={ "uncertainty": record["centroid_depth_in_km_error"] * 1000}, time=ref_origin["time"] + record["centroid_time"], time_errors={"uncertainty": record["centroid_time_error"]}, depth_type=record["type_of_centroid_depth"], origin_type="centroid", time_fixed=False, epicenter_fixed=False, creation_info=creation_info.copy() ) cmt_origin.resource_id = _get_resource_id(record["cmt_event_name"], "origin", tag="cmtorigin") event.origins = [ref_origin, cmt_origin] event.preferred_origin_id = cmt_origin.resource_id.id # Create the magnitude object. mag = Magnitude( force_resource_id=False, mag=round(record["Mw"], 2), magnitude_type="Mwc", origin_id=cmt_origin.resource_id, creation_info=creation_info.copy() ) mag.resource_id = _get_resource_id(record["cmt_event_name"], "magnitude", tag="moment_mag") event.magnitudes = [mag] event.preferred_magnitude_id = mag.resource_id.id # Add the reported mb, MS magnitudes as additional magnitude objects. event.magnitudes.append(Magnitude( force_resource_id=False, mag=record["mb"], magnitude_type="mb", comments=[Comment( force_resource_id=False, text="Reported magnitude in NDK file. Most likely 'mb'." )] )) event.magnitudes[-1].comments[-1].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="mb_magnitude") event.magnitudes[-1].resource_id = _get_resource_id( record["cmt_event_name"], "magnitude", tag="mb") event.magnitudes.append(Magnitude( force_resource_id=False, mag=record["MS"], magnitude_type="MS", comments=[Comment( force_resource_id=False, text="Reported magnitude in NDK file. Most likely 'MS'." )] )) event.magnitudes[-1].comments[-1].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="MS_magnitude") event.magnitudes[-1].resource_id = _get_resource_id( record["cmt_event_name"], "magnitude", tag="MS") # Take care of the moment tensor. tensor = Tensor( m_rr=record["m_rr"], m_rr_errors={"uncertainty": record["m_rr_error"]}, m_pp=record["m_pp"], m_pp_errors={"uncertainty": record["m_pp_error"]}, m_tt=record["m_tt"], m_tt_errors={"uncertainty": record["m_tt_error"]}, m_rt=record["m_rt"], m_rt_errors={"uncertainty": record["m_rt_error"]}, m_rp=record["m_rp"], m_rp_errors={"uncertainty": record["m_rp_error"]}, m_tp=record["m_tp"], m_tp_errors={"uncertainty": record["m_tp_error"]}, creation_info=creation_info.copy() ) mt = MomentTensor( force_resource_id=False, scalar_moment=record["scalar_moment"], tensor=tensor, data_used=[DataUsed(**i) for i in record["data_used"]], inversion_type=record["source_type"], source_time_function=SourceTimeFunction( type=record["moment_rate_type"], duration=record["moment_rate_duration"] ), derived_origin_id=cmt_origin.resource_id, creation_info=creation_info.copy() ) mt.resource_id = _get_resource_id(record["cmt_event_name"], "momenttensor") axis = [Axis(**i) for i in record["principal_axis"]] focmec = FocalMechanism( force_resource_id=False, moment_tensor=mt, principal_axes=PrincipalAxes( # The ordering is the same as for the IRIS SPUD service and # from a website of the Saint Louis University Earthquake # center so it should be correct. t_axis=axis[0], p_axis=axis[2], n_axis=axis[1] ), nodal_planes=NodalPlanes( nodal_plane_1=NodalPlane(**record["nodal_plane_1"]), nodal_plane_2=NodalPlane(**record["nodal_plane_2"]) ), comments=[ Comment(force_resource_id=False, text="CMT Analysis Type: %s" % record["cmt_type"].capitalize()), Comment(force_resource_id=False, text="CMT Timestamp: %s" % record["cmt_timestamp"])], creation_info=creation_info.copy() ) focmec.comments[0].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="cmt_type") focmec.comments[1].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="cmt_timestamp") focmec.resource_id = _get_resource_id(record["cmt_event_name"], "focal_mechanism") event.focal_mechanisms = [focmec] event.preferred_focal_mechanism_id = focmec.resource_id.id # Set at end to avoid duplicate resource id warning. event.resource_id = _get_resource_id(record["cmt_event_name"], "event") cat.append(event) if len(cat) == 0: msg = "No valid events found in NDK file." raise ObsPyNDKException(msg) return cat
def __read_single_fnetmt_entry(line, **kwargs): """ Reads a single F-net moment tensor solution to a :class:`~obspy.core.event.Event` object. :param line: String containing moment tensor information. :type line: str. """ a = line.split() try: ot = UTCDateTime().strptime(a[0], '%Y/%m/%d,%H:%M:%S.%f') except ValueError: ot = UTCDateTime().strptime(a[0], '%Y/%m/%d,%H:%M:%S') lat, lon, depjma, magjma = map(float, a[1:5]) depjma *= 1000 region = a[5] strike = tuple(map(int, a[6].split(';'))) dip = tuple(map(int, a[7].split(';'))) rake = tuple(map(int, a[8].split(';'))) mo = float(a[9]) depmt = float(a[10]) * 1000 magmt = float(a[11]) var_red = float(a[12]) mxx, mxy, mxz, myy, myz, mzz, unit = map(float, a[13:20]) event_name = util.gen_sc3_id(ot) e = Event(event_type="earthquake") e.resource_id = _get_resource_id(event_name, 'event') # Standard JMA solution o_jma = Origin(time=ot, latitude=lat, longitude=lon, depth=depjma, depth_type="from location", region=region) o_jma.resource_id = _get_resource_id(event_name, 'origin', 'JMA') m_jma = Magnitude(mag=magjma, magnitude_type='ML', origin_id=o_jma.resource_id) m_jma.resource_id = _get_resource_id(event_name, 'magnitude', 'JMA') # MT solution o_mt = Origin(time=ot, latitude=lat, longitude=lon, depth=depmt, region=region, depth_type="from moment tensor inversion") o_mt.resource_id = _get_resource_id(event_name, 'origin', 'MT') m_mt = Magnitude(mag=magmt, magnitude_type='Mw', origin_id=o_mt.resource_id) m_mt.resource_id = _get_resource_id(event_name, 'magnitude', 'MT') foc_mec = FocalMechanism(triggering_origin_id=o_jma.resource_id) foc_mec.resource_id = _get_resource_id(event_name, "focal_mechanism") nod1 = NodalPlane(strike=strike[0], dip=dip[0], rake=rake[0]) nod2 = NodalPlane(strike=strike[1], dip=dip[1], rake=rake[1]) nod = NodalPlanes(nodal_plane_1=nod1, nodal_plane_2=nod2) foc_mec.nodal_planes = nod tensor = Tensor(m_rr=mxx, m_tt=myy, m_pp=mzz, m_rt=mxy, m_rp=mxz, m_tp=myz) cm = Comment(text="Basis system: North,East,Down (Jost and \ Herrmann 1989") cm.resource_id = _get_resource_id(event_name, 'comment', 'mt') mt = MomentTensor(derived_origin_id=o_mt.resource_id, moment_magnitude_id=m_mt.resource_id, scalar_moment=mo, comments=[cm], tensor=tensor, variance_reduction=var_red) mt.resource_id = _get_resource_id(event_name, 'moment_tensor') foc_mec.moment_tensor = mt e.origins = [o_jma, o_mt] e.magnitudes = [m_jma, m_mt] e.focal_mechanisms = [foc_mec] e.preferred_magnitude_id = m_mt.resource_id.id e.preferred_origin_id = o_mt.resource_id.id e.preferred_focal_mechanism_id = foc_mec.resource_id.id return e
def _channel_loop(detection, template, min_cc, detection_id, interpolate, i, pre_lag_ccsum=None, detect_chans=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. :returns: Event object containing network, station, channel and pick information. :rtype: :class:`obspy.core.event.Event` """ event = Event() s_stachans = {} used_s_sta = [] 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 image = detection.select(station=temp_sta, channel=temp_chan) if image: if interpolate: try: ccc = normxcorr2(tr.data, image[0].data) shift, cc_max = _xcorr_interp(ccc=ccc, dt=image[0].stats.delta) except IndexError: log.error('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 picktime = image[0].stats.starttime + shift else: # Convert the maximum cross-correlation time to an actual time ccc = normxcorr2(tr.data, image[0].data) cc_max = np.amax(ccc) picktime = image[0].stats.starttime + (np.argmax(ccc) * image[0].stats.delta) log.debug('********DEBUG: Maximum cross-corr=%s' % cc_max) checksum += cc_max used_chans += 1 if cc_max < min_cc: 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:] == 'Z': phase = 'P' # Only take the S-pick with the best correlation elif temp_chan[-1:] in ['E', 'N']: phase = 'S' if temp_sta not in s_stachans and np.amax(ccc) > min_cc: s_stachans[temp_sta] = ((temp_chan, np.amax(ccc), picktime)) elif temp_sta in s_stachans and np.amax(ccc) > min_cc: if np.amax(ccc) > s_stachans[temp_sta][1]: picktime = picktime else: picktime = s_stachans[temp_sta][2] temp_chan = s_stachans[temp_sta][0] elif np.amax(ccc) < min_cc and temp_sta not in used_s_sta: used_s_sta.append(temp_sta) 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', 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.05: msg = ('lag-calc has decreased cccsum from %f to %f - ' 'report this error' % (pre_lag_ccsum, checksum)) raise LagCalcError(msg) else: warnings.warn('Cannot check is cccsum is better, used %i channels ' 'for detection, but %i are used here' % (detect_chans, used_chans)) return i, event
def _read_single_event(event_file, locate_dir, units, local_mag_ph): """ Parse an event file from QuakeMigrate into an obspy Event object. Parameters ---------- event_file : `pathlib.Path` object Path to .event file to read. locate_dir : `pathlib.Path` object Path to locate directory (contains "events", "picks" etc. directories). units : {"km", "m"} Grid projection coordinates for QM LUT (determines units of depths and uncertainties in the .event files). local_mag_ph : {"S", "P"} Amplitude measurement used to calculate local magnitudes. Returns ------- event : `obspy.Event` object Event object populated with all available information output by :class:`~quakemigrate.signal.scan.locate()`, including event locations and uncertainties, picks, and amplitudes and magnitudes if available. """ # Parse information from event file event_info = pd.read_csv(event_file).iloc[0] event_uid = str(event_info["EventID"]) # Set distance conversion factor (from units of QM LUT projection units). if units == "km": factor = 1e3 elif units == "m": factor = 1 else: raise AttributeError(f"units must be 'km' or 'm'; not {units}") # Create event object to store origin and pick information event = Event() event.extra = AttribDict() event.resource_id = str(event_info["EventID"]) event.creation_info = CreationInfo(author="QuakeMigrate", version=quakemigrate.__version__) # Add COA info to extra event.extra.coa = {"value": event_info["COA"], "namespace": ns} event.extra.coa_norm = {"value": event_info["COA_NORM"], "namespace": ns} event.extra.trig_coa = {"value": event_info["TRIG_COA"], "namespace": ns} event.extra.dec_coa = {"value": event_info["DEC_COA"], "namespace": ns} event.extra.dec_coa_norm = { "value": event_info["DEC_COA_NORM"], "namespace": ns } # Determine location of cut waveform data - add to event object as a # custom extra attribute. mseed = locate_dir / "raw_cut_waveforms" / event_uid event.extra.cut_waveforms_file = { "value": str(mseed.with_suffix(".m").resolve()), "namespace": ns } if (locate_dir / "real_cut_waveforms").exists(): mseed = locate_dir / "real_cut_waveforms" / event_uid event.extra.real_cut_waveforms_file = { "value": str(mseed.with_suffix(".m").resolve()), "namespace": ns } if (locate_dir / "wa_cut_waveforms").exists(): mseed = locate_dir / "wa_cut_waveforms" / event_uid event.extra.wa_cut_waveforms_file = { "value": str(mseed.with_suffix(".m").resolve()), "namespace": ns } # Create origin with spline location and set to preferred event origin. origin = Origin() origin.method_id = "spline" origin.longitude = event_info["X"] origin.latitude = event_info["Y"] origin.depth = event_info["Z"] * factor origin.time = UTCDateTime(event_info["DT"]) event.origins = [origin] event.preferred_origin_id = origin.resource_id # Create origin with gaussian location and associate with event origin = Origin() origin.method_id = "gaussian" origin.longitude = event_info["GAU_X"] origin.latitude = event_info["GAU_Y"] origin.depth = event_info["GAU_Z"] * factor origin.time = UTCDateTime(event_info["DT"]) event.origins.append(origin) ouc = OriginUncertainty() ce = ConfidenceEllipsoid() ce.semi_major_axis_length = event_info["COV_ErrY"] * factor ce.semi_intermediate_axis_length = event_info["COV_ErrX"] * factor ce.semi_minor_axis_length = event_info["COV_ErrZ"] * factor ce.major_axis_plunge = 0 ce.major_axis_azimuth = 0 ce.major_axis_rotation = 0 ouc.confidence_ellipsoid = ce ouc.preferred_description = "confidence ellipsoid" # Set uncertainties for both as the gaussian uncertainties for origin in event.origins: origin.longitude_errors.uncertainty = kilometer2degrees( event_info["GAU_ErrX"] * factor / 1e3) origin.latitude_errors.uncertainty = kilometer2degrees( event_info["GAU_ErrY"] * factor / 1e3) origin.depth_errors.uncertainty = event_info["GAU_ErrZ"] * factor origin.origin_uncertainty = ouc # Add OriginQuality info to each origin? for origin in event.origins: origin.origin_type = "hypocenter" origin.evaluation_mode = "automatic" # --- Handle picks file --- pick_file = locate_dir / "picks" / event_uid if pick_file.with_suffix(".picks").is_file(): picks = pd.read_csv(pick_file.with_suffix(".picks")) else: return None for _, pickline in picks.iterrows(): station = str(pickline["Station"]) phase = str(pickline["Phase"]) wid = WaveformStreamID(network_code="", station_code=station) for method in ["modelled", "autopick"]: pick = Pick() pick.extra = AttribDict() pick.waveform_id = wid pick.method_id = method pick.phase_hint = phase if method == "autopick" and str(pickline["PickTime"]) != "-1": pick.time = UTCDateTime(pickline["PickTime"]) pick.time_errors.uncertainty = float(pickline["PickError"]) pick.extra.snr = { "value": float(pickline["SNR"]), "namespace": ns } elif method == "modelled": pick.time = UTCDateTime(pickline["ModelledTime"]) else: continue event.picks.append(pick) # --- Handle amplitudes file --- amps_file = locate_dir / "amplitudes" / event_uid if amps_file.with_suffix(".amps").is_file(): amps = pd.read_csv(amps_file.with_suffix(".amps")) i = 0 for _, ampsline in amps.iterrows(): wid = WaveformStreamID(seed_string=ampsline["id"]) noise_amp = ampsline["Noise_amp"] / 1000 # mm to m for phase in ["P_amp", "S_amp"]: amp = Amplitude() if pd.isna(ampsline[phase]): continue amp.generic_amplitude = ampsline[phase] / 1000 # mm to m amp.generic_amplitude_errors.uncertainty = noise_amp amp.unit = "m" amp.type = "AML" amp.method_id = phase amp.period = 1 / ampsline[f"{phase[0]}_freq"] amp.time_window = TimeWindow( reference=UTCDateTime(ampsline[f"{phase[0]}_time"])) # amp.pick_id = ? amp.waveform_id = wid # amp.filter_id = ? amp.magnitude_hint = "ML" amp.evaluation_mode = "automatic" amp.extra = AttribDict() try: amp.extra.filter_gain = { "value": ampsline[f"{phase[0]}_filter_gain"], "namespace": ns } amp.extra.avg_amp = { "value": ampsline[f"{phase[0]}_avg_amp"] / 1000, # m "namespace": ns } except KeyError: pass if phase[0] == local_mag_ph and not pd.isna(ampsline["ML"]): i += 1 stat_mag = StationMagnitude() stat_mag.extra = AttribDict() # stat_mag.origin_id = ? local_mag_loc stat_mag.mag = ampsline["ML"] stat_mag.mag_errors.uncertainty = ampsline["ML_Err"] stat_mag.station_magnitude_type = "ML" stat_mag.amplitude_id = amp.resource_id stat_mag.extra.picked = { "value": ampsline["is_picked"], "namespace": ns } stat_mag.extra.epi_dist = { "value": ampsline["epi_dist"], "namespace": ns } stat_mag.extra.z_dist = { "value": ampsline["z_dist"], "namespace": ns } event.station_magnitudes.append(stat_mag) event.amplitudes.append(amp) mag = Magnitude() mag.extra = AttribDict() mag.mag = event_info["ML"] mag.mag_errors.uncertainty = event_info["ML_Err"] mag.magnitude_type = "ML" # mag.origin_id = ? mag.station_count = i mag.evaluation_mode = "automatic" mag.extra.r2 = {"value": event_info["ML_r2"], "namespace": ns} event.magnitudes = [mag] event.preferred_magnitude_id = mag.resource_id return event
def xcorr_pick_family(family, stream, shift_len=0.2, min_cc=0.4, horizontal_chans=['E', 'N', '1', '2'], vertical_chans=['Z'], cores=1, interpolate=False, plot=False, plotdir=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 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. :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] picked_chans = chans[i] detect_stream = detect_streams_dict[detection_id] checksum, cccsum, used_chans = 0.0, 0.0, 0 event = Event() 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 < min_cc: 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 build(self): """ Build an obspy moment tensor focal mech event This makes the tensor output into an Event containing: 1) a FocalMechanism with a MomentTensor, NodalPlanes, and PrincipalAxes 2) a Magnitude of the Mw from the Tensor Which is what we want for outputting QuakeML using the (slightly modified) obspy code. Input ----- filehandle => open file OR str from filehandle.read() Output ------ event => instance of Event() class as described above """ p = self.parser event = Event(event_type='earthquake') origin = Origin() focal_mech = FocalMechanism() nodal_planes = NodalPlanes() moment_tensor = MomentTensor() principal_ax = PrincipalAxes() magnitude = Magnitude() data_used = DataUsed() creation_info = CreationInfo(agency_id='NN') ev_mode = 'automatic' ev_stat = 'preliminary' evid = None orid = None # Parse the entire file line by line. for n,l in enumerate(p.line): if 'REVIEWED BY NSL STAFF' in l: ev_mode = 'manual' ev_stat = 'reviewed' if 'Event ID' in l: evid = p._id(n) if 'Origin ID' in l: orid = p._id(n) if 'Ichinose' in l: moment_tensor.category = 'regional' if re.match(r'^\d{4}\/\d{2}\/\d{2}', l): ev = p._event_info(n) if 'Depth' in l: derived_depth = p._depth(n) if 'Mw' in l: magnitude.mag = p._mw(n) magnitude.magnitude_type = 'Mw' if 'Mo' in l and 'dyne' in l: moment_tensor.scalar_moment = p._mo(n) if 'Percent Double Couple' in l: moment_tensor.double_couple = p._percent(n) if 'Percent CLVD' in l: moment_tensor.clvd = p._percent(n) if 'Epsilon' in l: moment_tensor.variance = p._epsilon(n) if 'Percent Variance Reduction' in l: moment_tensor.variance_reduction = p._percent(n) if 'Major Double Couple' in l and 'strike' in p.line[n+1]: np = p._double_couple(n) nodal_planes.nodal_plane_1 = NodalPlane(*np[0]) nodal_planes.nodal_plane_2 = NodalPlane(*np[1]) nodal_planes.preferred_plane = 1 if 'Spherical Coordinates' in l: mt = p._mt_sphere(n) moment_tensor.tensor = Tensor( m_rr = mt['Mrr'], m_tt = mt['Mtt'], m_pp = mt['Mff'], m_rt = mt['Mrt'], m_rp = mt['Mrf'], m_tp = mt['Mtf'], ) if 'Eigenvalues and eigenvectors of the Major Double Couple' in l: ax = p._vectors(n) principal_ax.t_axis = Axis(ax['T']['trend'], ax['T']['plunge'], ax['T']['ev']) principal_ax.p_axis = Axis(ax['P']['trend'], ax['P']['plunge'], ax['P']['ev']) principal_ax.n_axis = Axis(ax['N']['trend'], ax['N']['plunge'], ax['N']['ev']) if 'Number of Stations' in l: data_used.station_count = p._number_of_stations(n) if 'Maximum' in l and 'Gap' in l: focal_mech.azimuthal_gap = p._gap(n) if re.match(r'^Date', l): creation_info.creation_time = p._creation_time(n) # Creation Time creation_info.version = orid # Fill in magnitude values magnitude.evaluation_mode = ev_mode magnitude.evaluation_status = ev_stat magnitude.creation_info = creation_info.copy() magnitude.resource_id = self._rid(magnitude) # Stub origin origin.time = ev.get('time') origin.latitude = ev.get('lat') origin.longitude = ev.get('lon') origin.depth = derived_depth * 1000. origin.depth_type = "from moment tensor inversion" origin.creation_info = creation_info.copy() # Unique from true origin ID _oid = self._rid(origin) origin.resource_id = ResourceIdentifier(str(_oid) + '/mt') del _oid # Make an id for the MT that references this origin ogid = str(origin.resource_id) doid = ResourceIdentifier(ogid, referred_object=origin) # Make an id for the moment tensor mag which references this mag mrid = str(magnitude.resource_id) mmid = ResourceIdentifier(mrid, referred_object=magnitude) # MT todo: could check/use URL for RID if parsing the php file moment_tensor.evaluation_mode = ev_mode moment_tensor.evaluation_status = ev_stat moment_tensor.data_used = data_used moment_tensor.moment_magnitude_id = mmid moment_tensor.derived_origin_id = doid moment_tensor.creation_info = creation_info.copy() moment_tensor.resource_id = self._rid(moment_tensor) # Fill in focal_mech values focal_mech.nodal_planes = nodal_planes focal_mech.moment_tensor = moment_tensor focal_mech.principal_axes = principal_ax focal_mech.creation_info = creation_info.copy() focal_mech.resource_id = self._rid(focal_mech) # add mech and new magnitude to event event.focal_mechanisms = [focal_mech] event.magnitudes = [magnitude] event.origins = [origin] event.creation_info = creation_info.copy() # If an MT was done, that's the preferred mag/mech event.preferred_magnitude_id = str(magnitude.resource_id) event.preferred_focal_mechanism_id = str(focal_mech.resource_id) if evid: event.creation_info.version = evid event.resource_id = self._rid(event) self.event = 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 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: 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 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) 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', 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.30 * pre_lag_ccsum): msg = ('lag-calc has decreased cccsum from %f to %f - ' % (pre_lag_ccsum, checksum)) # warnings.warn(msg) 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_ndk(filename, *args, **kwargs): # @UnusedVariable """ Reads an NDK file to a :class:`~obspy.core.event.Catalog` object. :param filename: File or file-like object in text mode. """ # Read the whole file at once. While an iterator would be more efficient # the largest NDK file out in the wild is 13.7 MB so it does not matter # much. 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 Exception: try: data = filename.decode() except Exception: data = str(filename) data = data.strip() else: data = filename.read() if hasattr(data, "decode"): data = data.decode() # Create iterator that yields lines. def lines_iter(): prev_line = -1 while True: next_line = data.find("\n", prev_line + 1) if next_line < 0: break yield data[prev_line + 1:next_line] prev_line = next_line if len(data) > prev_line + 1: yield data[prev_line + 1:] # Use one Flinn Engdahl object for all region determinations. fe = FlinnEngdahl() cat = Catalog(resource_id=_get_resource_id("catalog", str(uuid.uuid4()))) # Loop over 5 lines at once. for _i, lines in enumerate(zip_longest(*[lines_iter()] * 5)): if None in lines: msg = "Skipped last %i lines. Not a multiple of 5 lines." % ( lines.count(None)) warnings.warn(msg, ObsPyNDKWarning) continue # Parse the lines to a human readable dictionary. try: record = _read_lines(*lines) except (ValueError, ObsPyNDKException): exc = traceback.format_exc() msg = ("Could not parse event %i (faulty file?). Will be " "skipped. Lines of the event:\n" "\t%s\n" "%s") % (_i + 1, "\n\t".join(lines), exc) warnings.warn(msg, ObsPyNDKWarning) continue # Use one creation info for essentially every item. creation_info = CreationInfo(agency_id="GCMT", version=record["version_code"]) # Use the ObsPy Flinn Engdahl region determiner as the region in the # NDK files is oftentimes trimmed. region = fe.get_region(record["centroid_longitude"], record["centroid_latitude"]) # Create an event object. event = Event(force_resource_id=False, event_type="earthquake", event_type_certainty="known", event_descriptions=[ EventDescription(text=region, type="Flinn-Engdahl region"), EventDescription(text=record["cmt_event_name"], type="earthquake name") ]) # Assemble the time for the reference origin. try: time = _parse_date_time(record["date"], record["time"]) except ObsPyNDKException: msg = ("Invalid time in event %i. '%s' and '%s' cannot be " "assembled to a valid time. Event will be skipped.") % \ (_i + 1, record["date"], record["time"]) warnings.warn(msg, ObsPyNDKWarning) continue # Create two origins, one with the reference latitude/longitude and # one with the centroidal values. ref_origin = Origin( force_resource_id=False, time=time, longitude=record["hypo_lng"], latitude=record["hypo_lat"], # Convert to m. depth=record["hypo_depth_in_km"] * 1000.0, origin_type="hypocenter", comments=[ Comment(text="Hypocenter catalog: %s" % record["hypocenter_reference_catalog"], force_resource_id=False) ]) ref_origin.comments[0].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="ref_origin") ref_origin.resource_id = _get_resource_id(record["cmt_event_name"], "origin", tag="reforigin") cmt_origin = Origin( force_resource_id=False, longitude=record["centroid_longitude"], longitude_errors={ "uncertainty": record["centroid_longitude_error"] }, latitude=record["centroid_latitude"], latitude_errors={"uncertainty": record["centroid_latitude_error"]}, # Convert to m. depth=record["centroid_depth_in_km"] * 1000.0, depth_errors={ "uncertainty": record["centroid_depth_in_km_error"] * 1000 }, time=ref_origin["time"] + record["centroid_time"], time_errors={"uncertainty": record["centroid_time_error"]}, depth_type=record["type_of_centroid_depth"], origin_type="centroid", time_fixed=False, epicenter_fixed=False, creation_info=creation_info.copy()) cmt_origin.resource_id = _get_resource_id(record["cmt_event_name"], "origin", tag="cmtorigin") event.origins = [ref_origin, cmt_origin] event.preferred_origin_id = cmt_origin.resource_id.id # Create the magnitude object. mag = Magnitude(force_resource_id=False, mag=round(record["Mw"], 2), magnitude_type="Mwc", origin_id=cmt_origin.resource_id, creation_info=creation_info.copy()) mag.resource_id = _get_resource_id(record["cmt_event_name"], "magnitude", tag="moment_mag") event.magnitudes = [mag] event.preferred_magnitude_id = mag.resource_id.id # Add the reported mb, MS magnitudes as additional magnitude objects. event.magnitudes.append( Magnitude( force_resource_id=False, mag=record["mb"], magnitude_type="mb", comments=[ Comment( force_resource_id=False, text="Reported magnitude in NDK file. Most likely 'mb'." ) ])) event.magnitudes[-1].comments[-1].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="mb_magnitude") event.magnitudes[-1].resource_id = _get_resource_id( record["cmt_event_name"], "magnitude", tag="mb") event.magnitudes.append( Magnitude( force_resource_id=False, mag=record["MS"], magnitude_type="MS", comments=[ Comment( force_resource_id=False, text="Reported magnitude in NDK file. Most likely 'MS'." ) ])) event.magnitudes[-1].comments[-1].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="MS_magnitude") event.magnitudes[-1].resource_id = _get_resource_id( record["cmt_event_name"], "magnitude", tag="MS") # Take care of the moment tensor. tensor = Tensor(m_rr=record["m_rr"], m_rr_errors={"uncertainty": record["m_rr_error"]}, m_pp=record["m_pp"], m_pp_errors={"uncertainty": record["m_pp_error"]}, m_tt=record["m_tt"], m_tt_errors={"uncertainty": record["m_tt_error"]}, m_rt=record["m_rt"], m_rt_errors={"uncertainty": record["m_rt_error"]}, m_rp=record["m_rp"], m_rp_errors={"uncertainty": record["m_rp_error"]}, m_tp=record["m_tp"], m_tp_errors={"uncertainty": record["m_tp_error"]}, creation_info=creation_info.copy()) mt = MomentTensor( force_resource_id=False, scalar_moment=record["scalar_moment"], tensor=tensor, data_used=[DataUsed(**i) for i in record["data_used"]], inversion_type=record["source_type"], source_time_function=SourceTimeFunction( type=record["moment_rate_type"], duration=record["moment_rate_duration"]), derived_origin_id=cmt_origin.resource_id, creation_info=creation_info.copy()) mt.resource_id = _get_resource_id(record["cmt_event_name"], "momenttensor") axis = [Axis(**i) for i in record["principal_axis"]] focmec = FocalMechanism( force_resource_id=False, moment_tensor=mt, principal_axes=PrincipalAxes( # The ordering is the same as for the IRIS SPUD service and # from a website of the Saint Louis University Earthquake # center so it should be correct. t_axis=axis[0], p_axis=axis[2], n_axis=axis[1]), nodal_planes=NodalPlanes( nodal_plane_1=NodalPlane(**record["nodal_plane_1"]), nodal_plane_2=NodalPlane(**record["nodal_plane_2"])), comments=[ Comment(force_resource_id=False, text="CMT Analysis Type: %s" % record["cmt_type"].capitalize()), Comment(force_resource_id=False, text="CMT Timestamp: %s" % record["cmt_timestamp"]) ], creation_info=creation_info.copy()) focmec.comments[0].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="cmt_type") focmec.comments[1].resource_id = _get_resource_id( record["cmt_event_name"], "comment", tag="cmt_timestamp") focmec.resource_id = _get_resource_id(record["cmt_event_name"], "focal_mechanism") event.focal_mechanisms = [focmec] event.preferred_focal_mechanism_id = focmec.resource_id.id # Set at end to avoid duplicate resource id warning. event.resource_id = _get_resource_id(record["cmt_event_name"], "event") cat.append(event) if len(cat) == 0: msg = "No valid events found in NDK file." raise ObsPyNDKException(msg) return cat
event=Event() picks=Pick() origin=Origin() arrival=Arrival() e_time=out_3[i][1::33] sta = out_3[i][2::33] cha = out_3[i][3::33] phase = out_3[i][4::33] pick_time = out_3[i][5::33] #phase pick time lat = out_3[i][6::33] lon = out_3[i][7::33] orid=out_3[i][8::33] dep = out_3[i][9::33] ml=out_3[i][10::33] for x, times in enumerate(pick_time): event.resource_id=str(out_3[i][0]) #assign evid rd = str(out_3[i][0]) picks = Pick(resource_id=rd, time=UTCDateTime(pick_time[x]), waveform_id = WaveformStreamID(network_code="CI", station_code=str(sta[x]), channel_code=str(cha[x])), phase_hint=str(phase[x])) origin = Origin(resource_id=(str(orid[x])), time = UTCDateTime(e_time[x]), longitude= str(lon[x]), latitude=str(lat[x]), depth=str(dep[x])) magnitude = Magnitude(mag = ml[x], magnitude_type = "M", origin_id = (str(orid[x]))) arrival = Arrival(pick_id = rd, phase = str(phase[x])) event.picks.append(picks) event.origins.append(origin) origin.arrivals.append(arrival) event.magnitudes.append(magnitude) db_catalog.append(event)
def detections_2_cat(detections, template_dict, stream, temp_prepick, max_lag, cc_thresh, extract_pre_pick=3.0, extract_post_pick=7.0, write_wav=False, debug=0): r"""Function to create a catalog from a list of detections, adjusting template pick \ times using cross correlation with data stream at the time of detection. :type detections: list of DETECTION objects :param detections: Detections which we want to extract and locate. :type template_dict: dict :param template_dict: Dictionary of template name: template stream for the entire \ catalog. Template names must be in the format found in the DETECTION objects. :type stream: obspy.Stream :param stream: stream encompassing time span of the detections. Will be used for pick \ refinement by cross correlation. Should be fed a stream processed in the same way \ as the streams in template dict (and in the same way that they were processed \ during matched filtering). The waveforms will not be processed here. :type write_wav: bool or str :param write_wav: If false, will not write detection waveforms to miniseed files. \ Otherwise, specify a directory to write the templates to. Will use name \ template_name_detection_time.mseed. :returns: :class: obspy.Catalog """ from obspy import UTCDateTime, Catalog, Stream from obspy.core.event import ResourceIdentifier, Event, Pick, CreationInfo, Comment, WaveformStreamID from obspy.signal.cross_correlation import xcorr from eqcorrscan.utils import plotting #XXX TODO Scripts havent been saving the actual detection objects so we cannot make #XXX TODO use of DETECTION.chans. Would be useful. # Copy stream out of the way st = stream.copy() # Create nested dictionary of delays template_name: stachan: delay # dict.items() works in both python 2 and 3 but is memory inefficient in 2 as both vars are # read into memory as lists delays = {} for name, temp in template_dict.items(): sorted_temp = temp.sort(['starttime']) stachans = [(tr.stats.station, tr.stats.channel, tr.stats.network) for tr in sorted_temp] mintime = sorted_temp[0].stats.starttime delays[name] = {(tr.stats.station, tr.stats.channel): tr.stats.starttime - mintime for tr in sorted_temp} # Loop over all detections, saving each as a new event in a catalog new_cat = Catalog() for detection in detections: if write_wav: new_stream = Stream() if hasattr(detection, 'event'): new_event = detection.event else: rid = ResourceIdentifier(id=detection.template_name + '_' +\ detection.detect_time.strftime('%Y%m%dT%H%M%S.%f'), prefix='smi:local') new_event = Event(resource_id=rid) cr_i = CreationInfo(author='EQcorrscan', creation_time=UTCDateTime()) new_event.creation_info = cr_i thresh_str = 'threshold=' + str(detection.threshold) ccc_str = 'detect_val=' + str(detection.detect_val) det_time_str = 'det_time=%s' % str(detection.detect_time) if detection.chans: used_chans = 'channels used: ' + \ ' '.join([str(pair) for pair in detection.chans]) new_event.comments.append(Comment(text=used_chans)) new_event.comments.append(Comment(text=thresh_str)) new_event.comments.append(Comment(text=ccc_str)) new_event.comments.append(Comment(text=det_time_str)) template = template_dict[detection.template_name] temp_len = template[0].stats.npts * template[0].stats.sampling_rate if template.sort(['starttime'])[0].stats.starttime == detection.detect_time: print('Template %s detected itself at %s.' % (detection.template_name, str(detection.detect_time))) new_event.resource_id = ResourceIdentifier(id=detection.template_name + '_self', prefix='smi:local') if debug >= 2: print('Plotting detection for template: %s' % detection.template_name) plt_st = Stream([st.select(station=tr.stats.station, channel=tr.stats.channel)[0].slice(detection.detect_time-extract_pre_pick, detection.detect_time+extract_post_pick) for tr in template if len(st.select(station=tr.stats.station, channel=tr.stats.channel)) > 0]) plotting.detection_multiplot(plt_st, template, [detection.detect_time.datetime]) # Loop over each trace in the template, correcting picks for new event if need be for tr in template: sta = tr.stats.station chan = tr.stats.channel if len(st.select(station=sta, channel=chan)) != 0: st_tr = st.select(station=sta, channel=chan)[0] else: print('No stream for %s: %s' % (sta, chan)) continue st_tr_pick = detection.detect_time + delays[detection.template_name][(sta, chan)] + temp_prepick i, absval, full_corr = xcorr(tr, st_tr.slice(st_tr_pick - temp_prepick, st_tr_pick - temp_prepick + temp_len), shift_len=max_lag, full_xcorr=True) ccval = max(full_corr) index = np.argmax(full_corr) - max_lag pk_str = 'ccval=' + str(ccval) if index == 0 or index == max_lag * 2: msg = 'Correlation correction at max_lag. Consider increasing max_lag.' warnings.warn(msg) if debug >= 3: print('Plotting full correlation function') print('index: %d' % index) print('max_ccval: %.2f' % ccval) plt.plot(full_corr) plt.show() plt.close() if ccval > cc_thresh: print('Threshold exceeded at %s: %s' % (sta, chan)) pick_tm = st_tr_pick + (index / tr.stats.sampling_rate) else: print('Correlation at %s: %s not good enough to correct pick' % (sta, chan)) pick_tm = st_tr_pick if tr.stats.channel[-1] in ['Z']: phase_hint = 'P' elif tr.stats.channel[-1] in ['N', 'E', '1', '2']: phase_hint = 'S' wv_id = WaveformStreamID(network_code=tr.stats.network, station_code=tr.stats.station, channel_code=tr.stats.channel) new_event.picks.append(Pick(time=pick_tm, waveform_id=wv_id, phase_hint=phase_hint, comments=[Comment(text=pk_str)])) if write_wav: new_stream.append(st_tr.slice(starttime=pick_tm - extract_pre_pick, endtime=pick_tm + extract_post_pick)) # Append to new catalog new_cat += new_event if write_wav: filename = '%s%s.mseed' % (write_wav, str(new_event.resource_id)) print('Writing new stream for detection to %s' % filename) new_stream.write(filename, format='MSEED') return new_cat