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]
Beispiel #2
0
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)
Beispiel #5
0
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)
Beispiel #7
0
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)
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #10
0
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
Beispiel #12
0
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
Beispiel #13
0
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)
Beispiel #16
0
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)
Beispiel #17
0
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
Beispiel #18
0
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)
Beispiel #21
0
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]
Beispiel #22
0
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