def __toStationMagnitude(parser, stat_mag_el): """ Parses a given station magnitude etree element. :type parser: :class:`~obspy.core.util.xmlwrapper.XMLParser` :param parser: Open XMLParser object. :type stat_mag_el: etree.element :param stat_mag_el: station magnitude element to be parsed. :return: A ObsPy :class:`~obspy.core.event.StationMagnitude` object. """ mag = StationMagnitude() mag.mag, mag.mag_errors = __toFloatQuantity(parser, stat_mag_el, "mag") # Use the waveform id to store station and channel(s) in the form # station.[channel_1, channel_2] or station.channel in the case only one # channel has been used. # XXX: This might be a violation of how this field is used within QuakeML channels = parser.xpath2obj('channels', stat_mag_el).split(',') channels = ','.join([_i.strip() for _i in channels]) if len(channels) > 1: channels = '%s' % channels station = parser.xpath2obj('station', stat_mag_el) mag.waveform_id = WaveformStreamID() mag.waveform_id.station_code = station mag.waveform_id.channel_code = channels weight_comment = Comment( text="Weight from the SeisHub event file: %.3f" % \ parser.xpath2obj("weight", stat_mag_el, float)) mag.comments.append(weight_comment) return mag
def __toStationMagnitude(parser, stat_mag_el): """ Parses a given station magnitude etree element. :type parser: :class:`~obspy.core.util.xmlwrapper.XMLParser` :param parser: Open XMLParser object. :type stat_mag_el: etree.element :param stat_mag_el: station magnitude element to be parsed. return: A ObsPy :class:`~obspy.core.event.StationMagnitude` object. """ global CURRENT_TYPE mag = StationMagnitude() mag.mag, mag.mag_errors = __toFloatQuantity(parser, stat_mag_el, "mag") mag.resource_id = ResourceIdentifier(prefix="/".join([RESOURCE_ROOT, "station_magnitude"])) # Use the waveform id to store station and channel(s) in the form # station.[channel_1, channel_2] or station.channel in the case only one # channel has been used. # XXX: This might be a violation of how this field is used within QuakeML channels = parser.xpath2obj('channels', stat_mag_el).split(',') channels = ','.join([_i.strip() for _i in channels]) if len(channels) > 1: channels = '%s' % channels station = fix_station_name(parser.xpath2obj('station', stat_mag_el)) location = parser.xpath2obj('location', stat_mag_el, str) or "" mag.waveform_id = WaveformStreamID() # Map some station names. if station in STATION_DICT: station = STATION_DICT[station] mag.waveform_id.station_code = station if CURRENT_TYPE == "obspyck": mag.method_id = "%s/station_magnitude_method/obspyck/1" % RESOURCE_ROOT network = parser.xpath2obj('network', stat_mag_el) if network is None: # network id is not stored in original stationMagnitude, try to find it # in a pick with same station name for waveform in parser.xpath("pick/waveform"): if waveform.attrib.get("stationCode") == station: network = waveform.attrib.get("networkCode") break if network is None: network = NETWORK_DICT[station] if network is None: print "AAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHHHH" raise Exception if "," not in channels: mag.waveform_id.channel_code = channels mag.waveform_id.network_code = network mag.waveform_id.location_code = location return mag
def __toStationMagnitude(parser, stat_mag_el): """ Parses a given station magnitude etree element. :type parser: :class:`~obspy.core.util.xmlwrapper.XMLParser` :param parser: Open XMLParser object. :type stat_mag_el: etree.element :param stat_mag_el: station magnitude element to be parsed. return: A ObsPy :class:`~obspy.core.event.StationMagnitude` object. """ global CURRENT_TYPE mag = StationMagnitude() mag.mag, mag.mag_errors = __toFloatQuantity(parser, stat_mag_el, "mag") mag.resource_id = ResourceIdentifier( prefix="/".join([RESOURCE_ROOT, "station_magnitude"])) # Use the waveform id to store station and channel(s) in the form # station.[channel_1, channel_2] or station.channel in the case only one # channel has been used. # XXX: This might be a violation of how this field is used within QuakeML channels = parser.xpath2obj('channels', stat_mag_el).split(',') channels = ','.join([_i.strip() for _i in channels]) if len(channels) > 1: channels = '%s' % channels station = fix_station_name(parser.xpath2obj('station', stat_mag_el)) location = parser.xpath2obj('location', stat_mag_el, str) or "" mag.waveform_id = WaveformStreamID() # Map some station names. if station in STATION_DICT: station = STATION_DICT[station] mag.waveform_id.station_code = station network = parser.xpath2obj('network', stat_mag_el) if network is None: # network id is not stored in original stationMagnitude, try to find it # in a pick with same station name for waveform in parser.xpath("pick/waveform"): if waveform.attrib.get("stationCode") == station: network = waveform.attrib.get("networkCode") break if network is None: network = NETWORK_DICT[station] if network is None: print "AAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHHHH" raise Exception if "," not in channels: mag.waveform_id.channel_code = channels mag.waveform_id.network_code = network mag.waveform_id.location_code = location return mag
def _mags(obj, evid, stamag=False, wid=None): mags = [] pm = None for magtype1, magtype2 in MAG_MAP.items(): magkey = 'mean ' * (not stamag) + 'magnitude ' + magtype1 if magkey in obj: magv = obj[magkey] if 'inf' in magv: warn('invalid value for magnitude: %s (event id %s)' % (magv, evid)) else: magv = float(magv) mag = (StationMagnitude(station_magnitude_type=magtype2, mag=magv, waveform_id=wid) if stamag else Magnitude(magnitude_type=magtype2, mag=magv)) mags.append(mag) if len(mags) == 1: pm = mags[0].resource_id return mags, pm
def _parseRecordM(self, line, event, pick): """ Parses the 'surface wave record' M """ #unused: Z_comp = line[7] Z_period = self._float(line[9:13]) # note: according to the format documentation, # column 20 should be blank. However, it seems that # Z_amplitude includes that column Z_amplitude = self._float(line[13:21]) # micrometers #TODO: N_comp and E_comp seems to be never there MSZ_mag = line[49:52] Ms_mag = self._float(line[53:56]) #unused: Ms_usage_flag = line[56] evid = event.resource_id.id.split('/')[-1] station_string =\ pick.waveform_id.getSEEDString()\ .replace(' ', '-').replace('.', '_').lower() amplitude = None if Z_amplitude is not None: amplitude = Amplitude() prefix = '/'.join((res_id_prefix, 'amp', evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = Z_amplitude * 1E-6 amplitude.unit = 'm' amplitude.period = Z_period amplitude.type = 'AS' amplitude.magnitude_hint = 'Ms' amplitude.pick_id = pick.resource_id event.amplitudes.append(amplitude) if MSZ_mag is not None: station_magnitude = StationMagnitude() prefix = '/'.join( (res_id_prefix, 'stationmagntiude', evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = event.origins[0].resource_id station_magnitude.mag = Ms_mag station_magnitude.station_magnitude_type = 'Ms' if amplitude is not None: station_magnitude.amplitude_id = amplitude.resource_id event.station_magnitudes.append(station_magnitude)
def _parse_record_m(self, line, event, pick): """ Parses the 'surface wave record' M """ # unused: Z_comp = line[7] z_period = self._float(line[9:13]) # note: according to the format documentation, # column 20 should be blank. However, it seems that # z_amplitude includes that column z_amplitude = self._float(line[13:21]) # micrometers # TODO: N_comp and E_comp seems to be never there msz_mag = line[49:52] ms_mag = self._float(line[53:56]) # unused: Ms_usage_flag = line[56] evid = event.resource_id.id.split('/')[-1] station_string = \ pick.waveform_id.get_seed_string()\ .replace(' ', '-').replace('.', '_').lower() amplitude = None if z_amplitude is not None: amplitude = Amplitude() prefix = '/'.join((res_id_prefix, 'amp', evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = z_amplitude * 1E-6 amplitude.unit = 'm' amplitude.period = z_period amplitude.type = 'AS' amplitude.magnitude_hint = 'Ms' amplitude.pick_id = pick.resource_id event.amplitudes.append(amplitude) if msz_mag is not None: station_magnitude = StationMagnitude() prefix = '/'.join((res_id_prefix, 'stationmagntiude', evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = event.origins[0].resource_id station_magnitude.mag = ms_mag station_magnitude.station_magnitude_type = 'Ms' if amplitude is not None: station_magnitude.amplitude_id = amplitude.resource_id event.station_magnitudes.append(station_magnitude)
def _parseRecordM(self, line, event, pick): """ Parses the 'surface wave record' M """ # unused: Z_comp = line[7] Z_period = self._float(line[9:13]) # note: according to the format documentation, # column 20 should be blank. However, it seems that # Z_amplitude includes that column Z_amplitude = self._float(line[13:21]) # micrometers # TODO: N_comp and E_comp seems to be never there MSZ_mag = line[49:52] Ms_mag = self._float(line[53:56]) # unused: Ms_usage_flag = line[56] evid = event.resource_id.id.split("/")[-1] station_string = pick.waveform_id.getSEEDString().replace(" ", "-").replace(".", "_").lower() amplitude = None if Z_amplitude is not None: amplitude = Amplitude() prefix = "/".join((res_id_prefix, "amp", evid, station_string)) amplitude.resource_id = ResourceIdentifier(prefix=prefix) amplitude.generic_amplitude = Z_amplitude * 1e-6 amplitude.unit = "m" amplitude.period = Z_period amplitude.type = "AS" amplitude.magnitude_hint = "Ms" amplitude.pick_id = pick.resource_id event.amplitudes.append(amplitude) if MSZ_mag is not None: station_magnitude = StationMagnitude() prefix = "/".join((res_id_prefix, "stationmagntiude", evid, station_string)) station_magnitude.resource_id = ResourceIdentifier(prefix=prefix) station_magnitude.origin_id = event.origins[0].resource_id station_magnitude.mag = Ms_mag station_magnitude.station_magnitude_type = "Ms" if amplitude is not None: station_magnitude.amplitude_id = amplitude.resource_id event.station_magnitudes.append(station_magnitude)
def _parseRecordP(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.getSEEDString()\ .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 _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] sta_mag.waveform_id = pick.waveform_id 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) # Associate station mag with network mag of same type mag = self._find_magnitude_by_type(event, origin_res_id, magnitude_types[i]) if mag: contrib = StationMagnitudeContribution() contrib.station_magnitude_id = sta_mag.resource_id contrib.weight = 1.0 mag.station_magnitude_contributions.append(contrib) except ValueError: pass
def _parse_phase(self, line): # since we can not identify which origin a phase line corresponds to, # we can not use any of the included information that would go in the # Arrival object, as that would have to be attached to the appropriate # origin.. # for now, just append all of these items as comments to the pick comments = [] # 1-5 a5 station code station_code = line[0:5].strip() # 7-12 f6.2 station-to-event distance (degrees) comments.append('station-to-event distance (degrees): "{}"'.format( line[6:12])) # 14-18 f5.1 event-to-station azimuth (degrees) comments.append('event-to-station azimuth (degrees): "{}"'.format( line[13:18])) # 20-27 a8 phase code phase_hint = line[19:27].strip() # 29-40 i2,a1,i2,a1,f6.3 arrival time (hh:mm:ss.sss) time = self._get_pick_time(line[28:40]) if time is None: msg = ('Could not determine absolute time of pick. This phase ' 'line will be ignored:\n{}').format(line) warnings.warn(msg) return None, None, None # 42-46 f5.1 time residual (seconds) comments.append('time residual (seconds): "{}"'.format(line[41:46])) # 48-52 f5.1 observed azimuth (degrees) comments.append('observed azimuth (degrees): "{}"'.format(line[47:52])) # 54-58 f5.1 azimuth residual (degrees) comments.append('azimuth residual (degrees): "{}"'.format(line[53:58])) # 60-65 f5.1 observed slowness (seconds/degree) comments.append('observed slowness (seconds/degree): "{}"'.format( line[59:65])) # 67-72 f5.1 slowness residual (seconds/degree) comments.append('slowness residual (seconds/degree): "{}"'.format( line[66:71])) # 74 a1 time defining flag (T or _) comments.append('time defining flag (T or _): "{}"'.format(line[73])) # 75 a1 azimuth defining flag (A or _) comments.append('azimuth defining flag (A or _): "{}"'.format( line[74])) # 76 a1 slowness defining flag (S or _) comments.append('slowness defining flag (S or _): "{}"'.format( line[75])) # 78-82 f5.1 signal-to-noise ratio comments.append('signal-to-noise ratio: "{}"'.format(line[77:82])) # 84-92 f9.1 amplitude (nanometers) amplitude = float_or_none(line[83:92]) # 94-98 f5.2 period (seconds) period = float_or_none(line[93:98]) # 100 a1 type of pick (a = automatic, m = manual) evaluation_mode = line[99] # 101 a1 direction of short period motion # (c = compression, d = dilatation, _= null) polarity = POLARITY[line[100].strip().lower()] # 102 a1 onset quality (i = impulsive, e = emergent, # q = questionable, _ = null) onset = ONSET[line[101].strip().lower()] # 104-108 a5 magnitude type (mb, Ms, ML, mbmle, msmle) magnitude_type = line[103:108].strip() # 109 a1 min max indicator (<, >, or blank) min_max_indicator = line[108] # 110-113 f4.1 magnitude value mag = float_or_none(line[109:113]) # 115-122 a8 arrival identification phase_id = line[114:122].strip() # process items waveform_id = WaveformStreamID(station_code=station_code) evaluation_mode = PICK_EVALUATION_MODE[evaluation_mode.strip().lower()] comments = [self._make_comment(', '.join(comments))] if phase_id: resource_id = self._construct_id(['pick'], add_hash=True) else: resource_id = self._construct_id(['pick', phase_id]) if mag: comment = ('min max indicator (<, >, or blank): ' + min_max_indicator) station_magnitude = StationMagnitude( mag=mag, magnitude_type=magnitude_type, resource_id=self._construct_id(['station_magnitude'], add_hash=True), comments=[self._make_comment(comment)]) # event init always sets an empty ResourceIdentifier, even when # specifying None, which is strange for key in ['origin_id', 'mag_errors']: setattr(station_magnitude, key, None) else: station_magnitude = None # assemble pick = Pick(phase_hint=phase_hint, time=time, waveform_id=waveform_id, evaluation_mode=evaluation_mode, comments=comments, polarity=polarity, onset=onset, resource_id=resource_id) # event init always sets an empty QuantityError, even when specifying # None, which is strange for key in ('time_errors', 'horizontal_slowness_errors', 'backazimuth_errors'): setattr(pick, key, None) if amplitude: amplitude /= 1e9 # convert from nanometers to meters amplitude = Amplitude(unit='m', generic_amplitude=amplitude, period=period) return pick, amplitude, station_magnitude
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 _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 relative_magnitudes(self, stream, pre_processed, process_cores=1, ignore_bad_data=False, parallel=False, min_cc=0.4, **kwargs): """ Compute relative magnitudes for the detections. Works in place on events in the Family :type stream: obspy.core.stream.Stream :param stream: All the data needed to cut from - can be a gappy Stream. :type pre_processed: bool :param pre_processed: Whether the stream has been pre-processed or not to match the templates. See note below. :param parallel: Turn parallel processing on or off. :type process_cores: int :param process_cores: Number of processes to use for pre-processing (if different to `cores`). :type ignore_bad_data: bool :param ignore_bad_data: If False (default), errors will be raised if data are excessively gappy or are mostly zeros. If True then no error will be raised, but an empty trace will be returned (and not used in detection). :type min_cc: float :param min_cc: Minimum correlation for magnitude to be computed. :param kwargs: Keyword arguments passed to `utils.mag_calc.relative_mags` .. Note:: Note on pre-processing: You can provide a pre-processed stream, which may be beneficial for detections over large time periods (the stream can have gaps, which reduces memory usage). However, in this case the processing steps are not checked, so you must ensure that the template in the Family has the same sampling rate and filtering as the stream. If pre-processing has not be done then the data will be processed according to the parameters in the template. """ processed_stream = self._process_streams( stream=stream, pre_processed=pre_processed, process_cores=process_cores, parallel=parallel, ignore_bad_data=ignore_bad_data) for detection in self.detections: event = detection.event if event is None: continue corr_dict = { p.waveform_id.get_seed_string(): float(p.comments[0].text.split("=")[-1]) for p in event.picks } template = self.template try: t_mag = (template.event.preferred_magnitude() or template.event.magnitudes[0]) except IndexError: Logger.info("No template magnitude, relative magnitudes cannot" " be computed for {0}".format(event.resource_id)) continue # Set the signal-window to be the template length signal_window = ( -template.prepick, min([tr.stats.npts * tr.stats.delta for tr in template.st]) - template.prepick) delta_mag = relative_magnitude(st1=template.st, st2=processed_stream, event1=template.event, event2=event, correlations=corr_dict, min_cc=min_cc, signal_window=signal_window, **kwargs) # Add station magnitudes sta_contrib = [] av_mag = 0.0 for seed_id, _delta_mag in delta_mag.items(): sta_mag = StationMagnitude( mag=t_mag.mag + _delta_mag, magnitude_type=t_mag.magnitude_type, method_id=ResourceIdentifier("relative"), waveform_id=WaveformStreamID(seed_string=seed_id), creation_info=CreationInfo(author="EQcorrscan", creation_time=UTCDateTime())) event.station_magnitudes.append(sta_mag) sta_contrib.append( StationMagnitudeContribution( station_magnitude_id=sta_mag.resource_id, weight=1.)) av_mag += sta_mag.mag if len(delta_mag) > 0: av_mag /= len(delta_mag) # Compute average magnitude event.magnitudes.append( Magnitude(mag=av_mag, magnitude_type=t_mag.magnitude_type, method_id=ResourceIdentifier("relative"), station_count=len(delta_mag), evaluation_mode="manual", station_magnitude_contributions=sta_contrib, creation_info=CreationInfo( author="EQcorrscan", creation_time=UTCDateTime()))) return self.catalog
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 write_qml(config, sourcepar): if not config.options.qml_file: return qml_file = config.options.qml_file cat = read_events(qml_file) evid = config.hypo.evid try: ev = [e for e in cat if evid in str(e.resource_id)][0] except Exception: logging.warning('Unable to find evid "{}" in QuakeML file. ' 'QuakeML output will not be written.'.format(evid)) origin = ev.preferred_origin() if origin is None: origin = ev.origins[0] origin_id = origin.resource_id origin_id_strip = origin_id.id.split('/')[-1] origin_id_strip = origin_id_strip.replace(config.smi_strip_from_origin_id, '') # Common parameters ssp_version = get_versions()['version'] method_id = config.smi_base + '/sourcespec/' + ssp_version cr_info = CreationInfo() cr_info.agency_id = config.agency_id if config.author is None: author = '{}@{}'.format(getuser(), gethostname()) else: author = config.author cr_info.author = author cr_info.creation_time = UTCDateTime() means = sourcepar.means_weight errors = sourcepar.errors_weight stationpar = sourcepar.station_parameters # Magnitude mag = Magnitude() _id = config.smi_magnitude_template.replace('$SMI_BASE', config.smi_base) _id = _id.replace('$ORIGIN_ID', origin_id_strip) mag.resource_id = ResourceIdentifier(id=_id) mag.method_id = ResourceIdentifier(id=method_id) mag.origin_id = origin_id mag.magnitude_type = 'Mw' mag.mag = means['Mw'] mag_err = QuantityError() mag_err.uncertainty = errors['Mw'] mag_err.confidence_level = 68.2 mag.mag_errors = mag_err mag.station_count = len([_s for _s in stationpar.keys()]) mag.evaluation_mode = 'automatic' mag.creation_info = cr_info # Seismic moment -- It has to be stored in a MomentTensor object # which, in turn, is part of a FocalMechanism object mt = MomentTensor() _id = config.smi_moment_tensor_template.replace('$SMI_BASE', config.smi_base) _id = _id.replace('$ORIGIN_ID', origin_id_strip) mt.resource_id = ResourceIdentifier(id=_id) mt.derived_origin_id = origin_id mt.moment_magnitude_id = mag.resource_id mt.scalar_moment = means['Mo'] mt_err = QuantityError() mt_err.lower_uncertainty = errors['Mo'][0] mt_err.upper_uncertainty = errors['Mo'][1] mt_err.confidence_level = 68.2 mt.scalar_moment_errors = mt_err mt.method_id = method_id mt.creation_info = cr_info # And here is the FocalMechanism object fm = FocalMechanism() _id = config.smi_focal_mechanism_template.replace('$SMI_BASE', config.smi_base) _id = _id.replace('$ORIGIN_ID', origin_id_strip) fm.resource_id = ResourceIdentifier(id=_id) fm.triggering_origin_id = origin_id fm.method_id = ResourceIdentifier(id=method_id) fm.moment_tensor = mt fm.creation_info = cr_info ev.focal_mechanisms.append(fm) # Station magnitudes for statId in sorted(stationpar.keys()): par = stationpar[statId] st_mag = StationMagnitude() seed_id = statId.split()[0] _id = config.smi_station_magnitude_template.replace( '$SMI_MAGNITUDE_TEMPLATE', config.smi_magnitude_template) _id = _id.replace('$ORIGIN_ID', origin_id_strip) _id = _id.replace('$SMI_BASE', config.smi_base) _id = _id.replace('$WAVEFORM_ID', seed_id) st_mag.resource_id = ResourceIdentifier(id=_id) st_mag.origin_id = origin_id st_mag.mag = par['Mw'] st_mag.station_magnitude_type = 'Mw' st_mag.method_id = mag.method_id st_mag.creation_info = cr_info st_mag.waveform_id = WaveformStreamID(seed_string=seed_id) st_mag.extra = SSPExtra() st_mag.extra.moment = SSPTag(par['Mo']) st_mag.extra.corner_frequency = SSPTag(par['fc']) st_mag.extra.t_star = SSPTag(par['t_star']) ev.station_magnitudes.append(st_mag) st_mag_contrib = StationMagnitudeContribution() st_mag_contrib.station_magnitude_id = st_mag.resource_id mag.station_magnitude_contributions.append(st_mag_contrib) ev.magnitudes.append(mag) # Write other average parameters as custom tags ev.extra = SSPExtra() ev.extra.corner_frequency = SSPContainerTag() ev.extra.corner_frequency.value.value = SSPTag(means['fc']) ev.extra.corner_frequency.value.lower_uncertainty =\ SSPTag(errors['fc'][0]) ev.extra.corner_frequency.value.upper_uncertainty =\ SSPTag(errors['fc'][1]) ev.extra.corner_frequency.value.confidence_level = SSPTag(68.2) ev.extra.t_star = SSPContainerTag() ev.extra.t_star.value.value = SSPTag(means['t_star']) ev.extra.t_star.value.uncertainty = SSPTag(errors['t_star']) ev.extra.t_star.value.confidence_level = SSPTag(68.2) ev.extra.source_radius = SSPContainerTag() ev.extra.source_radius.value.value = SSPTag(means['ra']) ev.extra.source_radius.value.lower_uncertainty =\ SSPTag(errors['ra'][0]) ev.extra.source_radius.value.upper_uncertainty =\ SSPTag(errors['ra'][1]) ev.extra.source_radius.value.confidence_level = SSPTag(68.2) ev.extra.stress_drop = SSPContainerTag() ev.extra.stress_drop.value.value = SSPTag(means['bsd']) ev.extra.stress_drop.value.lower_uncertainty =\ SSPTag(errors['bsd'][0]) ev.extra.stress_drop.value.upper_uncertainty =\ SSPTag(errors['bsd'][1]) ev.extra.stress_drop.value.confidence_level = SSPTag(68.2) if config.set_preferred_magnitude: ev.preferred_magnitude_id = mag.resource_id.id qml_file_out = os.path.join(config.options.outdir, evid + '.xml') ev.write(qml_file_out, format='QUAKEML') logging.info('QuakeML file written to: ' + qml_file_out)