def read_nsmn(filename, **kwargs): """Read the Turkish NSMN strong motion data format. Args: filename (str): path to NSMN data file. kwargs (ref): Other arguments will be ignored. Returns: list: Sequence of one StationStream object containing 3 StationTrace objects. """ header = _read_header(filename) header1 = copy.deepcopy(header) header2 = copy.deepcopy(header) header3 = copy.deepcopy(header) header1['standard']['horizontal_orientation'] = 0.0 header1['standard']['vertical_orientation'] = np.nan header1['channel'] = get_channel_name(header['sampling_rate'], True, False, True) header1['standard']['units_type'] = get_units_type(header1['channel']) header2['standard']['horizontal_orientation'] = 90.0 header2['standard']['vertical_orientation'] = np.nan header2['channel'] = get_channel_name(header['sampling_rate'], True, False, False) header2['standard']['units_type'] = get_units_type(header2['channel']) header3['standard']['horizontal_orientation'] = 0.0 header3['standard']['vertical_orientation'] = np.nan header3['channel'] = get_channel_name(header['sampling_rate'], True, True, False) header3['standard']['units_type'] = get_units_type(header3['channel']) # three columns of NS, EW, UD # data = np.genfromtxt(filename, skip_header=TEXT_HDR_ROWS, # delimiter=[COLWIDTH] * NCOLS, encoding=ENCODING) data = np.loadtxt(filename, skiprows=TEXT_HDR_ROWS, encoding=ENCODING) data1 = data[:, 0] data2 = data[:, 1] data3 = data[:, 2] trace1 = StationTrace(data=data1, header=header1) response = {'input_units': 'counts', 'output_units': 'cm/s^2'} trace1.setProvenance('remove_response', response) trace2 = StationTrace(data=data2, header=header2) trace2.setProvenance('remove_response', response) trace3 = StationTrace(data=data3, header=header3) trace3.setProvenance('remove_response', response) stream = StationStream(traces=[trace1, trace2, trace3]) return [stream]
def read_nsmn(filename, config=None): """Read the Turkish NSMN strong motion data format. Args: filename (str): path to NSMN data file. config (dict): Dictionary containing configuration. Returns: list: Sequence of one StationStream object containing 3 StationTrace objects. """ header = _read_header(filename) header1 = copy.deepcopy(header) header2 = copy.deepcopy(header) header3 = copy.deepcopy(header) header1["standard"]["horizontal_orientation"] = 0.0 header1["standard"]["vertical_orientation"] = np.nan header1["channel"] = get_channel_name(header["sampling_rate"], True, False, True) header1["standard"]["units_type"] = get_units_type(header1["channel"]) header2["standard"]["horizontal_orientation"] = 90.0 header2["standard"]["vertical_orientation"] = np.nan header2["channel"] = get_channel_name(header["sampling_rate"], True, False, False) header2["standard"]["units_type"] = get_units_type(header2["channel"]) header3["standard"]["horizontal_orientation"] = 0.0 header3["standard"]["vertical_orientation"] = np.nan header3["channel"] = get_channel_name(header["sampling_rate"], True, True, False) header3["standard"]["units_type"] = get_units_type(header3["channel"]) # three columns of NS, EW, UD # data = np.genfromtxt(filename, skip_header=TEXT_HDR_ROWS, # delimiter=[COLWIDTH] * NCOLS, encoding=ENCODING) data = np.loadtxt(filename, skiprows=TEXT_HDR_ROWS, encoding=ENCODING) data1 = data[:, 0] data2 = data[:, 1] data3 = data[:, 2] trace1 = StationTrace(data=data1, header=header1) response = {"input_units": "counts", "output_units": "cm/s^2"} trace1.setProvenance("remove_response", response) trace2 = StationTrace(data=data2, header=header2) trace2.setProvenance("remove_response", response) trace3 = StationTrace(data=data3, header=header3) trace3.setProvenance("remove_response", response) stream = StationStream(traces=[trace1, trace2, trace3]) return [stream]
def _stats_from_header(header, config): if "_format" in header and header._format.lower() == "sac": # The plan is to add separate if blocks to support the different # formats as we encounter them here. See the SAC header documentation # here: # http://ds.iris.edu/files/sac-manual/manual/file_format.html # Todo: add support for SAC with PZ file. coords = { "latitude": header["sac"]["stla"], "longitude": header["sac"]["stlo"], "elevation": header["sac"]["stel"], } standard = {} standard["corner_frequency"] = np.nan standard["instrument_damping"] = np.nan standard["instrument_period"] = np.nan standard["structure_type"] = "" standard["process_time"] = "" standard["process_level"] = "uncorrected physical units" standard["source"] = config["read"]["sac_source"] standard["source_file"] = "" standard["instrument"] = "" standard["sensor_serial_number"] = "" standard["horizontal_orientation"] = float(header["sac"]["cmpaz"]) # Note: vertical orientatin is defined here as angle from horizontal standard["vertical_orientation"] = 90.0 - float( header["sac"]["cmpinc"]) if "units_type" not in standard.keys() or standard["units_type"] == "": utype = get_units_type(header["channel"]) standard["units_type"] = utype standard["units"] = UNITS[utype] print(f"Stationtrace.py line 844: {standard['units_type']}") standard["comments"] = "" standard["station_name"] = "" standard["station_name"] = header["station"] format_specific = { "conversion_factor": float(config["read"]["sac_conversion_factor"]) } standard["source_format"] = header._format standard["instrument_sensitivity"] = np.nan standard["volts_to_counts"] = np.nan response = None else: raise Exception("Format unsuppored without StationXML file.") return (response, standard, coords, format_specific)
def _stats_from_header(header, config): if '_format' in header and header._format.lower() == 'sac': # The plan is to add separate if blocks to support the different # formats as we encounter them here. See the SAC header documentation # here: # http://ds.iris.edu/files/sac-manual/manual/file_format.html # Todo: add support for SAC with PZ file. coords = { 'latitude': header['sac']['stla'], 'longitude': header['sac']['stlo'], 'elevation': header['sac']['stel'] } standard = {} standard['corner_frequency'] = np.nan standard['instrument_damping'] = np.nan standard['instrument_period'] = np.nan standard['structure_type'] = '' standard['process_time'] = '' standard['process_level'] = 'uncorrected physical units' standard['source'] = config['read']['sac_source'] standard['source_file'] = '' standard['instrument'] = '' standard['sensor_serial_number'] = '' standard['instrument'] = '' standard['sensor_serial_number'] = '' standard['horizontal_orientation'] = float(header['sac']['cmpaz']) # Note: vertical orientatin is defined here as angle from horizontal standard['vertical_orientation'] = 90.0 - \ float(header['sac']['cmpinc']) utype = get_units_type(header['channel']) standard['units_type'] = utype standard['units'] = UNITS[utype] standard['comments'] = '' standard['station_name'] = '' standard['station_name'] = header['station'] format_specific = { 'conversion_factor': float(config['read']['sac_conversion_factor']) } standard['source_format'] = header._format standard['instrument_sensitivity'] = np.nan response = None else: raise Exception('Format unsuppored without StationXML file.') return (response, standard, coords, format_specific)
def read_cwb(filename, **kwargs): """Read Taiwan Central Weather Bureau strong motion file. Args: filename (str): Path to possible CWB data file. kwargs (ref): Other arguments will be ignored. Returns: Stream: Obspy Stream containing three channels of acceleration data (cm/s**2). """ logging.debug("Starting read_cwb.") if not is_cwb(filename): raise Exception('%s is not a valid CWB strong motion data file.' % filename) f = open(filename, 'rt') # according to the powers that defined the Network.Station.Channel.Location # "standard", Location is a two character field. Most data providers, # including CWB here, don't provide this. We'll flag it as "--". data = np.genfromtxt(filename, skip_header=HDR_ROWS, delimiter=[COLWIDTH] * NCOLS) # time, Z, NS, EW hdr = _get_header_info(f, data) f.close() head, tail = os.path.split(filename) hdr['standard']['source_file'] = tail or os.path.basename(head) hdr_z = hdr.copy() hdr_z['channel'] = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=True, is_north=False) hdr_z['standard']['horizontal_orientation'] = np.nan hdr_z['standard']['vertical_orientation'] = np.nan hdr_z['standard']['units_type'] = get_units_type(hdr_z['channel']) hdr_h1 = hdr.copy() hdr_h1['channel'] = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=True) hdr_h1['standard']['horizontal_orientation'] = np.nan hdr_h1['standard']['vertical_orientation'] = np.nan hdr_h1['standard']['units_type'] = get_units_type(hdr_h1['channel']) hdr_h2 = hdr.copy() hdr_h2['channel'] = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=False) hdr_h2['standard']['horizontal_orientation'] = np.nan hdr_h2['standard']['vertical_orientation'] = np.nan hdr_h2['standard']['units_type'] = get_units_type(hdr_h2['channel']) stats_z = Stats(hdr_z) stats_h1 = Stats(hdr_h1) stats_h2 = Stats(hdr_h2) response = {'input_units': 'counts', 'output_units': 'cm/s^2'} trace_z = StationTrace(data=data[:, 1], header=stats_z) trace_z.setProvenance('remove_response', response) trace_h1 = StationTrace(data=data[:, 2], header=stats_h1) trace_h1.setProvenance('remove_response', response) trace_h2 = StationTrace(data=data[:, 3], header=stats_h2) trace_h2.setProvenance('remove_response', response) stream = StationStream([trace_z, trace_h1, trace_h2]) return [stream]
def _stats_from_inventory(data, inventory, channelid): if len(inventory.source): if (inventory.sender is not None and inventory.sender != inventory.source): source = '%s,%s' % (inventory.source, inventory.sender) else: source = inventory.source # Due to pyasdf strict station merging criteria, we might actually have # to search for the correct station that contains the current channelid for sta in inventory.networks[0].stations: if channelid in [ cha.split('.')[-1] for cha in sta.get_contents()['channels'] ]: station = sta coords = { 'latitude': station.latitude, 'longitude': station.longitude, 'elevation': station.elevation } channel_names = [ch.code for ch in station.channels] channelidx = channel_names.index(channelid) channel = station.channels[channelidx] standard = {} # things we'll never get from an inventory object standard['corner_frequency'] = np.nan standard['instrument_damping'] = np.nan standard['instrument_period'] = np.nan standard['structure_type'] = '' standard['process_time'] = '' if data.dtype in INT_TYPES: standard['process_level'] = 'raw counts' else: standard['process_level'] = 'uncorrected physical units' standard['source'] = source standard['source_file'] = '' standard['instrument'] = '' standard['sensor_serial_number'] = '' if channel.sensor is not None: standard['instrument'] = ( '%s %s %s %s' % (channel.sensor.type, channel.sensor.manufacturer, channel.sensor.model, channel.sensor.description)) if channel.sensor.serial_number is not None: standard['sensor_serial_number'] = channel.sensor.serial_number else: standard['sensor_serial_number'] = '' if channel.azimuth is not None: standard['horizontal_orientation'] = channel.azimuth if channel.dip is not None: # Note: vertical orientatin is defined here as angle from horizontal standard['vertical_orientation'] = channel.dip else: standard['vertical_orientation'] = np.nan standard['units_type'] = get_units_type(channelid) if len(channel.comments): comments = ' '.join(channel.comments[i].value for i in range(len(channel.comments))) standard['comments'] = comments else: standard['comments'] = '' standard['station_name'] = '' if station.site.name != 'None': standard['station_name'] = station.site.name # extract the remaining standard info and format_specific info # from a JSON string in the station description. format_specific = {} if station.description is not None and station.description != 'None': jsonstr = station.description try: big_dict = json.loads(jsonstr) standard.update(big_dict['standard']) format_specific = big_dict['format_specific'] except json.decoder.JSONDecodeError: format_specific['description'] = jsonstr if 'source_format' not in standard or standard['source_format'] is None: standard['source_format'] = 'fdsn' standard['instrument_sensitivity'] = np.nan response = None if channel.response is not None: response = channel.response if hasattr(response, 'instrument_sensitivity'): units = response.instrument_sensitivity.input_units if '/' in units: num, denom = units.split('/') if num.lower() not in LENGTH_CONVERSIONS: raise KeyError( 'Sensitivity input units of %s are not supported.' % units) conversion = LENGTH_CONVERSIONS[num.lower()] sensitivity = response.instrument_sensitivity.value * \ conversion response.instrument_sensitivity.value = sensitivity standard['instrument_sensitivity'] = sensitivity else: standard['instrument_sensitivity'] = \ response.instrument_sensitivity.value return (response, standard, coords, format_specific)
def _read_header_lines(filename, offset): """Read the header lines for each channel. Args: filename (str): Input BHRC file name. offset (int): Number of lines to skip from the beginning of the file. Returns: tuple: (header dictionary containing Stats dictionary with extra sub-dicts, updated offset rows) """ with open(filename, 'rt') as f: for _ in range(offset): next(f) lines = [next(f) for x in range(TEXT_HDR_ROWS)] offset += TEXT_HDR_ROWS header = {} standard = {} coords = {} format_specific = {} # get the sensor azimuth with respect to the earthquake # this data has been rotated so that the longitudinal channel (L) # is oriented at the sensor azimuth, and the transverse (T) is # 90 degrees off from that. station_info = lines[7][lines[7].index('Station'):] (lat_str, lon_str, alt_str, lstr, tstr) = re.findall(FLOATRE, station_info) component = lines[4].strip() if component == 'V': angle = np.nan elif component == 'L': angle = float(lstr) else: angle = float(tstr) coords = {'latitude': float(lat_str), 'longitude': float(lon_str), 'elevation': float(alt_str)} # fill out the standard dictionary standard['source'] = SOURCE standard['source_format'] = SOURCE_FORMAT standard['instrument'] = lines[1].split('=')[1].strip() standard['sensor_serial_number'] = '' volstr = lines[0].split()[1].strip() if volstr not in LEVELS: raise KeyError('Volume %s files are not supported.' % volstr) standard['process_level'] = PROCESS_LEVELS[LEVELS[volstr]] standard['process_time'] = '' station_name = lines[7][0:lines[7].index('Station')].strip() standard['station_name'] = station_name standard['structure_type'] = '' standard['corner_frequency'] = np.nan standard['units'] = 'acc' period_str, damping_str = re.findall(FLOATRE, lines[9]) standard['instrument_period'] = float(period_str) standard['instrument_damping'] = float(damping_str) standard['horizontal_orientation'] = angle standard['comments'] = '' head, tail = os.path.split(filename) standard['source_file'] = tail or os.path.basename(head) # this field can be used for instrument correction # when data is in counts standard['instrument_sensitivity'] = np.nan # fill out the stats stuff # we don't know the start of the trace header['starttime'] = UTCDateTime(1970, 1, 1) npts_str, dur_str = re.findall(FLOATRE, lines[10]) header['npts'] = int(npts_str) header['duration'] = float(dur_str) header['delta'] = header['duration'] / (header['npts'] - 1) header['sampling_rate'] = 1 / header['delta'] if np.isnan(angle): header['channel'] = get_channel_name( header['sampling_rate'], is_acceleration=True, is_vertical=True, is_north=False) elif (angle > 315 or angle < 45) or (angle > 135 and angle < 225): header['channel'] = get_channel_name( header['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=True) else: header['channel'] = get_channel_name( header['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=False) standard['units_type'] = get_units_type(header['channel']) part1 = lines[0].split(':')[1] stationcode = part1.split('/')[0].strip() header['station'] = stationcode header['location'] = '--' header['network'] = NETWORK header['coordinates'] = coords header['standard'] = standard header['format_specific'] = format_specific offset += INT_HDR_ROWS offset += FLOAT_HDR_ROWS return (header, offset)
def _get_header_info(int_data, flt_data, lines, level, location=''): """Return stats structure from various headers. Output is a dictionary like this: - network (str): Default is '--' (unknown). Determined using COSMOS_NETWORKS - station (str) - channel (str) - location (str): Default is '--' - starttime (datetime) - sampling_rate (float) - delta (float) - coordinates: - latitude (float) - longitude (float) - elevation (float): Default is np.nan - standard (Defaults are either np.nan or '') - horizontal_orientation (float): Rotation from north (degrees) - instrument_period (float): Period of sensor (Hz) - instrument_damping (float): Fraction of critical - process_time (datetime): Reported date of processing - process_level: Either 'V0', 'V1', 'V2', or 'V3' - station_name (str): Long form station description - sensor_serial_number (str): Reported sensor serial - instrument (str) - comments (str): Processing comments - structure_type (str) - corner_frequency (float): Sensor corner frequency (Hz) - units (str) - source (str): Network source description - source_format (str): Always dmg - format_specific - sensor_sensitivity (float): Transducer sensitivity (cm/g) - time_sd (float): Standard deviaiton of time steop (millisecond) - fractional_unit (float): Units of digitized acceleration in file (fractions of g) - scaling_factor (float): Scaling used for converting acceleration from g/10 to cm/sec/sec - low_filter_corner (float): Filter corner for low frequency V2 filtering (Hz) - high_filter_corner (float): Filter corner for high frequency V2 filtering (Hz) Args: int_data (ndarray): Array of integer data flt_data (ndarray): Array of float data lines (list): List of text headers (str) level (str): Process level code (V0, V1, V2, V3) Returns: dictionary: Dictionary of header/metadata information """ hdr = {} coordinates = {} standard = {} format_specific = {} # Required metadata name_length = int_data[29] station_name = re.sub(' +', ' ', lines[6][:name_length]).strip() code = re.sub(' +', ' ', lines[1][name_length:]).strip().split(' ')[-1][:2] if code.upper() in CODES: network = code.upper() idx = np.argwhere(CODES == network)[0][0] source = SOURCES1[idx].decode( 'utf-8') + ', ' + SOURCES2[idx].decode('utf-8') else: network = '--' source = 'unknown' hdr['network'] = network station_line = lines[5] station = station_line[12:17].strip() hdr['station'] = station angle = int_data[26] hdr['delta'] = flt_data[60] hdr['sampling_rate'] = 1 / hdr['delta'] hdr['channel'] = _get_channel(angle, hdr['sampling_rate']) # this format uses codes of 500/600 in this angle to indicate a vertical # channel Obspy freaks out with azimuth values > 360, so let's just say # horizontal angle is zero in these cases if hdr['channel'].endswith('Z'): angle = '0.0' if location == '': hdr['location'] = '--' else: hdr['location'] = location # parse the trigger time try: trigger_time = _get_date(lines[4]) + _get_time(lines[4]) # sometimes these trigger times are in UTC, other times a different # time zone. Figure out if this is the case and modify start time # accordingly # look for three letter string that might be a time zone if 'PDT' in lines[3] or 'PST' in lines[3]: timezone = pytz.timezone('US/Pacific') utcoffset = timezone.utcoffset(trigger_time) # subtracting because we're going from pacific to utc trigger_time -= utcoffset hdr['starttime'] = trigger_time except BaseException: logging.warning('No start time provided on trigger line. ' 'This must be set manually for network/station: ' '%s/%s.' % (hdr['network'], hdr['station'])) standard['comments'] = 'Missing start time.' hdr['npts'] = int_data[52] # Coordinates latitude_str = station_line[20:27].strip() longitude_str = station_line[29:37].strip() latitude, longitude = _get_coords(latitude_str, longitude_str) coordinates['latitude'] = latitude coordinates['longitude'] = longitude logging.warning('Setting elevation to 0.0') coordinates['elevation'] = 0.0 # Standard metadata standard['units_type'] = get_units_type(hdr['channel']) standard['horizontal_orientation'] = float(angle) standard['vertical_orientation'] = np.nan standard['instrument_period'] = flt_data[0] standard['instrument_damping'] = flt_data[1] standard['process_time'] = _get_date(lines[1]).strftime(TIMEFMT) standard['process_level'] = PROCESS_LEVELS[level] if 'comments' not in standard: standard['comments'] = '' standard['structure_type'] = lines[7][46:80].strip() standard['instrument'] = station_line[39:47].strip() standard['sensor_serial_number'] = station_line[53:57].strip() standard['corner_frequency'] = np.nan standard['units'] = 'acc' standard['source'] = source standard['source_format'] = 'dmg' standard['station_name'] = station_name # this field can be used for instrument correction # when data is in counts standard['instrument_sensitivity'] = np.nan # Format specific metadata format_specific['fractional_unit'] = flt_data[4] format_specific['sensor_sensitivity'] = flt_data[5] if flt_data[13] == -999: format_specific['time_sd'] = np.nan else: format_specific['time_sd'] = flt_data[13] format_specific['scaling_factor'] = flt_data[51] format_specific['low_filter_corner'] = flt_data[61] format_specific['high_filter_corner'] = flt_data[72] # Set dictionary hdr['coordinates'] = coordinates hdr['standard'] = standard hdr['format_specific'] = format_specific return hdr
def _get_header_info(int_data, flt_data, lines, volume, location=''): """Return stats structure from various headers. Output is a dictionary like this: - network (str): 'LA' - station (str) - channel (str): Determined using COSMOS_ORIENTATIONS - location (str): Default is '--' - starttime (datetime) - duration (float) - sampling_rate (float) - npts (int) - coordinates: - latitude (float) - longitude (float) - elevation (float) - standard (Defaults are either np.nan or '') - horizontal_orientation (float): Rotation from north (degrees) - instrument_period (float): Period of sensor (Hz) - instrument_damping (float): Fraction of critical - process_time (datetime): Reported date of processing - process_level: Either 'V0', 'V1', 'V2', or 'V3' - station_name (str): Long form station description - sensor_serial_number (str): Reported sensor serial - instrument (str): See SENSOR_TYPES - comments (str): Processing comments - structure_type (str): See BUILDING_TYPES - corner_frequency (float): Sensor corner frequency (Hz) - units (str): See UNITS - source (str): Network source description - source_format (str): Always cosmos - format_specific - fractional_unit (float): Units of digitized acceleration in file (fractions of g) Args: int_data (ndarray): Array of integer data flt_data (ndarray): Array of float data lines (list): List of text headers (str) Returns: dictionary: Dictionary of header/metadata information """ hdr = {} coordinates = {} standard = {} format_specific = {} if volume == 'V1': hdr['duration'] = flt_data[2] hdr['npts'] = int_data[27] hdr['sampling_rate'] = (hdr['npts'] - 1) / hdr['duration'] # Get required parameter number hdr['network'] = 'LA' hdr['station'] = str(int_data[8]) logging.debug('station: %s' % hdr['station']) horizontal_angle = int_data[26] logging.debug('horizontal: %s' % horizontal_angle) if (horizontal_angle in USC_ORIENTATIONS or (horizontal_angle >= 0 and horizontal_angle <= 360)): if horizontal_angle in USC_ORIENTATIONS: channel = USC_ORIENTATIONS[horizontal_angle][1].upper() if channel == 'UP' or channel == 'DOWN' or channel == 'VERT': channel = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=True, is_north=False) horizontal_angle = 0.0 elif (horizontal_angle > 315 or horizontal_angle < 45 or (horizontal_angle > 135 and horizontal_angle < 225)): channel = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=True) else: channel = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=False) horizontal_orientation = horizontal_angle hdr['channel'] = channel logging.debug('channel: %s' % hdr['channel']) else: errstr = ('USC: Not enough information to distinguish horizontal ' 'from vertical channels.') raise BaseException(errstr) if location == '': hdr['location'] = '--' else: hdr['location'] = location month = str(int_data[21]) day = str(int_data[22]) year = str(int_data[23]) time = str(int_data[24]) tstr = month + '/' + day + '/' + year + '_' + time starttime = datetime.strptime(tstr, '%m/%d/%Y_%H%M') hdr['starttime'] = starttime # Get coordinates lat_deg = int_data[9] lat_min = int_data[10] lat_sec = int_data[11] lon_deg = int_data[12] lon_min = int_data[13] lon_sec = int_data[14] # Check for southern hemisphere, default is northern if lines[4].find('STATION USC#') >= 0: idx = lines[4].find('STATION USC#') + 12 if 'S' in lines[4][idx:]: lat_sign = -1 else: lat_sign = 1 else: lat_sign = 1 # Check for western hemisphere, default is western if lines[4].find('STATION USC#') >= 0: idx = lines[4].find('STATION USC#') + 12 if 'W' in lines[4][idx:]: lon_sign = -1 else: lon_sign = 1 else: lon_sign = -1 latitude = lat_sign * _dms2dd(lat_deg, lat_min, lat_sec) longitude = lon_sign * _dms2dd(lon_deg, lon_min, lon_sec) # Since sometimes longitudes are positive in this format for data in # the western hemisphere, we "fix" it here. Hopefully no one in the # eastern hemisphere uses this format! if longitude > 0: longitude = -longitude coordinates['latitude'] = latitude coordinates['longitude'] = longitude logging.warning('Setting elevation to 0.0') coordinates['elevation'] = 0.0 # Get standard paramaters standard['units_type'] = get_units_type(hdr['channel']) standard['horizontal_orientation'] = float(horizontal_orientation) standard['vertical_orientation'] = np.nan standard['instrument_period'] = flt_data[0] standard['instrument_damping'] = flt_data[1] standard['process_time'] = '' station_line = lines[5] station_length = int(lines[5][72:74]) name = station_line[:station_length] standard['station_name'] = name standard['sensor_serial_number'] = '' standard['instrument'] = '' standard['comments'] = '' standard['units'] = 'acc' standard['structure_type'] = '' standard['process_level'] = PROCESS_LEVELS['V1'] standard['corner_frequency'] = np.nan standard['source'] = ('Los Angeles Basin Seismic Network, University ' 'of Southern California') standard['source_format'] = 'usc' # this field can be used for instrument correction # when data is in counts standard['instrument_sensitivity'] = np.nan # Get format specific format_specific['fractional_unit'] = flt_data[4] # Set dictionary hdr['standard'] = standard hdr['coordinates'] = coordinates hdr['format_specific'] = format_specific return hdr
def _stats_from_inventory(data, inventory, channelid): if len(inventory.source): source = inventory.source station = inventory.networks[0].stations[0] coords = { 'latitude': station.latitude, 'longitude': station.longitude, 'elevation': station.elevation } channel_names = [ch.code for ch in station.channels] channelidx = channel_names.index(channelid) channel = station.channels[channelidx] standard = {} # things we'll never get from an inventory object standard['corner_frequency'] = np.nan standard['instrument_damping'] = np.nan standard['instrument_period'] = np.nan standard['structure_type'] = '' standard['process_time'] = '' if data.dtype in INT_TYPES: standard['process_level'] = 'raw counts' else: standard['process_level'] = 'uncorrected physical units' standard['source'] = source standard['source_file'] = '' standard['instrument'] = '' standard['sensor_serial_number'] = '' if channel.sensor is not None: standard['instrument'] = ( '%s %s %s %s' % (channel.sensor.type, channel.sensor.manufacturer, channel.sensor.model, channel.sensor.description)) if channel.sensor.serial_number is not None: standard['sensor_serial_number'] = channel.sensor.serial_number else: standard['sensor_serial_number'] = '' if channel.azimuth is not None: standard['horizontal_orientation'] = channel.azimuth standard['source_format'] = channel.storage_format if standard['source_format'] is None: standard['source_format'] = 'fdsn' standard['units_type'] = get_units_type(channelid) if len(channel.comments): comments = ' '.join(channel.comments[i].value for i in range(len(channel.comments))) standard['comments'] = comments else: standard['comments'] = '' standard['station_name'] = '' if station.site.name != 'None': standard['station_name'] = station.site.name # extract the remaining standard info and format_specific info # from a JSON string in the station description. format_specific = {} if station.description is not None and station.description != 'None': jsonstr = station.description try: big_dict = json.loads(jsonstr) standard.update(big_dict['standard']) format_specific = big_dict['format_specific'] except json.decoder.JSONDecodeError: format_specific['description'] = jsonstr standard['instrument_sensitivity'] = np.nan response = None if channel.response is not None: response = channel.response if hasattr(response, 'sensitivity'): standard['instrument_sensitivity'] = response.sensitivity.value return (response, standard, coords, format_specific)
def _get_header_info_v1(int_data, flt_data, lines, level, location=""): """Return stats structure from various V1 headers. Output is a dictionary like this: - network (str): Default is '--'. Determined using COSMOS_NETWORKS - station (str) - channel (str) - location (str): Default is '--' - starttime (datetime) - sampling_rate (float) - delta (float) - coordinates: - latitude (float) - longitude (float) - elevation (float): Default is np.nan - standard (Defaults are either np.nan or '') - horizontal_orientation (float): Rotation from north (degrees) - instrument_period (float): Period of sensor (Hz) - instrument_damping (float): Fraction of critical - process_time (datetime): Reported date of processing - process_level: Either 'V0', 'V1', 'V2', or 'V3' - station_name (str): Long form station description - sensor_serial_number (str): Reported sensor serial - instrument (str) - comments (str): Processing comments - structure_type (str) - corner_frequency (float): Sensor corner frequency (Hz) - units (str) - source (str): Network source description - source_format (str): Always dmg - format_specific - sensor_sensitivity (float): Transducer sensitivity (cm/g) - time_sd (float): Standard deviaiton of time steop (millisecond) - fractional_unit (float): Units of digitized acceleration in file (fractions of g) - scaling_factor (float): Scaling used for converting acceleration from g/10 to cm/sec/sec - low_filter_corner (float): Filter corner for low frequency V2 filtering (Hz) - high_filter_corner (float): Filter corner for high frequency V2 filtering (Hz) Args: int_data (ndarray): Array of integer data flt_data (ndarray): Array of float data lines (list): List of text headers (str) level (str): Process level code (V0, V1, V2, V3) Returns: dictionary: Dictionary of header/metadata information """ hdr = {} coordinates = {} standard = {} format_specific = {} # Required metadata code = "" if lines[0].find("CDMG") > -1: code = "CDMG" if code.upper() in CODES: network = code.upper() idx = np.argwhere(CODES == network)[0][0] source = SOURCES1[idx].decode("utf-8") + ", " + SOURCES2[idx].decode( "utf-8") else: # newer files have a record_id.network.station.location.channel thing # in the 4th line recinfo = lines[3][0:23] try: parts = recinfo.strip().split(".") network = parts[1].upper() idx = np.argwhere(CODES == network)[0][0] source = (SOURCES1[idx].decode("utf-8") + ", " + SOURCES2[idx].decode("utf-8")) except BaseException: network = "--" source = "unknown" hdr["network"] = network logging.debug(f"network: {network}") station_line = lines[4] station = station_line[12:17].strip() logging.debug(f"station: {station}") hdr["station"] = station angle = int_data[26] logging.debug(f"angle: {angle}") # newer files seem to have the *real* number of points in int header 32 if int_data[32] != 0: hdr["npts"] = int_data[32] else: hdr["npts"] = int_data[27] reclen = flt_data[2] logging.debug(f"reclen: {reclen}") logging.debug(f"npts: {hdr['npts']}") hdr["sampling_rate"] = np.round((hdr["npts"] - 1) / reclen) logging.debug(f"sampling_rate: {hdr['sampling_rate']}") hdr["delta"] = 1 / hdr["sampling_rate"] hdr["channel"] = _get_channel(angle, hdr["sampling_rate"]) # this format uses codes of 500/600 in this angle to indicate a vertical # channel Obspy freaks out with azimuth values > 360, so let's just say # horizontal angle is zero in these cases if hdr["channel"].endswith("Z"): angle = "0.0" logging.debug(f"channel: {hdr['channel']}") if location == "": hdr["location"] = "--" else: hdr["location"] = location # parse the trigger time try: trigger_time = _get_date(lines[3]) + _get_time(lines[3]) # sometimes these trigger times are in UTC, other times a different # time zone. Figure out if this is the case and modify start time # accordingly # look for three letter string that might be a time zone if "PDT" in lines[3] or "PST" in lines[3]: timezone = pytz.timezone("US/Pacific") utcoffset = timezone.utcoffset(trigger_time) # subtracting because we're going from pacific to utc trigger_time -= utcoffset hdr["starttime"] = trigger_time except BaseException: logging.warning("No start time provided on trigger line. " "This must be set manually for network/station: " "%s/%s." % (hdr["network"], hdr["station"])) standard["comments"] = "Missing start time." # Coordinates latitude_str = station_line[20:27].strip() longitude_str = station_line[29:37].strip() latitude, longitude = _get_coords(latitude_str, longitude_str) coordinates["latitude"] = latitude coordinates["longitude"] = longitude logging.warning("Setting elevation to 0.0") coordinates["elevation"] = 0.0 # Standard metadata standard["units_type"] = get_units_type(hdr["channel"]) standard["horizontal_orientation"] = float(angle) standard["vertical_orientation"] = np.nan standard["instrument_period"] = flt_data[0] standard["instrument_damping"] = flt_data[1] process_time = _get_date(lines[0]) if process_time is not None: standard["process_time"] = process_time.strftime(TIMEFMT) else: standard["process_time"] = "" standard["process_level"] = PROCESS_LEVELS[level] logging.debug(f"process_level: {standard['process_level']}") if "comments" not in standard: standard["comments"] = "" standard["structure_type"] = lines[7][46:80].strip() standard["instrument"] = station_line[39:47].strip() standard["sensor_serial_number"] = station_line[53:57].strip() standard["corner_frequency"] = np.nan standard["units"] = "acc" standard["source"] = source standard["source_format"] = "dmg" standard["station_name"] = lines[5].strip() # this field can be used for instrument correction # when data is in counts standard["instrument_sensitivity"] = np.nan # Format specific metadata format_specific["fractional_unit"] = flt_data[4] format_specific["sensor_sensitivity"] = flt_data[5] if flt_data[13] == -999: format_specific["time_sd"] = np.nan else: format_specific["time_sd"] = flt_data[13] # Set dictionary hdr["coordinates"] = coordinates hdr["standard"] = standard hdr["format_specific"] = format_specific return hdr
def _read_header(hdr_data, station, name, component, data_format, instrument, resolution): """Construct stats dictionary from header lines. Args: hdr_data (ndarray): (10,10) numpy array containing header data. station (str): Station code obtained from previous text portion of header. location (str): Location string obtained from previous text portion of header. component (str): Component direction (N18E, S72W, etc.) Returns: Dictionary containing fields: - network "NZ" - station - channel H1,H2,or Z. - location - sampling_rate Samples per second. - delta Interval between samples (seconds) - calib Calibration factor (always 1.0) - npts Number of samples in record. - starttime Datetime object containing start of record. - standard: - station_name - units "acc" - source 'New Zealand Institute of Geological and Nuclear Science' - horizontal_orientation - instrument_period - instrument_damping - processing_time - process_level - sensor_serial_number - instrument - comments - structure_type - corner_frequency - source_format - coordinates: - lat Latitude of station. - lon Longitude of station. - elevation Elevation of station. - format_specific: - sensor_bit_resolution """ hdr = {} standard = {} coordinates = {} format_specific = {} hdr["station"] = station standard["station_name"] = name # Note: Original sample interval (s): hdr_data[6, 4] # Sample inverval (s) hdr["delta"] = hdr_data[6, 5] hdr["sampling_rate"] = 1 / hdr["delta"] hdr["calib"] = 1.0 if data_format == "V1": hdr["npts"] = int(hdr_data[3, 0]) else: hdr["npts"] = int(hdr_data[3, 3]) hdr["network"] = "NZ" standard["units_type"] = "acc" standard["units"] = "cm/s/s" standard[ "source"] = "New Zealand Institute of Geological and Nuclear Science" logging.debug(f"component: {component}") standard["vertical_orientation"] = np.nan if component.lower() in ["up", "down"]: standard["horizontal_orientation"] = np.nan hdr["channel"] = get_channel_name(hdr["delta"], is_acceleration=True, is_vertical=True, is_north=False) else: angle = _get_channel(component) logging.debug(f"angle: {angle}") standard["horizontal_orientation"] = float(angle) if (angle > 315 or angle < 45) or (angle > 135 and angle < 225): hdr["channel"] = get_channel_name(hdr["delta"], is_acceleration=True, is_vertical=False, is_north=True) else: hdr["channel"] = get_channel_name(hdr["delta"], is_acceleration=True, is_vertical=False, is_north=False) logging.debug(f"channel: {hdr['channel']}") hdr["location"] = "--" # figure out the start time milliseconds = hdr_data[3, 9] seconds = int(milliseconds / 1000) microseconds = int(np.round(milliseconds / 1000.0 - seconds)) year = int(hdr_data[0, 8]) month = int(hdr_data[0, 9]) day = int(hdr_data[1, 8]) hour = int(hdr_data[1, 9]) minute = int(hdr_data[3, 8]) hdr["starttime"] = datetime(year, month, day, hour, minute, seconds, microseconds) # figure out station coordinates latdg = hdr_data[2, 0] latmn = hdr_data[2, 1] latsc = hdr_data[2, 2] coordinates["latitude"] = _dms_to_dd(latdg, latmn, latsc) * -1 londg = hdr_data[2, 3] lonmn = hdr_data[2, 4] lonsc = hdr_data[2, 5] coordinates["longitude"] = _dms_to_dd(londg, lonmn, lonsc) logging.warning("Setting elevation to 0.0") coordinates["elevation"] = 0.0 # get other standard metadata standard["units_type"] = get_units_type(hdr["channel"]) standard["instrument_period"] = 1 / hdr_data[4, 0] standard["instrument_damping"] = hdr_data[4, 1] standard["process_time"] = "" standard["process_level"] = PROCESS_LEVELS[data_format] logging.debug(f"process_level: {data_format}") standard["sensor_serial_number"] = "" standard["instrument"] = instrument standard["comments"] = "" standard["structure_type"] = "" standard["corner_frequency"] = np.nan standard["source_format"] = "geonet" # these fields can be used for instrument correction # when data is in counts standard["instrument_sensitivity"] = np.nan standard["volts_to_counts"] = np.nan # get format specific metadata format_specific["sensor_bit_resolution"] = resolution hdr["coordinates"] = coordinates hdr["standard"] = standard hdr["format_specific"] = format_specific return hdr
def _get_header_info_v1(int_data, flt_data, lines, level, location=''): """Return stats structure from various V1 headers. Output is a dictionary like this: - network (str): Default is 'ZZ'. Determined using COSMOS_NETWORKS - station (str) - channel (str) - location (str): Default is '--' - starttime (datetime) - sampling_rate (float) - delta (float) - coordinates: - latitude (float) - longitude (float) - elevation (float): Default is np.nan - standard (Defaults are either np.nan or '') - horizontal_orientation (float): Rotation from north (degrees) - instrument_period (float): Period of sensor (Hz) - instrument_damping (float): Fraction of critical - process_time (datetime): Reported date of processing - process_level: Either 'V0', 'V1', 'V2', or 'V3' - station_name (str): Long form station description - sensor_serial_number (str): Reported sensor serial - instrument (str) - comments (str): Processing comments - structure_type (str) - corner_frequency (float): Sensor corner frequency (Hz) - units (str) - source (str): Network source description - source_format (str): Always dmg - format_specific - sensor_sensitivity (float): Transducer sensitivity (cm/g) - time_sd (float): Standard deviaiton of time steop (millisecond) - fractional_unit (float): Units of digitized acceleration in file (fractions of g) - scaling_factor (float): Scaling used for converting acceleration from g/10 to cm/sec/sec - low_filter_corner (float): Filter corner for low frequency V2 filtering (Hz) - high_filter_corner (float): Filter corner for high frequency V2 filtering (Hz) Args: int_data (ndarray): Array of integer data flt_data (ndarray): Array of float data lines (list): List of text headers (str) level (str): Process level code (V0, V1, V2, V3) Returns: dictionary: Dictionary of header/metadata information """ hdr = {} coordinates = {} standard = {} format_specific = {} # Required metadata code = '' if lines[0].find('CDMG') > -1: code = 'CDMG' if code.upper() in CODES: network = code.upper() idx = np.argwhere(CODES == network)[0][0] source = SOURCES1[idx].decode('utf-8') + ', ' + SOURCES2[idx].decode( 'utf-8') else: network = 'ZZ' source = 'unknown' hdr['network'] = network logging.debug('network: %s' % network) station_line = lines[4] station = station_line[12:17].strip() logging.debug('station: %s' % station) hdr['station'] = station angle = int_data[26] logging.debug('angle: %s' % angle) hdr['npts'] = int_data[27] reclen = flt_data[2] logging.debug('reclen: %s' % reclen) logging.debug('npts: %s' % hdr['npts']) hdr['sampling_rate'] = np.round((hdr['npts'] - 1) / reclen) logging.debug('sampling_rate: %s' % hdr['sampling_rate']) hdr['delta'] = 1 / hdr['sampling_rate'] hdr['channel'] = _get_channel(angle, hdr['sampling_rate']) # this format uses codes of 500/600 in this angle to indicate a vertical channel # Obspy freaks out with azimuth values > 360, so let's just say horizontal angle # is zero in these cases if hdr['channel'].endswith('Z'): angle = '0.0' logging.debug('channel: %s' % hdr['channel']) if location == '': hdr['location'] = '--' else: hdr['location'] = location # parse the trigger time try: trigger_time = _get_date(lines[3]) + _get_time(lines[3]) hdr['starttime'] = trigger_time except: logging.warning('No start time provided on trigger line. ' 'This must be set manually for network/station: ' '%s/%s.' % (hdr['network'], hdr['station'])) standard['comments'] = 'Missing start time.' # Coordinates latitude_str = station_line[20:27].strip() longitude_str = station_line[29:37].strip() latitude, longitude = _get_coords(latitude_str, longitude_str) coordinates['latitude'] = latitude coordinates['longitude'] = longitude coordinates['elevation'] = np.nan # Standard metadata standard['units_type'] = get_units_type(hdr['channel']) standard['horizontal_orientation'] = float(angle) standard['instrument_period'] = flt_data[0] standard['instrument_damping'] = flt_data[1] process_time = _get_date(lines[0]) if process_time is not None: standard['process_time'] = process_time.strftime(TIMEFMT) else: standard['process_time'] = '' standard['process_level'] = PROCESS_LEVELS[level] logging.debug("process_level: %s" % standard['process_level']) if 'comments' not in standard: standard['comments'] = '' standard['structure_type'] = lines[7][46:80].strip() standard['instrument'] = station_line[39:47].strip() standard['sensor_serial_number'] = station_line[53:57].strip() standard['corner_frequency'] = np.nan standard['units'] = 'acc' standard['source'] = source standard['source_format'] = 'dmg' standard['station_name'] = lines[5].strip() # this field can be used for instrument correction # when data is in counts standard['instrument_sensitivity'] = np.nan # Format specific metadata format_specific['fractional_unit'] = flt_data[4] format_specific['sensor_sensitivity'] = flt_data[5] if flt_data[13] == -999: format_specific['time_sd'] = np.nan else: format_specific['time_sd'] = flt_data[13] # Set dictionary hdr['coordinates'] = coordinates hdr['standard'] = standard hdr['format_specific'] = format_specific return hdr
def _read_header_lines(filename, offset): """Read the header lines for each channel. Args: filename (str): Input BHRC file name. offset (int): Number of lines to skip from the beginning of the file. Returns: tuple: (header dictionary containing Stats dictionary with extra sub-dicts, updated offset rows) """ with open(filename, "rt", encoding="utf-8") as f: for _ in range(offset): next(f) lines = [next(f) for x in range(TEXT_HDR_ROWS)] offset += TEXT_HDR_ROWS header = {} standard = {} coords = {} format_specific = {} # get the sensor azimuth with respect to the earthquake # this data has been rotated so that the longitudinal channel (L) # is oriented at the sensor azimuth, and the transverse (T) is # 90 degrees off from that. station_info = lines[7][lines[7].index("Station"):] float_strings = re.findall(FLOATRE, station_info) (lat_str, lon_str, alt_str, lstr, tstr) = float_strings[0:5] component = lines[4].strip() if component == "V": angle = np.nan elif component == "L": angle = float(lstr) else: angle = float(tstr) coords = { "latitude": float(lat_str), "longitude": float(lon_str), "elevation": float(alt_str), } # fill out the standard dictionary standard["source"] = SOURCE standard["source_format"] = SOURCE_FORMAT standard["instrument"] = lines[1].split("=")[1].strip() standard["sensor_serial_number"] = "" volstr = lines[0].split()[1].strip() if volstr not in LEVELS: raise KeyError(f"Volume {volstr} files are not supported.") standard["process_level"] = PROCESS_LEVELS[LEVELS[volstr]] standard["process_time"] = "" station_name = lines[7][0:lines[7].index("Station")].strip() standard["station_name"] = station_name standard["structure_type"] = "" standard["corner_frequency"] = np.nan standard["units"] = "acc" period_str, damping_str = re.findall(FLOATRE, lines[9]) standard["instrument_period"] = float(period_str) if standard["instrument_period"] == 0: standard["instrument_period"] = np.nan standard["instrument_damping"] = float(damping_str) standard["horizontal_orientation"] = angle standard["vertical_orientation"] = np.nan standard["comments"] = "" head, tail = os.path.split(filename) standard["source_file"] = tail or os.path.basename(head) # this field can be used for instrument correction # when data is in counts standard["instrument_sensitivity"] = np.nan # fill out the stats stuff # we don't know the start of the trace header["starttime"] = UTCDateTime(1970, 1, 1) npts_str, dur_str = re.findall(FLOATRE, lines[10]) header["npts"] = int(npts_str) header["duration"] = float(dur_str) header["delta"] = header["duration"] / (header["npts"] - 1) header["sampling_rate"] = 1 / header["delta"] if np.isnan(angle): header["channel"] = get_channel_name( header["sampling_rate"], is_acceleration=True, is_vertical=True, is_north=False, ) elif (angle > 315 or angle < 45) or (angle > 135 and angle < 225): header["channel"] = get_channel_name( header["sampling_rate"], is_acceleration=True, is_vertical=False, is_north=True, ) else: header["channel"] = get_channel_name( header["sampling_rate"], is_acceleration=True, is_vertical=False, is_north=False, ) standard["units_type"] = get_units_type(header["channel"]) part1 = lines[0].split(":")[1] stationcode = part1.split("/")[0].strip() header["station"] = stationcode header["location"] = "--" header["network"] = NETWORK header["coordinates"] = coords header["standard"] = standard header["format_specific"] = format_specific offset += INT_HDR_ROWS offset += FLOAT_HDR_ROWS return (header, offset)
def validate(self): """Ensure that all required metadata fields have been set. Raises: KeyError: - When standard dictionary is missing required fields - When standard values are of the wrong type - When required values are set to a default. ValueError: - When number of points in header does not match data length. """ # here's something we thought obspy would do... # verify that npts matches length of data if self.stats.npts != len(self.data): raise ValueError( "Number of points in header does not match the number of " "points in the data.") if "remove_response" not in self.getProvenanceKeys(): self.stats.standard.units = "raw counts" self.stats.standard.units_type = get_units_type(self.stats.channel) # are all of the defined standard keys in the standard dictionary? req_keys = set(STANDARD_KEYS.keys()) std_keys = set(list(self.stats.standard.keys())) if not req_keys <= std_keys: missing = str(req_keys - std_keys) raise KeyError( f'Missing standard values in StationTrace header: "{missing}"') type_errors = [] required_errors = [] for key in req_keys: keydict = STANDARD_KEYS[key] value = self.stats.standard[key] required = keydict["required"] vtype = keydict["type"] default = keydict["default"] if not isinstance(value, vtype): type_errors.append(key) if required: if isinstance(default, list): if value not in default: required_errors.append(key) if value == default: required_errors.append(key) type_error_msg = "" if len(type_errors): fmt = 'The following standard keys have the wrong type: "%s"' tpl = ",".join(type_errors) type_error_msg = fmt % tpl required_error_msg = "" if len(required_errors): fmt = 'The following standard keys are required: "%s"' tpl = ",".join(required_errors) required_error_msg = fmt % tpl error_msg = type_error_msg + "\n" + required_error_msg if len(error_msg.strip()): raise KeyError(error_msg)
def _get_header_info(filename, any_structure=False, accept_flagged=False, location=''): """Return stats structure from various headers. Output is a dictionary like this: - network - station - channel - location (str): Set to floor the sensor is located on. If not a multi-sensor array, default is '--'. Can be set manually by the user. - starttime - sampling_rate - npts - coordinates: - latitude - longitude - elevation - standard - horizontal_orientation - instrument_period - instrument_damping - process_level - station_name - sensor_serial_number - instrument - comments - structure_type - corner_frequency - units - source - source_format - format_specific - vertical_orientation - building_floor (0=basement, 1=floor above basement, -1=1st sub-basement, etc. - bridge_number_spans - bridge_transducer_location ("free field", "at the base of a pier or abutment", "on an abutment", "on the deck at the top of a pier" "on the deck between piers or between an abutment and a pier." dam_transducer_location ("upstream or downstream free field", "at the base of the dam", "on the crest of the dam", on the abutment of the dam") construction_type ("Reinforced concrete gravity", "Reinforced concrete arch", "earth fill", "other") filter_poles data_source """ stats = {} standard = {} format_specific = {} coordinates = {} # read the ascii header lines with open(filename) as f: ascheader = [next(f).strip() for x in range(ASCII_HEADER_LINES)] standard['process_level'] = PROCESS_LEVELS[VALID_HEADERS[ascheader[0]]] logging.debug("process_level: %s" % standard['process_level']) # station code is in the third line stats['station'] = '' if len(ascheader[2]) >= 4: stats['station'] = ascheader[2][0:4].strip() stats['station'] = stats['station'].strip('\x00') logging.debug('station: %s' % stats['station']) standard['process_time'] = '' standard['station_name'] = ascheader[5][10:40].strip() # sometimes the data source has nothing in it, # most of the time it seems has has USGS in it # sometimes it's something like JPL/USGS, CDOT/USGS, etc. # if it's got USGS in it, let's just say network=US, otherwise "--" stats['network'] = 'ZZ' if ascheader[7].find('USGS') > -1: stats['network'] = 'US' try: standard['source'] = ascheader[7].split('=')[2].strip() except IndexError: standard['source'] = 'USGS' if standard['source'] == '': standard['source'] = 'USGS' standard['source_format'] = 'smc' # read integer header data intheader = np.genfromtxt(filename, dtype=np.int32, max_rows=INTEGER_HEADER_LINES, skip_header=ASCII_HEADER_LINES, delimiter=INT_HEADER_WIDTHS) # 8 columns per line # first line is start time information, and then inst. serial number missing_data = intheader[0, 0] year = intheader[0, 1] # sometimes the year field has a 0 in it. When this happens, we # can try to get a timestamp from line 4 of the ascii header. if year == 0: parts = ascheader[3].split() try: year = int(parts[0]) except ValueError as ve: fmt = ('Could not find year in SMC file %s. Not present ' 'in integer header and not parseable from line ' '4 of ASCII header. Error: "%s"') raise GMProcessException(fmt % (filename, str(ve))) jday = intheader[0, 2] hour = intheader[0, 3] minute = intheader[0, 4] if (year != missing_data and jday != missing_data and hour != missing_data and minute != missing_data): # Handle second if missing second = 0 if not intheader[0, 5] == missing_data: second = intheader[0, 5] # Handle microsecond if missing and convert milliseconds to microseconds microsecond = 0 if not intheader[0, 6] == missing_data: microsecond = intheader[0, 6] / 1e3 datestr = '%i %00i %i %i %i %i' % (year, jday, hour, minute, second, microsecond) stats['starttime'] = datetime.strptime(datestr, '%Y %j %H %M %S %f') else: logging.warning('No start time provided. ' 'This must be set manually for network/station: ' '%s/%s.' % (stats['network'], stats['station'])) standard['comments'] = 'Missing start time.' standard['sensor_serial_number'] = '' if intheader[1, 3] != missing_data: standard['sensor_serial_number'] = str(intheader[1, 3]) # we never get a two character location code so floor location is used if location == '': location = intheader.flatten()[24] if location != missing_data: location = str(location) if len(location) < 2: location = location.zfill(2) stats['location'] = location else: stats['location'] = '--' else: stats['location'] = location # second line is information about number of channels, orientations # we care about orientations format_specific['vertical_orientation'] = np.nan if intheader[1, 4] != missing_data: format_specific['vertical_orientation'] = int(intheader[1, 4]) standard['horizontal_orientation'] = np.nan standard['vertical_orientation'] = np.nan if intheader[1, 5] != missing_data: standard['horizontal_orientation'] = float(intheader[1, 5]) if intheader[1, 6] == missing_data or intheader[1, 6] not in INSTRUMENTS: standard['instrument'] = '' else: standard['instrument'] = INSTRUMENTS[intheader[1, 6]] num_comments = intheader[1, 7] # third line contains number of data points stats['npts'] = intheader[2, 0] problem_flag = intheader[2, 1] if problem_flag == 1: if not accept_flagged: fmt = 'SMC: Record found in file %s has a problem flag!' raise GMProcessException(fmt % filename) else: logging.warning( 'SMC: Data contains a problem flag for network/station: ' '%s/%s. See comments.' % (stats['network'], stats['station'])) stype = intheader[2, 2] if stype == missing_data: stype = np.nan elif stype not in STRUCTURES: # structure type is not defined and should will be considered 'other' stype = 4 fmt = 'SMC: Record found in file %s is not a free-field sensor!' standard['structure_type'] = STRUCTURES[stype] if standard['structure_type'] == 'building' and not any_structure: raise Exception(fmt % filename) format_specific['building_floor'] = np.nan if intheader[3, 0] != missing_data: format_specific['building_floor'] = intheader[3, 0] format_specific['bridge_number_spans'] = np.nan if intheader[3, 1] != missing_data: format_specific['bridge_number_spans'] = intheader[3, 1] format_specific['bridge_transducer_location'] = BRIDGE_LOCATIONS[0] if intheader[3, 2] != missing_data: bridge_number = intheader[3, 2] format_specific['bridge_transducer_location'] = \ BRIDGE_LOCATIONS[bridge_number] format_specific['dam_transducer_location'] = DAM_LOCATIONS[0] if intheader[3, 3] != missing_data: dam_number = intheader[3, 3] format_specific['dam_transducer_location'] = DAM_LOCATIONS[dam_number] c1 = format_specific['bridge_transducer_location'].find('free field') == -1 c2 = format_specific['dam_transducer_location'].find('free field') == -1 if (c1 or c2) and not any_structure: raise Exception(fmt % filename) format_specific['construction_type'] = CONSTRUCTION_TYPES[4] if intheader[3, 4] != missing_data: format_specific['construction_type'] = \ CONSTRUCTION_TYPES[intheader[3, 4]] # station is repeated here if all numeric if not len(stats['station']): stats['station'] = '%i' % intheader[3, 5] # read float header data skip = ASCII_HEADER_LINES + INTEGER_HEADER_LINES floatheader = np.genfromtxt(filename, max_rows=FLOAT_HEADER_LINES, skip_header=skip, delimiter=FLOAT_HEADER_WIDTHS) # float headers are 10 lines of 5 floats each missing_data = floatheader[0, 0] stats['sampling_rate'] = floatheader[0, 1] if stats['sampling_rate'] >= MAX_ALLOWED_SAMPLE_RATE: fmt = 'Sampling rate of %.2g samples/second is nonsensical.' raise Exception(fmt % stats['sampling_rate']) coordinates['latitude'] = floatheader[2, 0] # the documentation for SMC says that sometimes longitudes are # positive in the western hemisphere. Since it is very unlikely # any of these files exist for the eastern hemisphere, check for # positive longitudes and fix them. lon = floatheader[2, 1] if lon > 0: lon = -1 * lon coordinates['longitude'] = lon coordinates['elevation'] = 0.0 if floatheader[2, 2] != missing_data: coordinates['elevation'] = floatheader[2, 2] else: logging.warning('Setting elevation to 0.0') # figure out the channel code if format_specific['vertical_orientation'] in [0, 180]: stats['channel'] = get_channel_name(stats['sampling_rate'], is_acceleration=True, is_vertical=True, is_north=False) else: ho = standard['horizontal_orientation'] quad1 = ho > 315 and ho <= 360 quad2 = ho > 0 and ho <= 45 quad3 = ho > 135 and ho <= 225 if quad1 or quad2 or quad3: stats['channel'] = get_channel_name(stats['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=True) else: stats['channel'] = get_channel_name(stats['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=False) logging.debug('channel: %s' % stats['channel']) sensor_frequency = floatheader[4, 1] standard['instrument_period'] = 1 / sensor_frequency standard['instrument_damping'] = floatheader[4, 2] standard['corner_frequency'] = floatheader[3, 4] format_specific['filter_poles'] = floatheader[4, 0] standard['units'] = 'acc' standard['units_type'] = get_units_type(stats['channel']) # this field can be used for instrument correction # when data is in counts standard['instrument_sensitivity'] = np.nan # read in the comment lines with open(filename) as f: skip = ASCII_HEADER_LINES + INTEGER_HEADER_LINES + FLOAT_HEADER_LINES _ = [next(f) for x in range(skip)] standard['comments'] = [ next(f).strip().lstrip('|') for x in range(num_comments) ] standard['comments'] = ' '.join(standard['comments']) stats['coordinates'] = coordinates stats['standard'] = standard stats['format_specific'] = format_specific head, tail = os.path.split(filename) stats['standard']['source_file'] = tail or os.path.basename(head) return (stats, num_comments)
def _get_header_info(int_data, flt_data, lines, cmt_data, location=''): """Return stats structure from various headers. Output is a dictionary like this: - network (str): Default is '--'. Determined using COSMOS_NETWORKS - station (str) - channel (str): Determined using COSMOS_ORIENTATIONS - location (str): Set to location index of sensor site at station. If not a multi-site array, default is '--'. - starttime (datetime) - duration (float) - sampling_rate (float) - delta (float) - npts (int) - coordinates: - latitude (float) - longitude (float) - elevation (float) - standard (Defaults are either np.nan or '') - horizontal_orientation (float): Rotation from north (degrees) - instrument_period (float): Period of sensor (Hz) - instrument_damping (float): Fraction of critical - process_time (datetime): Reported date of processing - process_level: Either 'V0', 'V1', 'V2', or 'V3' - station_name (str): Long form station description - sensor_serial_number (str): Reported sensor serial - instrument (str): See SENSOR_TYPES - comments (str): Processing comments - structure_type (str): See BUILDING_TYPES - corner_frequency (float): Sensor corner frequency (Hz) - units (str): See UNITS - source (str): Network source description - source_format (str): Always cosmos - format_specific - physical_units (str): See PHYSICAL_UNITS - v30 (float): Site geology V30 (km/s) - least_significant_bit: Recorder LSB in micro-volts (uv/count) - low_filter_type (str): Filter used for low frequency V2 filtering (see FILTERS) - low_filter_corner (float): Filter corner for low frequency V2 filtering (Hz) - low_filter_decay (float): Filter decay for low frequency V2 filtering (dB/octabe) - high_filter_type (str): Filter used for high frequency V2 filtering (see FILTERS) - high_filter_corner (float): Filter corner for high frequency V2 filtering (Hz) - high_filter_decay (float): Filter decay for high frequency V2 filtering (dB/octabe) - maximum (float): Maximum value - maximum_time (float): Time at which maximum occurs - station_code (int): Code for structure_type - record_flag (str): Either 'No problem', 'Fixed', 'Unfixed problem'. Should be described in more depth in comments. - scaling_factor (float): Scaling used for converting acceleration from g/10 to cm/s/s - sensor_sensitivity (float): Sensitvity in volts/g Args: int_data (ndarray): Array of integer data flt_data (ndarray): Array of float data lines (list): List of text headers (str) cmt_data (ndarray): Array of comments (str) Returns: dictionary: Dictionary of header/metadata information """ hdr = {} coordinates = {} standard = {} format_specific = {} # Get unknown parameter number try: unknown = int(lines[12][64:71]) except ValueError: unknown = -999 # required metadata network_num = int(int_data[10]) # Get network from cosmos table or fdsn code sheet if network_num in COSMOS_NETWORKS: network = COSMOS_NETWORKS[network_num][0] source = COSMOS_NETWORKS[network_num][1] if network == '': network = COSMOS_NETWORKS[network_num][2] else: network_code = lines[4][25:27].upper() if network_code in CODES: network = network_code idx = np.argwhere(CODES == network_code)[0][0] source = SOURCES1[idx].decode( 'utf-8') + ', ' + SOURCES2[idx].decode('utf-8') else: network = '--' source = '' hdr['network'] = network logging.debug('network: %s' % network) hdr['station'] = lines[4][28:34].strip() logging.debug('station: %s' % hdr['station']) # the channel orientation can be either relative to true north (idx 53) # or relative to sensor orientation (idx 54). horizontal_angle = int(int_data[53]) logging.debug('horizontal_angle: %s' % horizontal_angle) if horizontal_angle not in VALID_AZIMUTH_INTS: angles = np.array(int_data[19:21]).astype(np.float32) angles[angles == unknown] = np.nan if np.isnan(angles).all(): logging.warning("Horizontal_angle in COSMOS header is not valid.") else: ref = angles[~np.isnan(angles)][0] horizontal_angle = int(int_data[54]) if horizontal_angle not in VALID_AZIMUTH_INTS: logging.warning( "Horizontal_angle in COSMOS header is not valid.") else: horizontal_angle += ref if horizontal_angle > 360: horizontal_angle -= 360 horizontal_angle = float(horizontal_angle) # Store delta and duration. Use them to calculate npts and sampling_rate # NOTE: flt_data[33] is the delta of the V0 format, and if we are reading # a V1 or V2 format then it may have been resampled. We should consider # adding flt_data[33] delta to the provenance record at some point. delta = float(flt_data[61]) * MSEC_TO_SEC if delta != unknown: hdr['delta'] = delta hdr['sampling_rate'] = 1 / delta # Determine the angle based upon the cosmos table # Set horizontal angles other than N,S,E,W to H1 and H2 # Missing angle results in the channel number if horizontal_angle != unknown: if horizontal_angle in COSMOS_ORIENTATIONS: channel = COSMOS_ORIENTATIONS[horizontal_angle][1].upper() if channel == 'UP' or channel == 'DOWN' or channel == 'VERT': channel = get_channel_name( hdr['sampling_rate'], is_acceleration=True, is_vertical=True, is_north=False) horizontal_angle = 360.0 elif channel == 'RADL' or channel == 'LONG' or channel == 'H1': channel = get_channel_name( hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=True) horizontal_angle = 0.0 elif channel == 'TRAN' or channel == 'TANG' or channel == 'H2': channel = get_channel_name( hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=False) horizontal_angle = 90.0 else: # For the occassional 'OTHR' channel raise ValueError('Channel name is not valid.') elif horizontal_angle >= 0 and horizontal_angle <= 360: if ( horizontal_angle > 315 or horizontal_angle < 45 or (horizontal_angle > 135 and horizontal_angle < 225) ): channel = get_channel_name( hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=True) else: channel = get_channel_name( hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=False) horizontal_orientation = horizontal_angle else: errstr = ('Not enough information to distinguish horizontal from ' 'vertical channels.') raise BaseException('COSMOS: ' + errstr) hdr['channel'] = channel logging.debug('channel: %s' % hdr['channel']) if location == '': location = int(int_data[55]) location = str(_check_assign(location, unknown, '--')) if len(location) < 2: location = location.zfill(2) hdr['location'] = location else: hdr['location'] = location year = int(int_data[39]) month = int(int_data[41]) day = int(int_data[42]) hour = int(int_data[43]) minute = int(int_data[44]) second = float(flt_data[29]) # If anything more than seconds is excluded # It is considered inadequate time information if second == unknown: try: hdr['starttime'] = datetime( year, month, day, hour, minute) except BaseException: raise BaseException( 'COSMOS: Inadequate start time information.') else: second = second microsecond = int((second - int(second)) * 1e6) try: hdr['starttime'] = datetime( year, month, day, hour, minute, int(second), microsecond) except BaseException: raise BaseException( 'COSMOS: Inadequate start time information.') if flt_data[62] != unknown: # COSMOS **defines** "length" as npts*dt (note this is a bit unusual) cosmos_length = flt_data[62] npts = int(cosmos_length / delta) hdr['duration'] = (npts - 1) * delta hdr['npts'] = npts else: raise ValueError('COSMOS file does not specify length.') # coordinate information coordinates['latitude'] = float(flt_data[0]) coordinates['longitude'] = float(flt_data[1]) coordinates['elevation'] = float(flt_data[2]) for key in coordinates: if coordinates[key] == unknown: if key != 'elevation': logging.warning( 'Missing %r. Setting to np.nan.' % key, Warning) coordinates[key] = np.nan else: logging.warning('Missing %r. Setting to 0.0.' % key, Warning) coordinates[key] = 0.0 hdr['coordinates'] = coordinates # standard metadata standard['units_type'] = get_units_type(channel) standard['source'] = source standard['horizontal_orientation'] = horizontal_orientation standard['vertical_orientation'] = np.nan station_name = lines[4][40:-1].strip() standard['station_name'] = station_name instrument_frequency = float(flt_data[39]) if instrument_frequency == 0: standard['instrument_period'] = np.nan logging.warning('Instrument Frequency == 0') else: inst_freq = _check_assign(instrument_frequency, unknown, np.nan) standard['instrument_period'] = 1.0 / inst_freq instrument_damping = float(flt_data[40]) standard['instrument_damping'] = _check_assign(instrument_damping, unknown, np.nan) process_line = lines[10][10:40] if process_line.find('-') >= 0 or process_line.find('/') >= 0: if process_line.find('-') >= 0: delimeter = '-' elif process_line.find('/') >= 0: delimeter = '/' try: date = process_line.split(delimeter) month = int(date[0][-2:]) day = int(date[1]) year = int(date[2][:4]) time = process_line.split(':') hour = int(time[0][-2:]) minute = int(time[1]) second = float(time[2][:2]) microsecond = int((second - int(second)) * 1e6) etime = datetime(year, month, day, hour, minute, int(second), microsecond) standard['process_time'] = etime.strftime(TIMEFMT) except BaseException: standard['process_time'] = '' else: standard['process_time'] = '' process_level = int(int_data[0]) if process_level == 0: standard['process_level'] = PROCESS_LEVELS['V0'] elif process_level == 1: standard['process_level'] = PROCESS_LEVELS['V1'] elif process_level == 2: standard['process_level'] = PROCESS_LEVELS['V2'] elif process_level == 3: standard['process_level'] = PROCESS_LEVELS['V3'] else: standard['process_level'] = PROCESS_LEVELS['V1'] logging.debug("process_level: %s" % process_level) serial = int(int_data[52]) if serial != unknown: standard['sensor_serial_number'] = str(_check_assign( serial, unknown, '')) else: standard['sensor_serial_number'] = '' instrument = int(int_data[51]) if instrument != unknown and instrument in SENSOR_TYPES: standard['instrument'] = SENSOR_TYPES[instrument] else: standard['instrument'] = lines[6][57:-1].strip() structure_type = int(int_data[18]) if structure_type != unknown and structure_type in BUILDING_TYPES: standard['structure_type'] = BUILDING_TYPES[structure_type] else: standard['structure_type'] = '' frequency = float(flt_data[25]) standard['corner_frequency'] = _check_assign(frequency, unknown, np.nan) physical_parameter = int(int_data[2]) units = int(int_data[1]) if units != unknown and units in UNITS: standard['units'] = UNITS[units] else: if physical_parameter in [2, 4, 7, 10, 11, 12, 23]: standard['units'] = 'acc' elif physical_parameter in [5, 8, 24]: standard['units'] = 'vel' elif physical_parameter in [6, 9, 25]: standard['units'] = 'disp' standard['source_format'] = 'cosmos' standard['comments'] = ', '.join(cmt_data) # format specific metadata if physical_parameter in PHYSICAL_UNITS: physical_parameter = PHYSICAL_UNITS[physical_parameter][0] format_specific['physical_units'] = physical_parameter v30 = float(flt_data[3]) format_specific['v30'] = _check_assign(v30, unknown, np.nan) least_significant_bit = float(flt_data[21]) format_specific['least_significant_bit'] = _check_assign( least_significant_bit, unknown, np.nan) gain = float(flt_data[46]) format_specific['gain'] = _check_assign(gain, unknown, np.nan) low_filter_type = int(int_data[60]) if low_filter_type in FILTERS: format_specific['low_filter_type'] = FILTERS[low_filter_type] else: format_specific['low_filter_type'] = '' low_filter_corner = float(flt_data[53]) format_specific['low_filter_corner'] = _check_assign( low_filter_corner, unknown, np.nan) low_filter_decay = float(flt_data[54]) format_specific['low_filter_decay'] = _check_assign( low_filter_decay, unknown, np.nan) high_filter_type = int(int_data[61]) if high_filter_type in FILTERS: format_specific['high_filter_type'] = FILTERS[high_filter_type] else: format_specific['high_filter_type'] = '' high_filter_corner = float(flt_data[56]) format_specific['high_filter_corner'] = _check_assign( high_filter_corner, unknown, np.nan) high_filter_decay = float(flt_data[57]) format_specific['high_filter_decay'] = _check_assign( high_filter_decay, unknown, np.nan) maximum = float(flt_data[63]) format_specific['maximum'] = _check_assign(maximum, unknown, np.nan) maximum_time = float(flt_data[64]) format_specific['maximum_time'] = _check_assign( maximum_time, unknown, np.nan) format_specific['station_code'] = _check_assign( structure_type, unknown, np.nan) record_flag = int(int_data[75]) if record_flag == 0: format_specific['record_flag'] = 'No problem' elif record_flag == 1: format_specific['record_flag'] = 'Fixed' elif record_flag == 2: format_specific['record_flag'] = 'Unfixed problem' else: format_specific['record_flag'] = '' scaling_factor = float(flt_data[87]) format_specific['scaling_factor'] = _check_assign( scaling_factor, unknown, np.nan) scaling_factor = float(flt_data[41]) format_specific['sensor_sensitivity'] = _check_assign( scaling_factor, unknown, np.nan) # for V0 files, set a standard field called instrument_sensitivity ctov = least_significant_bit / MICRO_TO_VOLT vtog = 1 / format_specific['sensor_sensitivity'] if not np.isnan(format_specific['gain']): gain = format_specific['gain'] else: gain = 1.0 if gain == 0: fmt = '%s.%s.%s.%s' tpl = (hdr['network'], hdr['station'], hdr['channel'], hdr['location']) nscl = fmt % tpl raise ValueError('Gain of 0 discovered for NSCL: %s' % nscl) denom = ctov * vtog * (1.0 / gain) * sp.g standard['instrument_sensitivity'] = 1 / denom # Set dictionary hdr['standard'] = standard hdr['coordinates'] = coordinates hdr['format_specific'] = format_specific return hdr
def read_knet(filename, config=None, **kwargs): """Read Japanese KNET strong motion file. Args: filename (str): Path to possible KNET data file. config (dict): Dictionary containing configuration. kwargs (ref): Other arguments will be ignored. Returns: Stream: Obspy Stream containing three channels of acceleration data (cm/s**2). """ logging.debug("Starting read_knet.") if not is_knet(filename, config): raise Exception(f"{filename} is not a valid KNET file") # Parse the header portion of the file with open(filename, "rt") as f: lines = [next(f) for x in range(TEXT_HDR_ROWS)] hdr = {} coordinates = {} standard = {} hdr["network"] = "BO" hdr["station"] = lines[5].split()[2] logging.debug(f"station: {hdr['station']}") standard["station_name"] = "" # according to the powers that defined the Network.Station.Channel.Location # "standard", Location is a two character field. Most data providers, # including KNET here, don't provide this. We'll flag it as "--". hdr["location"] = "--" coordinates["latitude"] = float(lines[6].split()[2]) coordinates["longitude"] = float(lines[7].split()[2]) coordinates["elevation"] = float(lines[8].split()[2]) hdr["sampling_rate"] = float( re.search("\\d+", lines[10].split()[2]).group()) hdr["delta"] = 1 / hdr["sampling_rate"] standard["units_type"] = "acc" standard["units_type"] = "cm/s/s" dir_string = lines[12].split()[1].strip() # knet files have directions listed as N-S, E-W, or U-D, # whereas in kiknet those directions are '4', '5', or '6'. if dir_string in ["N-S", "1", "4"]: hdr["channel"] = get_channel_name(hdr["sampling_rate"], is_acceleration=True, is_vertical=False, is_north=True) elif dir_string in ["E-W", "2", "5"]: hdr["channel"] = get_channel_name( hdr["sampling_rate"], is_acceleration=True, is_vertical=False, is_north=False, ) elif dir_string in ["U-D", "3", "6"]: hdr["channel"] = get_channel_name(hdr["sampling_rate"], is_acceleration=True, is_vertical=True, is_north=False) else: raise Exception( f"KNET: Could not parse direction {lines[12].split()[1]}") logging.debug(f"channel: {hdr['channel']}") scalestr = lines[13].split()[2] parts = scalestr.split("/") num = float(parts[0].replace("(gal)", "")) den = float(parts[1]) calib = num / den hdr["calib"] = calib duration = float(lines[11].split()[2]) hdr["npts"] = int(duration * hdr["sampling_rate"]) timestr = " ".join(lines[9].split()[2:4]) # The K-NET and KiK-Net data logger adds a 15s time delay # this is removed here sttime = datetime.strptime(timestr, TIMEFMT) - timedelta(seconds=15.0) # Shift the time to utc (Japanese time is 9 hours ahead) sttime = sttime - timedelta(seconds=9 * 3600.0) hdr["starttime"] = sttime # read in the data - there is a max of 8 columns per line # the code below handles the case when last line has # less than 8 columns if hdr["npts"] % COLS_PER_LINE != 0: nrows = int(np.floor(hdr["npts"] / COLS_PER_LINE)) nrows2 = 1 else: nrows = int(np.ceil(hdr["npts"] / COLS_PER_LINE)) nrows2 = 0 data = np.genfromtxt(filename, skip_header=TEXT_HDR_ROWS, max_rows=nrows, filling_values=np.nan) data = data.flatten() if nrows2: skip_header = TEXT_HDR_ROWS + nrows data2 = np.genfromtxt(filename, skip_header=skip_header, max_rows=nrows2, filling_values=np.nan) data = np.hstack((data, data2)) nrows += nrows2 # apply the correction factor we're given in the header data *= calib # fill out the rest of the standard dictionary standard["units_type"] = get_units_type(hdr["channel"]) standard["horizontal_orientation"] = np.nan standard["vertical_orientation"] = np.nan standard["instrument_period"] = np.nan standard["instrument_damping"] = np.nan standard["process_time"] = "" standard["process_level"] = PROCESS_LEVELS["V1"] standard["sensor_serial_number"] = "" standard["instrument"] = "" standard["comments"] = "" standard["structure_type"] = "" if dir_string in ["1", "2", "3"]: standard["structure_type"] = "borehole" standard["corner_frequency"] = np.nan standard["units"] = "acc" standard["source"] = SRC standard["source_format"] = "knet" head, tail = os.path.split(filename) standard["source_file"] = tail or os.path.basename(head) # these fields can be used for instrument correction # when data is in counts standard["instrument_sensitivity"] = np.nan standard["volts_to_counts"] = np.nan hdr["coordinates"] = coordinates hdr["standard"] = standard # create a Trace from the data and metadata trace = StationTrace(data.copy(), Stats(hdr.copy())) response = {"input_units": "counts", "output_units": "cm/s^2"} trace.setProvenance("remove_response", response) stream = StationStream(traces=[trace]) return [stream]
def _read_header(hdr_data, station, name, component, data_format, instrument, resolution): """Construct stats dictionary from header lines. Args: hdr_data (ndarray): (10,10) numpy array containing header data. station (str): Station code obtained from previous text portion of header. location (str): Location string obtained from previous text portion of header. component (str): Component direction (N18E, S72W, etc.) Returns: Dictionary containing fields: - network "NZ" - station - channel H1,H2,or Z. - location - sampling_rate Samples per second. - delta Interval between samples (seconds) - calib Calibration factor (always 1.0) - npts Number of samples in record. - starttime Datetime object containing start of record. - standard: - station_name - units "acc" - source 'New Zealand Institute of Geological and Nuclear Science' - horizontal_orientation - instrument_period - instrument_damping - processing_time - process_level - sensor_serial_number - instrument - comments - structure_type - corner_frequency - source_format - coordinates: - lat Latitude of station. - lon Longitude of station. - elevation Elevation of station. - format_specific: - sensor_bit_resolution """ hdr = {} standard = {} coordinates = {} format_specific = {} hdr['station'] = station standard['station_name'] = name # Note: Original sample interval (s): hdr_data[6, 4] # Sample inverval (s) hdr['delta'] = hdr_data[6, 5] hdr['sampling_rate'] = 1 / hdr['delta'] hdr['calib'] = 1.0 if data_format == 'V1': hdr['npts'] = int(hdr_data[3, 0]) else: hdr['npts'] = int(hdr_data[3, 3]) hdr['network'] = 'NZ' standard['units'] = 'acc' standard['source'] = ('New Zealand Institute of Geological and ' 'Nuclear Science') logging.debug('component: %s' % component) standard['vertical_orientation'] = np.nan if component.lower() in ['up', 'down']: standard['horizontal_orientation'] = np.nan hdr['channel'] = get_channel_name( hdr['delta'], is_acceleration=True, is_vertical=True, is_north=False) else: angle = _get_channel(component) logging.debug('angle: %s' % angle) standard['horizontal_orientation'] = float(angle) if (angle > 315 or angle < 45) or (angle > 135 and angle < 225): hdr['channel'] = get_channel_name( hdr['delta'], is_acceleration=True, is_vertical=False, is_north=True) else: hdr['channel'] = get_channel_name( hdr['delta'], is_acceleration=True, is_vertical=False, is_north=False) logging.debug('channel: %s' % hdr['channel']) hdr['location'] = '--' # figure out the start time milliseconds = hdr_data[3, 9] seconds = int(milliseconds / 1000) microseconds = int(np.round(milliseconds / 1000.0 - seconds)) year = int(hdr_data[0, 8]) month = int(hdr_data[0, 9]) day = int(hdr_data[1, 8]) hour = int(hdr_data[1, 9]) minute = int(hdr_data[3, 8]) hdr['starttime'] = datetime( year, month, day, hour, minute, seconds, microseconds) # figure out station coordinates latdg = hdr_data[2, 0] latmn = hdr_data[2, 1] latsc = hdr_data[2, 2] coordinates['latitude'] = _dms_to_dd(latdg, latmn, latsc) * -1 londg = hdr_data[2, 3] lonmn = hdr_data[2, 4] lonsc = hdr_data[2, 5] coordinates['longitude'] = _dms_to_dd(londg, lonmn, lonsc) logging.warning('Setting elevation to 0.0') coordinates['elevation'] = 0.0 # get other standard metadata standard['units_type'] = get_units_type(hdr['channel']) standard['instrument_period'] = 1 / hdr_data[4, 0] standard['instrument_damping'] = hdr_data[4, 1] standard['process_time'] = '' standard['process_level'] = PROCESS_LEVELS[data_format] logging.debug("process_level: %s" % data_format) standard['sensor_serial_number'] = '' standard['instrument'] = instrument standard['comments'] = '' standard['structure_type'] = '' standard['corner_frequency'] = np.nan standard['source_format'] = 'geonet' # this field can be used for instrument correction # when data is in counts standard['instrument_sensitivity'] = np.nan # get format specific metadata format_specific['sensor_bit_resolution'] = resolution hdr['coordinates'] = coordinates hdr['standard'] = standard hdr['format_specific'] = format_specific return hdr
def _stats_from_inventory(data, inventory, seed_id, start_time): if len(inventory.source): if inventory.sender is not None and inventory.sender != inventory.source: source = f"{inventory.source},{inventory.sender}" else: source = inventory.source network_code, station_code, location_code, channel_code = seed_id.split( ".") selected_inventory = inventory.select( network=network_code, station=station_code, location=location_code, channel=channel_code, time=start_time, ) station = selected_inventory.networks[0].stations[0] channel = station.channels[0] coords = { "latitude": channel.latitude, "longitude": channel.longitude, "elevation": channel.elevation, } standard = {} # things we'll never get from an inventory object standard["corner_frequency"] = np.nan standard["instrument_damping"] = np.nan standard["instrument_period"] = np.nan standard["structure_type"] = "" standard["process_time"] = "" if data.dtype in INT_TYPES: standard["process_level"] = "raw counts" else: standard["process_level"] = "uncorrected physical units" standard["source"] = source standard["source_file"] = "" standard["instrument"] = "" standard["sensor_serial_number"] = "" if channel.sensor is not None: standard["instrument"] = "%s %s %s %s" % ( channel.sensor.type, channel.sensor.manufacturer, channel.sensor.model, channel.sensor.description, ) if channel.sensor.serial_number is not None: standard["sensor_serial_number"] = channel.sensor.serial_number else: standard["sensor_serial_number"] = "" if channel.azimuth is not None: standard["horizontal_orientation"] = channel.azimuth else: standard["horizontal_orientation"] = np.nan if channel.dip is not None: # Note: vertical orientatin is defined here as angle from horizontal standard["vertical_orientation"] = channel.dip else: standard["vertical_orientation"] = np.nan standard["units_type"] = get_units_type(channel_code) if len(channel.comments): comments = " ".join(channel.comments[i].value for i in range(len(channel.comments))) standard["comments"] = comments else: standard["comments"] = "" standard["station_name"] = "" if station.site.name != "None": standard["station_name"] = station.site.name # extract the remaining standard info and format_specific info # from a JSON string in the station description. format_specific = {} if station.description is not None and station.description != "None": jsonstr = station.description try: big_dict = json.loads(jsonstr) standard.update(big_dict["standard"]) format_specific = big_dict["format_specific"] except json.decoder.JSONDecodeError: format_specific["description"] = jsonstr if "source_format" not in standard or standard["source_format"] is None: standard["source_format"] = "fdsn" standard["instrument_sensitivity"] = np.nan response = None if channel.response is not None: response = channel.response if hasattr(response, "instrument_sensitivity"): units = response.instrument_sensitivity.input_units if "/" in units: num, denom = units.split("/") if num.lower() not in LENGTH_CONVERSIONS: raise KeyError( f"Sensitivity input units of {units} are not supported." ) conversion = LENGTH_CONVERSIONS[num.lower()] sensitivity = response.instrument_sensitivity.value * conversion response.instrument_sensitivity.value = sensitivity standard["instrument_sensitivity"] = sensitivity else: standard[ "instrument_sensitivity"] = response.instrument_sensitivity.value return (response, standard, coords, format_specific)
def read_knet(filename): """Read Japanese KNET strong motion file. Args: filename (str): Path to possible KNET data file. kwargs (ref): Other arguments will be ignored. Returns: Stream: Obspy Stream containing three channels of acceleration data (cm/s**2). """ logging.debug("Starting read_knet.") if not is_knet(filename): raise Exception('%s is not a valid KNET file' % filename) # Parse the header portion of the file with open(filename, 'rt') as f: lines = [next(f) for x in range(TEXT_HDR_ROWS)] hdr = {} coordinates = {} standard = {} hdr['network'] = 'BO' hdr['station'] = lines[5].split()[2] logging.debug('station: %s' % hdr['station']) standard['station_name'] = '' # according to the powers that defined the Network.Station.Channel.Location # "standard", Location is a two character field. Most data providers, # including KNET here, don't provide this. We'll flag it as "--". hdr['location'] = '--' coordinates['latitude'] = float(lines[6].split()[2]) coordinates['longitude'] = float(lines[7].split()[2]) coordinates['elevation'] = float(lines[8].split()[2]) hdr['sampling_rate'] = float( re.search('\\d+', lines[10].split()[2]).group()) hdr['delta'] = 1 / hdr['sampling_rate'] standard['units'] = 'acc' dir_string = lines[12].split()[1].strip() # knet files have directions listed as N-S, E-W, or U-D, # whereas in kiknet those directions are '4', '5', or '6'. if dir_string in ['N-S', '1', '4']: hdr['channel'] = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=True) elif dir_string in ['E-W', '2', '5']: hdr['channel'] = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=False, is_north=False) elif dir_string in ['U-D', '3', '6']: hdr['channel'] = get_channel_name(hdr['sampling_rate'], is_acceleration=True, is_vertical=True, is_north=False) else: raise Exception('KNET: Could not parse direction %s' % lines[12].split()[1]) logging.debug('channel: %s' % hdr['channel']) scalestr = lines[13].split()[2] parts = scalestr.split('/') num = float(parts[0].replace('(gal)', '')) den = float(parts[1]) calib = num / den hdr['calib'] = calib duration = float(lines[11].split()[2]) hdr['npts'] = int(duration * hdr['sampling_rate']) timestr = ' '.join(lines[9].split()[2:4]) # The K-NET and KiK-Net data logger adds a 15s time delay # this is removed here sttime = datetime.strptime(timestr, TIMEFMT) - timedelta(seconds=15.0) # Shift the time to utc (Japanese time is 9 hours ahead) sttime = sttime - timedelta(seconds=9 * 3600.) hdr['starttime'] = sttime # read in the data - there is a max of 8 columns per line # the code below handles the case when last line has # less than 8 columns if hdr['npts'] % COLS_PER_LINE != 0: nrows = int(np.floor(hdr['npts'] / COLS_PER_LINE)) nrows2 = 1 else: nrows = int(np.ceil(hdr['npts'] / COLS_PER_LINE)) nrows2 = 0 data = np.genfromtxt(filename, skip_header=TEXT_HDR_ROWS, max_rows=nrows, filling_values=np.nan) data = data.flatten() if nrows2: skip_header = TEXT_HDR_ROWS + nrows data2 = np.genfromtxt(filename, skip_header=skip_header, max_rows=nrows2, filling_values=np.nan) data = np.hstack((data, data2)) nrows += nrows2 # apply the correction factor we're given in the header data *= calib # fill out the rest of the standard dictionary standard['units_type'] = get_units_type(hdr['channel']) standard['horizontal_orientation'] = np.nan standard['instrument_period'] = np.nan standard['instrument_damping'] = np.nan standard['process_time'] = '' standard['process_level'] = PROCESS_LEVELS['V1'] standard['sensor_serial_number'] = '' standard['instrument'] = '' standard['comments'] = '' standard['structure_type'] = '' if dir_string in ['1', '2', '3']: standard['structure_type'] = 'borehole' standard['corner_frequency'] = np.nan standard['units'] = 'acc' standard['source'] = SRC standard['source_format'] = 'knet' head, tail = os.path.split(filename) standard['source_file'] = tail or os.path.basename(head) # this field can be used for instrument correction # when data is in counts standard['instrument_sensitivity'] = np.nan hdr['coordinates'] = coordinates hdr['standard'] = standard # create a Trace from the data and metadata trace = StationTrace(data.copy(), Stats(hdr.copy())) response = {'input_units': 'counts', 'output_units': 'cm/s^2'} trace.setProvenance('remove_response', response) stream = StationStream(traces=[trace]) return [stream]
def _get_header_info(int_data, flt_data, lines, volume, location=""): """Return stats structure from various headers. Output is a dictionary like this: - network (str): 'LA' - station (str) - channel (str): Determined using COSMOS_ORIENTATIONS - location (str): Default is '--' - starttime (datetime) - duration (float) - sampling_rate (float) - npts (int) - coordinates: - latitude (float) - longitude (float) - elevation (float) - standard (Defaults are either np.nan or '') - horizontal_orientation (float): Rotation from north (degrees) - instrument_period (float): Period of sensor (Hz) - instrument_damping (float): Fraction of critical - process_time (datetime): Reported date of processing - process_level: Either 'V0', 'V1', 'V2', or 'V3' - station_name (str): Long form station description - sensor_serial_number (str): Reported sensor serial - instrument (str): See SENSOR_TYPES - comments (str): Processing comments - structure_type (str): See BUILDING_TYPES - corner_frequency (float): Sensor corner frequency (Hz) - units (str): See UNITS - source (str): Network source description - source_format (str): Always cosmos - format_specific - fractional_unit (float): Units of digitized acceleration in file (fractions of g) Args: int_data (ndarray): Array of integer data flt_data (ndarray): Array of float data lines (list): List of text headers (str) Returns: dictionary: Dictionary of header/metadata information """ hdr = {} coordinates = {} standard = {} format_specific = {} if volume == "V1": hdr["duration"] = flt_data[2] hdr["npts"] = int_data[27] hdr["sampling_rate"] = (hdr["npts"] - 1) / hdr["duration"] # Get required parameter number hdr["network"] = "LA" hdr["station"] = str(int_data[8]) logging.debug(f"station: {hdr['station']}") horizontal_angle = int_data[26] logging.debug(f"horizontal: {horizontal_angle}") if horizontal_angle in USC_ORIENTATIONS or (horizontal_angle >= 0 and horizontal_angle <= 360): if horizontal_angle in USC_ORIENTATIONS: channel = USC_ORIENTATIONS[horizontal_angle][1].upper() if channel == "UP" or channel == "DOWN" or channel == "VERT": channel = get_channel_name( hdr["sampling_rate"], is_acceleration=True, is_vertical=True, is_north=False, ) horizontal_angle = 0.0 elif (horizontal_angle > 315 or horizontal_angle < 45 or (horizontal_angle > 135 and horizontal_angle < 225)): channel = get_channel_name( hdr["sampling_rate"], is_acceleration=True, is_vertical=False, is_north=True, ) else: channel = get_channel_name( hdr["sampling_rate"], is_acceleration=True, is_vertical=False, is_north=False, ) horizontal_orientation = horizontal_angle hdr["channel"] = channel logging.debug(f"channel: {hdr['channel']}") else: errstr = ("USC: Not enough information to distinguish horizontal " "from vertical channels.") raise BaseException(errstr) if location == "": hdr["location"] = "--" else: hdr["location"] = location month = str(int_data[21]) day = str(int_data[22]) year = str(int_data[23]) time = str(int_data[24]) tstr = month + "/" + day + "/" + year + "_" + time starttime = datetime.strptime(tstr, "%m/%d/%Y_%H%M") hdr["starttime"] = starttime # Get coordinates lat_deg = int_data[9] lat_min = int_data[10] lat_sec = int_data[11] lon_deg = int_data[12] lon_min = int_data[13] lon_sec = int_data[14] # Check for southern hemisphere, default is northern if lines[4].find("STATION USC#") >= 0: idx = lines[4].find("STATION USC#") + 12 if "S" in lines[4][idx:]: lat_sign = -1 else: lat_sign = 1 else: lat_sign = 1 # Check for western hemisphere, default is western if lines[4].find("STATION USC#") >= 0: idx = lines[4].find("STATION USC#") + 12 if "W" in lines[4][idx:]: lon_sign = -1 else: lon_sign = 1 else: lon_sign = -1 latitude = lat_sign * _dms2dd(lat_deg, lat_min, lat_sec) longitude = lon_sign * _dms2dd(lon_deg, lon_min, lon_sec) # Since sometimes longitudes are positive in this format for data in # the western hemisphere, we "fix" it here. Hopefully no one in the # eastern hemisphere uses this format! if longitude > 0: longitude = -longitude coordinates["latitude"] = latitude coordinates["longitude"] = longitude logging.warning("Setting elevation to 0.0") coordinates["elevation"] = 0.0 # Get standard paramaters standard["units_type"] = get_units_type(hdr["channel"]) standard["horizontal_orientation"] = float(horizontal_orientation) standard["vertical_orientation"] = np.nan standard["instrument_period"] = flt_data[0] standard["instrument_damping"] = flt_data[1] standard["process_time"] = "" station_line = lines[5] station_length = int(lines[5][72:74]) name = station_line[:station_length] standard["station_name"] = name standard["sensor_serial_number"] = "" standard["instrument"] = "" standard["comments"] = "" standard["units"] = "cm/s/s" standard["structure_type"] = "" standard["process_level"] = PROCESS_LEVELS["V1"] standard["corner_frequency"] = np.nan standard[ "source"] = "Los Angeles Basin Seismic Network, University of Southern California" standard["source_format"] = "usc" # these fields can be used for instrument correction # when data is in counts standard["instrument_sensitivity"] = np.nan standard["volts_to_counts"] = np.nan # Get format specific format_specific["fractional_unit"] = flt_data[4] # Set dictionary hdr["standard"] = standard hdr["coordinates"] = coordinates hdr["format_specific"] = format_specific return hdr