pyart.io.uf — pyart-mch 0.4.1 documentation

# Source code for pyart.io.uf

    """
    pyart.io.uf
    ===========

    Reading of Universal format (UF) files.

    .. autosummary::
        :toctree: generated/

        read_uf
        _get_scan_type
        _get_instrument_parameters

    """

    import warnings

    import numpy as np
    from netCDF4 import date2num

    from ..config import FileMetadata, get_fillvalue
    from ..core.radar import Radar
    from .common import make_time_unit_str, _test_arguments, prepare_for_read
    from .uffile import UFFile

    _LIGHT_SPEED = 2.99792458e8  # speed of light in meters per second
    _UF_SWEEP_MODES = {
        0: 'calibration',
        1: 'ppi',
        2: 'coplane',
        3: 'rhi',
        4: 'vpt',
        5: 'target',
        6: 'manual',
        7: 'idle',
        8: 'ppi',   # RadX used this to indicate surveillance PPI scans
    }

    _SWEEP_MODE_STR = {
        'calibration': 'calibration',
        'ppi': 'azimuth_surveillance',
        'coplane': 'coplane',
        'rhi': 'rhi',
        'vpt': 'vertical_pointing',
        'target': 'pointing',
        'manual': 'manual',
        'idle': 'idle',
    }

    def read_uf(filename, field_names=None, additional_metadata=None,
                file_field_names=False, exclude_fields=None,
                include_fields=None, delay_field_loading=False, **kwargs):
        """
        Read a UF File.

        Parameters
        ----------
        filename : str or file-like
            Name of Universal format file to read data from.
        field_names : dict, optional
            Dictionary mapping UF data type names to radar field names. If a
            data type found in the file does not appear in this dictionary or has
            a value of None it will not be placed in the radar.fields dictionary.
            A value of None, the default, will use the mapping defined in the
            Py-ART configuration file.
        additional_metadata : dict of dicts, optional
            Dictionary of dictionaries to retrieve metadata from during this read.
            This metadata is not used during any successive file reads unless
            explicitly included. A value of None, the default, will not
            introduce any addition metadata and the file specific or default
            metadata as specified by the Py-ART configuration file will be used.
        file_field_names : bool, optional
            True to force the use of the field names from the file in which
            case the `field_names` parameter is ignored. False will use to
            `field_names` parameter to rename fields.
        exclude_fields : list or None, optional
            List of fields to exclude from the radar object. This is applied
            after the `file_field_names` and `field_names` parameters. Set
            to None to include all fields specified by include_fields.
        include_fields : list or None, optional
            List of fields to include from the radar object. This is applied
            after the `file_field_names` and `field_names` parameters. Set
            to None to include all fields not specified by exclude_fields.
        delay_field_loading : bool
            This option is not implemented in the function but included for
            compatibility.

        Returns
        -------
        radar : Radar
            Radar object.

        """
        # test for non empty kwargs
        _test_arguments(kwargs)

        # create metadata retrieval object
        filemetadata = FileMetadata('uf', field_names, additional_metadata,
                                    file_field_names, exclude_fields,
                                    include_fields)

        # Open UF file and get handle
        ufile = UFFile(prepare_for_read(filename))
        first_ray = ufile.rays[0]

        # time
        dts = ufile.get_datetimes()
        units = make_time_unit_str(min(dts))
        time = filemetadata('time')
        time['units'] = units
        time['data'] = date2num(dts, units).astype('float32')

        # range
        _range = filemetadata('range')
        # assume that the number of gates and spacing from the first ray is
        # representative of the entire volume
        field_header = first_ray.field_headers[0]
        ngates = field_header['nbins']
        step = field_header['range_spacing_m']
        # this gives distances to the center of each gate, remove step/2 for start
        start = (field_header['range_start_km'] * 1000. +
                 field_header['range_start_m'] + step / 2.)
        _range['data'] = np.arange(ngates, dtype='float32') * step + start
        _range['meters_to_center_of_first_gate'] = start
        _range['meters_between_gates'] = step

        # latitude, longitude and altitude
        latitude = filemetadata('latitude')
        longitude = filemetadata('longitude')
        altitude = filemetadata('altitude')
        lat, lon, height = first_ray.get_location()
        latitude['data'] = np.array([lat], dtype='float64')
        longitude['data'] = np.array([lon], dtype='float64')
        altitude['data'] = np.array([height], dtype='float64')

        # metadata
        metadata = filemetadata('metadata')
        metadata['original_container'] = 'UF'
        metadata['site_name'] = first_ray.mandatory_header['site_name']
        metadata['radar_name'] = first_ray.mandatory_header['radar_name']

        # sweep_start_ray_index, sweep_end_ray_index
        sweep_start_ray_index = filemetadata('sweep_start_ray_index')
        sweep_end_ray_index = filemetadata('sweep_end_ray_index')
        sweep_start_ray_index['data'] = ufile.first_ray_in_sweep
        sweep_end_ray_index['data'] = ufile.last_ray_in_sweep

        # sweep number
        sweep_number = filemetadata('sweep_number')
        sweep_number['data'] = np.arange(ufile.nsweeps, dtype='int32')

        # scan_type
        scan_type = _get_scan_type(first_ray)

        # sweep_mode
        sweep_mode = filemetadata('sweep_mode')
        sweep_mode['data'] = np.array(
            ufile.nsweeps * [_SWEEP_MODE_STR[scan_type]], dtype='S')

        # elevation
        elevation = filemetadata('elevation')
        elevation['data'] = ufile.get_elevations()

        # azimuth
        azimuth = filemetadata('azimuth')
        azimuth['data'] = ufile.get_azimuths()

        # fixed_angle
        fixed_angle = filemetadata('fixed_angle')
        fixed_angle['data'] = ufile.get_sweep_fixed_angles()

        # fields
        fields = {}
        for uf_field_number, uf_field_dic in enumerate(first_ray.field_positions):
            uf_field_name = uf_field_dic['data_type'].decode('ascii')
            field_name = filemetadata.get_field_name(uf_field_name)
            if field_name is None:
                continue
            field_dic = filemetadata(field_name)
            field_dic['data'] = ufile.get_field_data(uf_field_number)
            field_dic['_FillValue'] = get_fillvalue()
            fields[field_name] = field_dic

        # instrument_parameters
        instrument_parameters = _get_instrument_parameters(ufile, filemetadata)

        # scan rate
        scan_rate = filemetadata('scan_rate')
        scan_rate['data'] = ufile.get_sweep_rates()

        ufile.close()
        return Radar(
            time, _range, fields, metadata, scan_type,
            latitude, longitude, altitude,
            sweep_number, sweep_mode, fixed_angle, sweep_start_ray_index,
            sweep_end_ray_index,
            azimuth, elevation,
            scan_rate=scan_rate,
            instrument_parameters=instrument_parameters)

    def _get_scan_type(ufray):
        """ Ruturn the scan type of a UF ray. """
        uf_sweep_mode = ufray.mandatory_header['sweep_mode']
        if uf_sweep_mode in _UF_SWEEP_MODES:
            return _UF_SWEEP_MODES[uf_sweep_mode]
        else:
            warnings.warn('Unknown sweep mode, defaulting to ppi')
            return 'ppi'

    def _get_instrument_parameters(ufile, filemetadata):
        """ Return a dictionary containing instrument parameters. """

        # pulse width
        pulse_width = filemetadata('pulse_width')
        pulse_width['data'] = ufile.get_pulse_widths() / _LIGHT_SPEED  # m->sec

        # assume that the parameters in the first ray represent the beam widths,
        # bandwidth and frequency in the entire volume
        first_ray = ufile.rays[0]
        field_header = first_ray.field_headers[0]
        beam_width_h = field_header['beam_width_h'] / 64.
        beam_width_v = field_header['beam_width_v'] / 64.
        bandwidth = field_header['bandwidth'] / 16. * 1.e6
        wavelength_cm = field_header['wavelength_cm'] / 64.
        if wavelength_cm == 0:
            warnings.warn('Invalid wavelength, frequency set to default value.')
            wavelength_hz = 9999.0
        else:
            wavelength_hz = _LIGHT_SPEED / (wavelength_cm / 100.)

        # radar_beam_width_h
        radar_beam_width_h = filemetadata('radar_beam_width_h')
        radar_beam_width_h['data'] = np.array([beam_width_h], dtype='float32')

        # radar_beam_width_v
        radar_beam_width_v = filemetadata('radar_beam_width_v')
        radar_beam_width_v['data'] = np.array([beam_width_v], dtype='float32')

        # radar_receiver_bandwidth
        radar_receiver_bandwidth = filemetadata('radar_receiver_bandwidth')
        radar_receiver_bandwidth['data'] = np.array([bandwidth], dtype='float32')

        # polarization_mode
        polarization_mode = filemetadata('polarization_mode')
        polarization_mode['data'] = ufile.get_sweep_polarizations()

        # frequency
        frequency = filemetadata('frequency')
        frequency['data'] = np.array([wavelength_hz], dtype='float32')

        # prt
        prt = filemetadata('prt')
        prt['data'] = ufile.get_prts() / 1e6  # us->sec

        instrument_parameters = {
            'pulse_width': pulse_width,
            'radar_beam_width_h': radar_beam_width_h,
            'radar_beam_width_v': radar_beam_width_v,
            'radar_receiver_bandwidth': radar_receiver_bandwidth,
            'polarization_mode': polarization_mode,
            'frequency': frequency,
            'prt': prt,
        }

        # nyquist velocity if defined
        nyquist_velocity = filemetadata('nyquist_velocity')
        nyquist_velocity['data'] = ufile.get_nyquists()
        if nyquist_velocity['data'] is not None:
            instrument_parameters['nyquist_velocity'] = nyquist_velocity

        return instrument_parameters