pyart.aux_io.rainbow_psr — pyart-mch 0.4.1 documentation

# Source code for pyart.aux_io.rainbow_psr

    """
    pyart.aux_io.rainbow_psr
    ========================

    Routines for reading RAINBOW PSR files (Used by SELEX)

    .. autosummary::
        :toctree: generated/

        read_rainbow_psr
        read_rainbow_psr_spectra
        read_psr_header
        read_psr_cpi_header
        read_psr_spectra
        get_item_numbers
        get_field
        get_spectra_field
        get_Doppler_info
        get_noise_field
        convert_data
        get_library
        get_library_path

    """

    # specific modules for this function
    import os
    from warnings import warn
    import ctypes
    from copy import deepcopy

    import numpy as np

    from ..config import FileMetadata
    from ..io.common import _test_arguments
    from ..core.radar_spectra import RadarSpectra
    from ..exceptions import MissingOptionalDependency
    from ..util import subset_radar
    from .rainbow_wrl import read_rainbow_wrl

    # Check existence of required libraries
    try:
        # `read_rainbow` as of wradlib version 1.0.0
        from wradlib.io import read_Rainbow as read_rainbow
        _WRADLIB_AVAILABLE = True
    except ImportError:
        try:
            from wradlib.io import read_rainbow
            _WRADLIB_AVAILABLE = True
        except ImportError:
            _WRADLIB_AVAILABLE = False

    PSR_FIELD_NAMES = {
        'TXh': 'transmitted_power_h',
        'TXv': 'transmitted_power_v',
        'NADUhh': 'spectral_noise_power_hh_ADU',
        'NADUvv': 'spectral_noise_power_vv_ADU',
        'NADUhv': 'spectral_noise_power_hv_ADU',
        'NADUvh': 'spectral_noise_power_vh_ADU',
        'ShhADU': 'complex_spectra_hh_ADU',
        'SvvADU': 'complex_spectra_vv_ADU',
    }

    def read_rainbow_psr(filename, filenames_psr, field_names=None,
                         additional_metadata=None, file_field_names=False,
                         exclude_fields=None, include_fields=None,
                         undo_txcorr=True, cpi='mean', ang_tol=0.5, azi_min=None,
                         azi_max=None, ele_min=None, ele_max=None, rng_min=None,
                         rng_max=None, **kwargs):
        """
        Read a PSR file.

        Parameters
        ----------
        filename : str
            Name of the rainbow file to be used as reference.
        filenames_psr : list of str
            Name of the PSR files
        field_names : dict, optional
            Dictionary mapping RAINBOW field 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 during this read.
            This metadata is not used during any successive file reads unless
            explicitly included.  A value of None, the default, will not
            introduct 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 use the MDV data type names for the field names. If this
            case the field_names parameter is ignored. The field dictionary will
            likely only have a 'data' key, unless the fields are defined in
            `additional_metadata`.
        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.
        undo_txcorr: Bool
            If True the correction of the transmitted power is removed from the
            noise signal
        cpi : str
            The CPI to use. Can be 'low_prf', 'intermediate_prf', 'high_prf',
            'mean', 'all'. If 'mean' the mean within the angle step is taken
        ang_tol : float
            Tolerated angle distance between nominal radar angle and angle in
            PSR files
        azi_min, azi_max, ele_min, ele_max : float or None
            The minimum and maximum angles to keep (deg)
        rng_min, rng_max : float or None
            The minimum and maximum ranges to keep (m)

        Returns
        -------
        radar : Radar
            Radar object containing data from PSR file.

        """
        # check that wradlib is available
        if not _WRADLIB_AVAILABLE:
            raise MissingOptionalDependency(
                "wradlib is required to use read_rainbow_psr but is not installed")

        # test for non empty kwargs
        _test_arguments(kwargs)

        # create radar object used as reference
        try:
            radar = read_rainbow_wrl(filename)
            radar.fields = dict()
            radar = subset_radar(
                radar, None, rng_min=rng_min, rng_max=rng_max, ele_min=ele_min,
                ele_max=ele_max, azi_min=azi_min, azi_max=azi_max)
        except OSError as ee:
            warn(str(ee))
            warn('Unable to read file '+filename)
            return None

        # create metadata retrieval object
        if field_names is None:
            field_names = PSR_FIELD_NAMES
        filemetadata = FileMetadata('PSR', field_names, additional_metadata,
                                    file_field_names, exclude_fields,
                                    include_fields)
        dBADU_to_dBm_hh = filemetadata('dBADU_to_dBm_hh')
        dBADU_to_dBm_vv = filemetadata('dBADU_to_dBm_vv')
        mfloss_h = filemetadata('matched_filter_loss_h')
        mfloss_v = filemetadata('matched_filter_loss_v')
        pathatt = filemetadata('path_attenuation')

        cpi_header, header = read_psr_cpi_headers(filenames_psr)

        if cpi_header is None:
            return None

        # keep only valid items
        prfs = np.sort(np.unique(cpi_header['prfs']))
        items, radar = get_item_numbers(
            radar, cpi_header['azi_start'], cpi_header['azi_stop'],
            cpi_header['ele_start'], cpi_header['ele_stop'], cpi_header['prfs'],
            prfs, cpi=cpi, ang_tol=ang_tol)

        if items.size == 0:
            warn('No items matching radar object')
            return None

        for field_name in field_names:
            field_data = get_field(
                radar, cpi_header, header, items, prfs.size, field_name,
                undo_txcorr=undo_txcorr, cpi=cpi)

            if field_name in ('noisedBADU_hh', 'noisedBADU_vv', 'noisedBm_hh',
                              'noisedBm_vv', 'noisedBZ_hh', 'noisedBZ_vv'):
                field_data = get_noise_field(
                    radar, field_data, header, field_name)

            field_dict = filemetadata(field_name)
            field_dict['data'] = field_data
            radar.add_field(field_name, field_dict)

        # get further metadata
        pw_ind = header['states.spbpwidth']

        dBADU_to_dBm_hh['data'] = np.array([
            header['states.spbdbmtologoffset'][pw_ind]])
        radar.radar_calibration.update({'dBADU_to_dBm_hh': dBADU_to_dBm_hh})

        dBADU_to_dBm_vv['data'] = np.array([
            header['states.spbdpvdbmtologoffset'][pw_ind]])
        radar.radar_calibration.update({'dBADU_to_dBm_vv': dBADU_to_dBm_vv})

        mfloss_h['data'] = np.array([header['states.gdrxmfloss'][pw_ind]])
        mfloss_v['data'] = np.array([header['states.gdrxmfloss'][pw_ind]])
        radar.radar_calibration.update({'matched_filter_loss_h': mfloss_h})
        radar.radar_calibration.update({'matched_filter_loss_v': mfloss_v})

        pathatt['data'] = np.array([header['states.rspathatt']])
        radar.radar_calibration.update({'path_attenuation': pathatt})

        return radar

    def read_rainbow_psr_spectra(filename, filenames_psr, field_names=None,
                                 additional_metadata=None, file_field_names=False,
                                 exclude_fields=None, include_fields=None,
                                 undo_txcorr=True, fold=True, positive_away=True,
                                 cpi='low_prf', ang_tol=0.5, azi_min=None,
                                 azi_max=None, ele_min=None, ele_max=None,
                                 rng_min=None, rng_max=None, **kwargs):
        """
        Read a PSR file to get the complex spectra

        Parameters
        ----------
        filename : str
            Name of the rainbow file to be used as reference.
        filenames_psr : list of str
            list of PSR file names
        field_names : dict, optional
            Dictionary mapping RAINBOW field 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 during this read.
            This metadata is not used during any successive file reads unless
            explicitly included.  A value of None, the default, will not
            introduct 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 use the MDV data type names for the field names. If this
            case the field_names parameter is ignored. The field dictionary will
            likely only have a 'data' key, unless the fields are defined in
            `additional_metadata`.
        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.
        undo_txcorr: Bool
            If True the correction of the transmitted power is removed from the
            noise signal
        fold: Bool
            If True the spectra is folded so that 0-Doppler is in the middle
        positive_away: Bool
            If True the spectra is reversed so that positive velocities are
            away from the radar
        cpi : str
            The CPI to use. Can be 'low_prf', 'intermediate_prf', 'high_prf' or
            'all'
        ang_tol : float
            Tolerated angle distance between nominal radar angle and angle in
            PSR files
        azi_min, azi_max, ele_min, ele_max : float or None
            The minimum and maximum angles to keep (deg)
        rng_min, rng_max : float or None
            The minimum and maximum ranges to keep (m)

        Returns
        -------
        radar : Radar
            Radar object containing data from PSR file.

        """
        # check that wradlib is available
        if not _WRADLIB_AVAILABLE:
            raise MissingOptionalDependency(
                "wradlib is required to use read_rainbow_psr but is not installed")

        # test for non empty kwargs
        _test_arguments(kwargs)

        # create radar object used as reference
        try:
            radar = read_rainbow_wrl(filename)
            radar.fields = dict()
            rng_orig = radar.range['data']
            radar = subset_radar(
                radar, None, rng_min=rng_min, rng_max=rng_max, ele_min=ele_min,
                ele_max=ele_max, azi_min=azi_min, azi_max=azi_max)
            if radar is None:
                warn('No data within specified azimuth, elevation and'
                     ' range limits')
                return None
            ind_rng_start = np.where(rng_orig == radar.range['data'][0])[0]
            ind_rng_end = np.where(rng_orig == radar.range['data'][-1])[0]
            ind_rng = np.arange(ind_rng_start, ind_rng_end+1, dtype=int)
        except OSError as ee:
            warn(str(ee))
            warn('Unable to read file '+filename)
            return None

        # create metadata retrieval object
        if field_names is None:
            field_names = PSR_FIELD_NAMES
        filemetadata = FileMetadata('PSR', field_names, additional_metadata,
                                    file_field_names, exclude_fields,
                                    include_fields)
        dBADU_to_dBm_hh = filemetadata('dBADU_to_dBm_hh')
        dBADU_to_dBm_vv = filemetadata('dBADU_to_dBm_vv')
        mfloss_h = filemetadata('matched_filter_loss_h')
        mfloss_v = filemetadata('matched_filter_loss_v')
        pathatt = filemetadata('path_attenuation')

        cpi_header, header = read_psr_cpi_headers(filenames_psr)

        if cpi_header is None:
            return None

        # keep only valid items
        prfs = np.sort(np.unique(cpi_header['prfs']))
        items, radar = get_item_numbers(
            radar, cpi_header['azi_start'], cpi_header['azi_stop'],
            cpi_header['ele_start'], cpi_header['ele_stop'], cpi_header['prfs'],
            prfs, cpi=cpi, ang_tol=ang_tol)

        if items.size == 0:
            warn('No items matching radar object')
            return None

        fields = {}
        for field_name in field_names:
            if field_name in ('spectral_noise_power_hh_ADU',
                              'spectral_noise_power_vv_ADU',
                              'spectral_noise_power_hv_ADU',
                              'spectral_noise_power_vh_ADU'):
                field_data = get_spectral_noise(
                    radar, cpi_header, header, items, undo_txcorr=undo_txcorr)
            else:
                field_data = get_spectra_field(
                    radar, filenames_psr, cpi_header['npulses'],
                    header['items_per_file'], items, ind_rng, fold=fold,
                    positive_away=positive_away)

            field_dict = filemetadata(field_name)
            field_dict['data'] = field_data
            fields[field_name] = field_dict

        Doppler_velocity = filemetadata('Doppler_velocity')
        Doppler_frequency = filemetadata('Doppler_frequency')
        npulses = filemetadata('number_of_pulses')

        vel_data, freq_data = get_Doppler_info(
            cpi_header['prfs'][items], cpi_header['npulses'][items],
            header['states.rsplambda']*1e-2, fold=fold)

        Doppler_velocity['data'] = vel_data
        Doppler_frequency['data'] = freq_data
        npulses['data'] = cpi_header['npulses'][items]

        # get further metadata
        pw_ind = header['states.spbpwidth']

        dBADU_to_dBm_hh['data'] = np.array([
            header['states.spbdbmtologoffset'][pw_ind]])
        radar.radar_calibration.update({'dBADU_to_dBm_hh': dBADU_to_dBm_hh})

        dBADU_to_dBm_vv['data'] = np.array([
            header['states.spbdpvdbmtologoffset'][pw_ind]])
        radar.radar_calibration.update({'dBADU_to_dBm_vv': dBADU_to_dBm_vv})

        mfloss_h['data'] = np.array([header['states.gdrxmfloss'][pw_ind]])
        mfloss_v['data'] = np.array([header['states.gdrxmfloss'][pw_ind]])
        radar.radar_calibration.update({'matched_filter_loss_h': mfloss_h})
        radar.radar_calibration.update({'matched_filter_loss_v': mfloss_v})

        pathatt['data'] = np.array([header['states.rspathatt']])
        radar.radar_calibration.update({'path_attenuation': pathatt})

        return RadarSpectra(
            radar.time, radar.range, fields, radar.metadata,
            radar.scan_type, radar.latitude, radar.longitude, radar.altitude,
            radar.sweep_number, radar.sweep_mode, radar.fixed_angle,
            radar.sweep_start_ray_index, radar.sweep_end_ray_index,
            radar.azimuth, radar.elevation, npulses,
            Doppler_velocity=Doppler_velocity, Doppler_frequency=Doppler_frequency,
            rays_are_indexed=radar.rays_are_indexed,
            ray_angle_res=radar.ray_angle_res,
            instrument_parameters=radar.instrument_parameters,
            radar_calibration=radar.radar_calibration)

    def read_psr_cpi_headers(filenames):
        """
        Reads the CPI data headers contained in multiple PSR files

        Parameters
        ----------
        filenames : list of str
            Name of the PSR files

        Returns
        -------
        cpi_header, header : dict
            Dictionary containing the PSR header data and the CPI headers data

        """
        cpi_header = {
            'azi_start': [],
            'azi_stop': [],
            'ele_start': [],
            'ele_stop': [],
            'npulses': [],
            'prfs': [],
            'ngates': [],
            'tx_pwr': [],
            'noise': [],
        }

        header = None
        for filename in filenames:
            cpi_header_aux, header_aux = read_psr_cpi_header(filename)
            if cpi_header_aux is None:
                warn('File '+filename+' could not be read')
                continue

            if header is None:
                header = header_aux
                header.update({'items_per_file': [header_aux['item.count']]})
            else:
                header['item.count'] = (
                    header['item.count']+header_aux['item.count'])
                header['items_per_file'].append(header_aux['item.count'])

            cpi_header['azi_start'].extend(cpi_header_aux['azi_start'])
            cpi_header['azi_stop'].extend(cpi_header_aux['azi_stop'])
            cpi_header['ele_start'].extend(cpi_header_aux['ele_start'])
            cpi_header['ele_stop'].extend(cpi_header_aux['ele_stop'])
            cpi_header['npulses'].extend(cpi_header_aux['npulses'])
            cpi_header['prfs'].extend(cpi_header_aux['prfs'])
            cpi_header['ngates'].extend(cpi_header_aux['ngates'])
            cpi_header['tx_pwr'].extend(cpi_header_aux['tx_pwr'])

            if 'noise' not in cpi_header_aux:
                cpi_header['noise'] = None

            if cpi_header['noise'] is not None:
                cpi_header['noise'].extend(cpi_header_aux['noise'])

        cpi_header['azi_start'] = np.array(cpi_header['azi_start'])
        cpi_header['azi_stop'] = np.array(cpi_header['azi_stop'])
        cpi_header['ele_start'] = np.array(cpi_header['ele_start'])
        cpi_header['ele_stop'] = np.array(cpi_header['ele_stop'])
        cpi_header['npulses'] = np.array(cpi_header['npulses'])
        cpi_header['prfs'] = np.array(cpi_header['prfs'])
        cpi_header['ngates'] = np.array(cpi_header['ngates'])
        cpi_header['tx_pwr'] = np.array(cpi_header['tx_pwr'])
        cpi_header['noise'] = np.array(cpi_header['noise'])
        header['items_per_file'] = np.array(header['items_per_file'])

        return cpi_header, header

    def read_psr_header(filename):
        """
        Read a PSR file header.

        Parameters
        ----------
        filename : str
            Name of the PSR file

        Returns
        -------
        header : dict
            Dictionary containing the PSR header data

        """
        header = dict()
        try:
            with open(filename, 'r', newline=None, encoding='latin-1') as txtfile:
                # read first line: ARCHIVE_HEADER_START
                line = txtfile.readline()
                while 0 == 0:
                    line = txtfile.readline()

                    if 'ARCHIVE_HEADER_END' in line:
                        break

                    strings = line.split('=')
                    key = strings[0]
                    val = convert_data(strings[1].replace('\n', ''))
                    header.update({key: val})

                # read noise
                line = txtfile.readline()
                if 'PSR REDUCED' not in line:
                    warn('Header does not contain noise data')
                    return header

                noise = np.array([])
                while 0 == 0:
                    line = txtfile.readline()

                    if not line.startswith('i'):
                        break

                    strings = line.split()
                    noise = np.append(noise, float(strings[3].split('=')[1]))

                header.update({'noise': noise})

                return header
        except EnvironmentError as ee:
            warn(str(ee))
            warn('Unable to read file '+filename)
            return header

    def read_psr_cpi_header(filename):
        """
        Reads the CPI data headers contained in a PSR file

        Parameters
        ----------
        filename : str
            Name of the PSR file

        Returns
        -------
        cpi_header, header : dict
            Dictionary containing the PSR header data and the CPI headers data

        """
        # load PSR library
        psr_lib = get_library()

        c_float_p = ctypes.POINTER(ctypes.c_float)
        c_long_p = ctypes.POINTER(ctypes.c_long)

        header = read_psr_header(filename)
        nitems = header['item.count']

        azi_start = np.empty(nitems, dtype=np.float32)
        azi_stop = np.empty(nitems, dtype=np.float32)
        ele_start = np.empty(nitems, dtype=np.float32)
        ele_stop = np.empty(nitems, dtype=np.float32)
        npulses = np.empty(nitems, dtype=np.int32)
        prfs = np.empty(nitems, dtype=np.float32)
        ngates = np.empty(nitems, dtype=np.int32)
        tx_pwr = np.empty(nitems, dtype=np.float32)

        c_filename = ctypes.c_char_p(filename.encode('utf-8'))

        try:
            psr_lib.psr_getValueArrays(
                c_filename, azi_start.ctypes.data_as(c_float_p),
                azi_stop.ctypes.data_as(c_float_p),
                ele_start.ctypes.data_as(c_float_p),
                ele_stop.ctypes.data_as(c_float_p),
                npulses.ctypes.data_as(c_long_p),
                prfs.ctypes.data_as(c_float_p),
                ngates.ctypes.data_as(c_long_p),
                tx_pwr.ctypes.data_as(c_float_p))
        except EnvironmentError as ee:
            warn(str(ee))
            warn('Unable to read file '+filename)
            return None, None

        noise = None
        if 'noise' in header:
            noise = header['noise']*npulses

        cpi_header = {
            'azi_start': azi_start,
            'azi_stop': azi_stop,
            'ele_start': ele_start,
            'ele_stop': ele_stop,
            'npulses': npulses,
            'prfs': prfs,
            'ngates': ngates,
            'tx_pwr': tx_pwr.astype(np.float),
            'noise': noise.astype(np.float),
        }

        return cpi_header, header

    def read_psr_spectra(filename):
        """
        Reads the complex spectral data contained in a PSR file

        Parameters
        ----------
        filename : str
            Name of the PSR file

        Returns
        -------
        spectra : 3D complex ndArray
           The complex spectra

        """
        # load PSR library
        psr_lib = get_library()

        c_filename = ctypes.c_char_p(filename.encode('utf-8'))
        c_complex_p = np.ctypeslib.ndpointer(
            dtype=np.complex64, ndim=1, flags='C')

        cpi_header, _ = read_psr_cpi_header(filename)
        if cpi_header is None:
            return None

        nitems = cpi_header['ngates'].size
        ngates = np.max(cpi_header['ngates'])
        npulses_max = np.max(cpi_header['npulses'])

        spectra = np.ma.masked_all(
            (nitems, ngates, npulses_max), dtype=np.complex128)
        for item in range(nitems):
            npulses = cpi_header['npulses'][item]
            for gate in range(cpi_header['ngates'][item]):
                spectrum = np.empty(npulses, dtype=np.complex64)
                try:
                    psr_lib.psr_getPowerSpectrum(
                        c_filename, ctypes.c_int(item), ctypes.c_int(gate),
                        spectrum.ctypes.data_as(c_complex_p))
                    spectra[item, gate, 0:npulses] = spectrum
                except EnvironmentError as ee:
                    warn(str(ee))
                    warn('Unable to get CPI element '+str(item) +
                         ' at range gate '+str(gate)+' from file '+filename)

        return spectra

    def get_item_numbers(radar, azi_start, azi_stop, ele_start, ele_stop,
                         prf_array, prfs, cpi='low_prf', ang_tol=0.5):
        """
        Gets the item numbers to be used and eventually modify the radar object
        to accomodate more angles

        Parameters
        ----------
        radar: radar object
            the reference radar
        azi_start, azi_stop, ele_start, ele_stop : float array
            The start and stop angles of the CPI elements
        prf_array: float array
            The PRF of each CPI element
        prfs : float array
            The unique PRFs contained in the PSR file
        cpi : str
            The CPI to use. Can be 'low_prf', 'intermediate_prf', 'high_prf',
            'mean', 'all'. If 'mean' the mean within the angle step is taken.
            If 'all' the data is not filtered by PRF
        ang_tol : float
            Angle tolerance

        Returns
        -------
        items : int array
            the item number selected

        """
        if radar.scan_type == 'ppi':
            cpi_ang_center = azi_start+(azi_stop-azi_start)/2.
            cpi_fixed_angle = ele_start +(ele_stop-ele_start)/2.

            ref_ang = radar.azimuth['data']
            fixed_ang = radar.elevation['data']

        elif radar.scan_type == 'rhi':
            cpi_ang_center = ele_start +(ele_stop-ele_start)/2.
            cpi_fixed_angle = azi_start+(azi_stop-azi_start)/2.

            ref_ang = radar.elevation['data']
            fixed_ang = radar.azimuth['data']

        if prfs.size > 1 and cpi == 'all':
            items = np.array([], dtype=int)
            sweep_start = radar.sweep_start_ray_index['data']
            sweep_end = radar.sweep_end_ray_index['data']
            items_per_sweep = np.array([], dtype=int)

            items_aux = np.arange(cpi_ang_center.size)
            for i, (s_start, s_end) in enumerate(zip(sweep_start, sweep_end)):
                # only angles within fixed angle tolerance
                fixed = radar.fixed_angle['data'][i]
                ind = np.where(np.abs(cpi_fixed_angle-fixed) < ang_tol)[0]

                cpi_ang_center_aux = cpi_ang_center[ind]
                items_aux2 = items_aux[ind]

                # get angles within radar limits
                ind = np.where(np.logical_and(
                    cpi_ang_center_aux >= ref_ang[s_start]-ang_tol,
                    cpi_ang_center_aux <= ref_ang[s_end]+ang_tol))[0]

                items = np.append(items, items_aux2[ind])
                items_per_sweep = np.append(items_per_sweep, items_aux2[ind].size)

            radar = change_rays(
                radar, cpi_ang_center[items], cpi_fixed_angle[items],
                items_per_sweep)
            return items, radar

        items_aux = np.arange(cpi_ang_center.size)
        if prfs.size > 1:
            if cpi == 'low_prf':
                cpi_ang_center = cpi_ang_center[prf_array == prfs[0]]
                cpi_fixed_angle = cpi_fixed_angle[prf_array == prfs[0]]
                items_aux = items_aux[prf_array == prfs[0]]
            elif cpi == 'intermediate_prf':
                if prfs.size == 3:
                    cpi_ang_center = cpi_ang_center[prf_array == prfs[1]]
                    cpi_fixed_angle = cpi_fixed_angle[prf_array == prfs[1]]
                    items_aux = items_aux[prf_array == prfs[1]]
                else:
                    warn('Less than 3 different prfs. ' +
                         'Low prf data will be used')
                    cpi_ang_center = cpi_ang_center[prf_array == prfs[0]]
                    cpi_fixed_angle = cpi_fixed_angle[prf_array == prfs[0]]
                    items_aux = items_aux[prf_array == prfs[0]]
            elif cpi == 'high_prf':
                cpi_ang_center = cpi_ang_center[prf_array == prfs[-1]]
                cpi_fixed_angle = cpi_fixed_angle[prf_array == prfs[-1]]
                items_aux = items_aux[prf_array == prfs[-1]]
            elif cpi == 'mean':
                cpi_ang_center_aux = []
                cpi_fixed_angle_aux = []
                items_aux2 = []
                for prf in prfs:
                    cpi_ang_center_aux.append(cpi_ang_center[prf_array == prf])
                    cpi_fixed_angle_aux.append(cpi_fixed_angle[prf_array == prf])
                    items_aux2.append(items_aux[prf_array == prf])

                cpi_ang_center = cpi_ang_center_aux
                cpi_fixed_angle = cpi_fixed_angle_aux
                items_aux = items_aux2

        # get items to extract
        if prfs.size > 1 and cpi == 'mean':
            items = []
            for j, prf in enumerate(prfs):
                cpi_fixed_angle_aux = cpi_fixed_angle[j]
                cpi_ang_center_aux = cpi_ang_center[j]
                items_aux2 = items_aux[j]
                items_aux4 = np.array([], dtype=int)
                for i, ang in enumerate(ref_ang):
                    # only angles within fixed angle tolerance
                    fixed = fixed_ang[i]
                    ind = np.where(np.abs(cpi_fixed_angle_aux-fixed) < ang_tol)
                    cpi_ang_center_aux2 = cpi_ang_center_aux[ind]
                    items_aux3 = items_aux2[ind]

                    # get angle closest to reference angle
                    ind = np.argmin(np.abs(cpi_ang_center_aux2-ang))
                    items_aux4 = np.append(items_aux4, items_aux3[ind])
                items.append(items_aux4)

            return items, radar

        items = np.array([], dtype=int)
        for i, ang in enumerate(ref_ang):
            # only angles within fixed angle tolerance
            fixed = fixed_ang[i]
            ind = np.where(np.abs(cpi_fixed_angle-fixed) < ang_tol)[0]

            if ind.size == 0:
                continue

            cpi_ang_center_aux = cpi_ang_center[ind]
            items_aux2 = items_aux[ind]

            # get angle closest to reference angle
            ind = np.argmin(np.abs(cpi_ang_center_aux-ang))
            items = np.append(items, items_aux2[ind])

        return items, radar

    def get_field(radar, cpi_header, header, items, nprfs, field_name,
                  undo_txcorr=True, cpi='low_prf'):
        """
        Gets the field corresponding to the reference radar

        Parameters
        ----------
        radar: radar object
            the reference radar
        cpi_header, header : dict
            dictionaries containing the PSR file header and CPI headers data
        items : int array
            array containing the items to select
        nprfs: float
            The number of different prfs in the file
        field_name : str
            The name of the field to filter
        undo_txcorr : bool
            If True and field is a noise field the correction of the received
            signal by the transmitted power is undone
        cpi : str
            The CPI to use. Can be 'low_prf', 'intermediate_prf', 'high_prf',
            'all'. If 'all' the mean within the angle step is taken

        Returns
        -------
        field_data : 2D float array
            The PSR data in the format of the reference radar fields

        """
        if field_name in ('noisedBZ_hh', 'noisedBZ_vv', 'noisedBm_hh',
                          'noisedBm_vv', 'noisedBADU_hh', 'noisedBADU_vv'):
            field = cpi_header['noise']
            if undo_txcorr:
                field[cpi_header['tx_pwr'] > 0.] *= (
                    cpi_header['tx_pwr'][cpi_header['tx_pwr'] > 0.] /
                    header['states.spbtxpowkw'][header['states.spbpwidth']])

        elif field_name in ('transmitted_signal_power_h',
                            'transmitted_signal_power_v'):
            field = cpi_header['tx_pwr']

        if nprfs > 1 and cpi == 'mean':
            field_filt = np.ma.masked_all((radar.nrays, nprfs))
            npulses_filt = np.ma.masked_all((radar.nrays, nprfs), dtype=int)
            npulses = cpi_header['npulses']
            for i in range(nprfs):
                items_aux = items[i]
                for j, item in enumerate(items_aux):
                    field_filt[j, i] = field[item]*npulses[item]
                    npulses_filt[j, i] = npulses[item]
            field_filt = np.transpose(np.atleast_2d(
                np.ma.sum(field_filt, axis=-1)/np.ma.sum(npulses_filt, axis=-1)))
        else:
            field_filt = np.ma.masked_all((radar.nrays, 1))
            for i, item in enumerate(items):
                field_filt[i, 0] = field[item]

        return np.broadcast_to(field_filt, (radar.nrays, radar.ngates))

    def get_spectral_noise(radar, cpi_header, header, items, undo_txcorr=True):
        """
        Gets the field corresponding to the reference radar

        Parameters
        ----------
        radar: radar object
            the reference radar
        cpi_header, header : dict
            dictionaries containing the PSR file header and CPI headers data
        items : int array
            array containing the items to select
        field_name : str
            The name of the field to filter
        undo_txcorr : bool
            If True and field is a noise field the correction of the received
            signal by the transmitted power is undone

        Returns
        -------
        field_data : 2D float array
            The PSR data in the format of the reference radar fields

        """
        field = cpi_header['noise']/cpi_header['npulses']
        if undo_txcorr:
            field[cpi_header['tx_pwr'] > 0.] *= (
                cpi_header['tx_pwr'][cpi_header['tx_pwr'] > 0.] /
                header['states.spbtxpowkw'][header['states.spbpwidth']])

        field_filt = np.ma.masked_all((radar.nrays, 1, 1))
        for i, item in enumerate(items):
            field_filt[i, 0, 0] = field[item]

        npulses_max = np.max(cpi_header['npulses'][items])

        return np.broadcast_to(
            field_filt, (radar.nrays, radar.ngates, npulses_max))

    def get_spectra_field(radar, filenames, npulses, items_per_file, items,
                          ind_rng, fold=True, positive_away=True):
        """
        Gets the field corresponding to the reference radar

        Parameters
        ----------
        radar: radar object
            the reference radar
        filename : str
            name of the PSR file
        npulses : int array
            array containing the number of pulses for each item
        items_per_file : int array
            array containing the number of items in each PSR file
        items : int array
            array containing the items to select
        ind_rng : int array
            array containing the indices to the range gates to select
        fold : Bool
            If True the spectra is folded
        positive_away : Bool
            If True positive Doppler velocities are way from the radar

        Returns
        -------
        spectra : 3D complex float array
            The complex spectra field

        """
        # load PSR library
        psr_lib = get_library()

        c_complex_p = np.ctypeslib.ndpointer(
            dtype=np.complex64, ndim=1, flags='C')

        npulses_max = np.max(npulses[items])

        spectra = np.ma.masked_all(
            (radar.nrays, radar.ngates, npulses_max), dtype=np.complex64)

        accu_items = np.cumsum(items_per_file)
        for ray, item in enumerate(items):
            # get file to read and item number within file
            ind = np.where(accu_items >= item)[0]
            if ind.size == 0 or ind[0] == 0:
                item_aux = item
                ind = 0
            else:
                ind = ind[0]
                item_aux = item - accu_items[ind-1]

            c_filename = ctypes.c_char_p(filenames[ind].encode('utf-8'))
            for rng, gate in enumerate(ind_rng):
                npulses_item = npulses[item]
                spectrum = np.empty(npulses_item, dtype=np.complex64)
                try:
                    psr_lib.psr_getPowerSpectrum(
                        c_filename, ctypes.c_int(item_aux), ctypes.c_int(gate),
                        spectrum.ctypes.data_as(c_complex_p))

                    if fold:
                        nfold = int(np.ceil(npulses_item/2.))
                        spectrum = np.append(spectrum[nfold:], spectrum[0:nfold])
                    if positive_away:
                        spectrum = spectrum[::-1]

                    spectra[ray, rng, 0:npulses_item] = spectrum
                except EnvironmentError as ee:
                    warn(str(ee))
                    warn('Unable to get CPI element '+str(item) +
                         ' at range gate '+str(gate)+' from file '+filenames[ind])

        spectra = np.ma.masked_equal(spectra, 0.)

        return spectra

    def get_Doppler_info(prfs, npulses, wavelength, fold=True):
        """
        Gets the Doppler information

        Parameters
        ----------
        prfs: float array
            the PRF at each ray
        npulses : float array
            the number of pulses per ray
        wavelength : float
            the radar wavelength [m]
        fold : Bool
            If True the spectra is folded

        Returns
        -------
        Doppler_velocity, Doppler_frequency : 2D float array
            The Doppler velocity and Doppler frequency bins for each ray

        """
        nrays = npulses.size
        npulses_max = np.max(npulses)
        freq_res = prfs/npulses
        vel_res = freq_res*wavelength/2.

        Doppler_frequency = np.ma.masked_all((nrays, npulses_max))
        Doppler_velocity = np.ma.masked_all((nrays, npulses_max))
        for ray in range(nrays):
            pulses_ray = np.arange(npulses[ray])
            npulses_ray = pulses_ray.size
            if fold:
                nfold = int(np.ceil(npulses_ray/2.))
                pulses_ray = np.append(
                    pulses_ray[nfold:]-npulses_ray, pulses_ray[0:nfold])

            Doppler_frequency[ray, 0:npulses_ray] = pulses_ray*freq_res[ray]
            Doppler_velocity[ray, 0:npulses_ray] = pulses_ray*vel_res[ray]

        return Doppler_velocity, Doppler_frequency

    def get_noise_field(radar, field_data, header, field_name):
        """
        Puts the noise field in the desired units

        Parameters
        ----------
        radar: radar object
            the reference radar
        field_data : 2D float array
            The PSR data in the format of the reference radar fields
        header : dict
            Dictionary containing the PSR file metadata
        field_name : str
            The name of the field

        Returns
        -------
        field_data : 2D float array
            The PSR data in the format of the reference radar fields

        """
        field_data = 10.*np.log10(field_data)

        if field_name in ('noisedBADU_hh', 'noisedBADU_vv'):
            return field_data

        pw_ind = header['states.spbpwidth']
        if field_name in ('noisedBZ_hh', 'noisedBm_hh'):
            dBadu2dBm = header['states.spbdbmtologoffset'][pw_ind]
        else:
            dBadu2dBm = header['states.spbdpvdbmtologoffset'][pw_ind]

        field_data = field_data+dBadu2dBm

        if field_name in ('noisedBm_hh', 'noisedBm_vv'):
            return field_data

        if field_name == 'noisedBZ_hh':
            radconst = header['states.rspdphradconst'][pw_ind]
        else:
            radconst = header['states.rspdpvradconst'][pw_ind]
        mfloss = header['states.gdrxmfloss'][pw_ind]
        pathatt = header['states.rspathatt']

        rangeKm = np.broadcast_to(
            np.atleast_2d(radar.range['data']/1000.), (radar.nrays, radar.ngates))

        field_data += radconst+mfloss+pathatt*rangeKm+20.*np.log10(rangeKm)

        return field_data

    def convert_data(values):
        """
        Converts an string of values into the corresponding format

        Parameters
        ----------
        values: str
            string containg the values to convert

        Returns
        -------
        values : int, float, str or 1D array of int, float or str
            The converted values

        """
        if ' ' in values[1:-1]:
            values = values.split()
            try:
                return np.array(values, dtype=int)
            except ValueError:
                try:
                    return np.array(values, dtype=float)
                except ValueError:
                    return np.array(values, dtype=str)

        try:
            return int(values)
        except ValueError:
            try:
                return float(values)
            except ValueError:
                return values

    def get_library():
        """
            return the link to C-shared library

            Returns
            -------
            psr_lib : link
                loaded PSR C-library

        """
        try:
            library_path = get_library_path()
        except MissingOptionalDependency:
            raise MissingOptionalDependency(" PSR library path NOT defined")

        try:
            psr_lib = ctypes.cdll.LoadLibrary(library_path+'/'+'libDX50.so')
        except OSError as ee:
            warn(str(ee))
            raise MissingOptionalDependency('Unable to load PSR library')

        return psr_lib

    def get_library_path():
        """
        find valid library path

        Returns
        -------
        psr_lib_path : str
            library path

        """
        # Check if path is correct
        library_paths = [
            os.environ.get('PSRLIB_PATH'),
            os.path.expanduser('~')+'/pyrad/src/libDX50/lib/']
        library_path = ''
        for p in library_paths:
            if p is not None:
                if os.path.isdir(p):
                    library_path = p
                    break

        if not library_path:
            raise MissingOptionalDependency(" PSR library path NOT defined")

        return library_path

    def change_rays(radar, moving_angle, fixed_angle, rays_per_sweep):
        """
        Modify the radar object to accomodate new rays

        Parameters
        ----------
        radar : radar object
            the radar to modify
        moving_angle : float array
            The moving angles
        fixed_angle : float array
            The fixed angles
        rays_per_sweep : array of ints
            The number of rays per sweep

        Returns
        -------
        new_radar : radar object
            The modified radar

        """
        new_radar = deepcopy(radar)
        new_radar.nrays = moving_angle.size
        if radar.scan_type == 'ppi':
            new_radar.azimuth['data'] = moving_angle
            new_radar.elevation['data'] = fixed_angle
        elif radar.scan_type == 'rhi':
            new_radar.azimuth['data'] = fixed_angle
            new_radar.elevation['data'] = moving_angle

        ray_factor = int(new_radar.nrays/radar.nrays)

        # change time
        time_res = np.append(
            (radar.time['data'][1:]-radar.time['data'][:-1])/ray_factor,
            (radar.time['data'][-1]-radar.time['data'][-2])/ray_factor)
        time_sum = np.cumsum(
            np.reshape(
                np.repeat(time_res, ray_factor), (radar.nrays, ray_factor)),
            axis=1).flatten()
        new_radar.time['data'] = (
            np.repeat(radar.time['data'], ray_factor)+ time_sum)

        new_radar.sweep_start_ray_index['data'] = np.append(
            0, np.cumsum(rays_per_sweep[:-1]))
        new_radar.sweep_end_ray_index['data'] = np.cumsum(rays_per_sweep-1)
        new_radar.init_rays_per_sweep()
        if new_radar.ray_angle_res is not None:
            new_radar.ray_angle_res['data'] /= ray_factor

        new_radar.init_gate_x_y_z()
        new_radar.init_gate_longitude_latitude()
        new_radar.init_gate_altitude()

        return new_radar