Source code for pyart.aux_io.rad4alp_iq_reader

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

Routines for reading RAINBOW PSR files (Used by SELEX)

.. autosummary::
    :toctree: generated/

    read_iq
    read_iq_data

"""

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

import numpy as np
from scipy.constants import speed_of_light

from ..config import FileMetadata
from ..io.common import _test_arguments
from ..core.radar_spectra import RadarSpectra
from ..util import subset_radar
from .metranet_reader import read_metranet
from .rainbow_psr import get_Doppler_info

IQ_FIELD_NAMES = {
    'IQhhADU': 'IQ_hh_ADU',
    'IQvvADU': 'IQ_vv_ADU',
}


[docs]def read_iq(filename, filenames_iq, field_names=None, additional_metadata=None, file_field_names=False, exclude_fields=None, include_fields=None, reader='C', nbytes=4, prf=None, ang_tol=0.4, noise_h=None, noise_v=None, rconst_h=None, rconst_v=None, radconst_h=None, radconst_v=None, mfloss_h=1., mfloss_v=1., azi_min=None, azi_max=None, ele_min=None, ele_max=None, rng_min=None, rng_max=None, **kwargs): """ Read a rad4alp IQ file. Parameters ---------- filename : str Name of the METRANET file to be used as reference. filenames_iq : list of str Name of the IQ 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. reader : str The library used to read the METRANET reference file. Can be either 'C' or 'python' nbytes : int The number of bytes used to store the data in numpy arrays, e.g. if nbytes=4 then floats are going to be stored as np.float32 prf : float The PRF of the read scan ang_tol : float Tolerated angle distance between nominal radar angle and angle in PSR files noise_h, noise_v : float The estimated H(V) noise power (ADU) of the scan rconst_h, rconst_v : float Dynamical factor used in the conversion from dBADU to dBm/dBZ radconst_h, radconst_v : float The H(V) radar constant mfloss_h, mfloss_v : float The H(V) matched filter losses in the receiver (dB) 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. """ # test for non empty kwargs _test_arguments(kwargs) # create radar object used as reference try: radar = read_metranet( filename, field_names={'ZH': 'reflectivity'}, reader=reader, nbytes=nbytes) radar.fields = dict() rng_orig = radar.range['data'] radar = subset_radar( radar, None, rng_min=None, rng_max=None, 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 = IQ_FIELD_NAMES filemetadata = FileMetadata('IQ', field_names, additional_metadata, file_field_names, exclude_fields, include_fields) npulses = filemetadata('number_of_pulses') dBADU_to_dBm_hh = filemetadata('dBADU_to_dBm_hh') dBADU_to_dBm_vv = filemetadata('dBADU_to_dBm_vv') mfloss_h_dict = filemetadata('matched_filter_loss_h') mfloss_v_dict = filemetadata('matched_filter_loss_v') prt = filemetadata('prt') # Keep only IQ files with data from radar object filenames_iq_aux, npulses['data'] = get_valid_rays( filenames_iq, radar.azimuth['data'], radar.fixed_angle['data'][0], ang_tol=ang_tol) if filenames_iq_aux is None: return None # Select valid range gates if rng_min is None: rng_min = 0. if rng_max is None: rng_max = np.max(radar.range['data']) ind_rng = np.where(np.logical_and( radar.range['data'] >= rng_min, radar.range['data'] <= rng_max))[0] if ind_rng.size == 0: warn('No range bins between '+str(rng_min)+' and '+str(rng_max)+' m') return None if ind_rng.size == radar.ngates: ind_rng = None # Read IQ data if 'IQ_hh_ADU' in field_names or 'IQ_vv_ADU' in field_names: npulses_max = np.max(npulses['data']) data_hh = np.ma.masked_all( (radar.nrays, radar.ngates, npulses_max), dtype=np.complex64) data_vv = np.ma.masked_all( (radar.nrays, radar.ngates, npulses_max), dtype=np.complex64) for i, npuls in enumerate(npulses['data']): data_ray_hh, data_ray_vv = read_iq_data( filenames_iq_aux[i], radar.ngates, npuls) if data_ray_hh is not None: data_hh[i, :, 0:npuls] = data_ray_hh if data_ray_vv is not None: data_vv[i, :, 0:npuls] = data_ray_vv if ind_rng is not None: data_hh = data_hh[:, ind_rng, :] data_vv = data_vv[:, ind_rng, :] # cut radar range if ind_rng is not None: radar.range['data'] = radar.range['data'][ind_rng] radar.init_gate_x_y_z() radar.init_gate_longitude_latitude() radar.init_gate_altitude() radar.ngates = ind_rng.size # Prepare output fields fields = {} for field_name in field_names: field_dict = filemetadata(field_name) if field_name == 'IQ_hh_ADU': field_dict['data'] = data_hh elif field_name == 'IQ_vv_ADU': field_dict['data'] = data_vv elif field_name == 'IQ_noise_power_hh_ADU' and noise_h is not None: npulses_max = np.max(npulses['data']) field_dict['data'] = np.ma.masked_all( (radar.nrays, radar.ngates, npulses_max), dtype=np.float) for i, npuls in enumerate(npulses['data']): field_dict['data'][i, :, 0:npuls] = noise_h elif field_name == 'IQ_noise_power_vv_ADU' and noise_v is not None: npulses_max = np.max(npulses['data']) field_dict['data'] = np.ma.masked_all( (radar.nrays, radar.ngates, npulses_max), dtype=np.float) for i, npuls in enumerate(npulses['data']): field_dict['data'][i, :, 0:npuls] = noise_v else: warn('Field name '+field_name+' not known') continue fields[field_name] = field_dict # get further metadata if rconst_h is not None and radconst_h is not None and mfloss_h is not None: dBADU_to_dBm_hh['data'] = np.array([rconst_h-40.-radconst_h-mfloss_h]) if radar.radar_calibration is None: radar.radar_calibration = {'dBADU_to_dBm_hh': dBADU_to_dBm_hh} else: radar.radar_calibration.update( {'dBADU_to_dBm_hh': dBADU_to_dBm_hh}) else: warn('Unable to compute dBADU_to_dBm_hh. Missing data') if rconst_v is not None and radconst_v is not None and mfloss_v is not None: dBADU_to_dBm_vv['data'] = np.array([rconst_v-40.-radconst_v-mfloss_v]) if radar.radar_calibration is None: radar.radar_calibration = {'dBADU_to_dBm_vv': dBADU_to_dBm_vv} else: radar.radar_calibration.update( {'dBADU_to_dBm_vv': dBADU_to_dBm_vv}) else: warn('Unable to compute dBADU_to_dBm_vv. Missing data') if mfloss_h is not None: mfloss_h_dict['data'] = np.array([mfloss_h]) if radar.radar_calibration is None: radar.radar_calibration = {'matched_filter_loss_h': mfloss_h_dict} else: radar.radar_calibration.update( {'matched_filter_loss_h': mfloss_h_dict}) else: warn('matched_filter_loss_h not known') if mfloss_v is not None: mfloss_v_dict['data'] = np.array([mfloss_v]) if radar.radar_calibration is None: radar.radar_calibration = {'matched_filter_loss_v': mfloss_v_dict} else: radar.radar_calibration.update( {'matched_filter_loss_v': mfloss_v_dict}) else: warn('matched_filter_loss_v not known') if prf is not None: prt['data'] = np.zeros(radar.nrays)+1./prf if radar.instrument_parameters is None: radar.instrument_parameters = {'prt': prt} else: radar.instrument_parameters.update({'prt': prt}) else: warn('prt not known') Doppler_velocity = None Doppler_frequency = None if (prf is not None and radar.instrument_parameters is not None and 'frequency' in radar.instrument_parameters): Doppler_velocity = filemetadata('Doppler_velocity') Doppler_frequency = filemetadata('Doppler_frequency') vel_data, freq_data = get_Doppler_info( np.zeros(radar.nrays)+prf, npulses['data'], speed_of_light/radar.instrument_parameters['frequency']['data'][0], fold=True) Doppler_velocity['data'] = vel_data Doppler_frequency['data'] = freq_data 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_iq_data(filename, ngates, npulses, nchannels=2): """ Reads the IQ data Parameters ---------- filename : str Name of file containing the IQ data of a ray ngates : int Number of gates in ray npulses : int Number of pulses in ray nchannels : int Number of channels in file Returns ------- data_hh, data_vv : 2D array arrays containing the HH and VV channels IQ data """ try: with open(filename, 'rb') as file: # file.readline() data = np.fromfile( file, dtype=np.complex64, count=ngates*npulses*nchannels) if data.size != ngates*npulses*nchannels: warn('Data file containing '+str(data.size)+' elements. ' + str(ngates*npulses*nchannels)+' expected.') return None, None data = np.reshape(data, [ngates, nchannels, npulses], order='F') data_hh = data[:, 0, :] data_vv = data[:, 1, :] return data_hh, data_vv except EnvironmentError as ee: warn(str(ee)) warn('Unable to read file '+filename) return None, None def get_valid_rays(filenames_iq, ref_azi, ref_ele, ang_tol=0.4): """ Selects the IQ files corresponding to each ray azimuth and gets the number of pulses corresponding to each ray Parameters ---------- filenames_iq : list of str List of files containing the IQ information of each ray ref_azi : float array The radar azimuths (deg) ref_ele : int The elevation of the current scan (deg) ang_tol : float The angle tolerance [deg] between the reference azimuth angle of the radar object and that of the IQ file Returns ------- filenames_iq_out : array of strings List of files containing IQ information for valid rays npulses_vec : array of ints Number of pulses for each ray """ # Get azimuth from file name azi_vec = [] for filename_iq in filenames_iq: azi_vec.append(float(os.path.basename(filename_iq)[39:42])+0.5) azi_vec = np.array(azi_vec) filenames_aux = np.array(deepcopy(filenames_iq)) # Keep only IQ files with data from radar object filenames_iq_out = [] npulses_vec = [] for azi in ref_azi: ind = np.where(np.logical_and( azi_vec >= azi-ang_tol, azi_vec <= azi+ang_tol))[0] if ind.size == 0: warn('No file found for azimuth angle '+str(azi)) return None, None if ind.size > 1: filenames_aux2 = filenames_aux[ind] ele_vec = [] for fname in filenames_aux2: ele_vec.append(float(os.path.basename(fname)[34:38])/100.) delta_ele_vec = np.abs(ele_vec-ref_ele) ind = ind[np.argmin(delta_ele_vec)] else: ind = ind[0] filenames_iq_out.append(filenames_aux[ind]) npulses_vec.append( int(int(os.path.basename(str(filenames_aux[ind]))[49:54])/2)) return np.array(filenames_iq_out), np.array(npulses_vec)