def phase_to_src(self, src='ZEN', generate_uvw=True): """ Apply phase corrections to phase to source. Generates new UVW coordinates, then applies geometric delay (W component) to phase flux data to the new phase center. Parameters ---------- src (str): Source to phase to. Sources are three capital letters: ZEN: Zenith (RA will be computed from timestamps) CYG: Cygnus A CAS: Cassiopeia A TAU: Taurus A VIR: Virgo A generate_uvw (bool): Skip regeneration of UVW coords? """ self.pp.h1("Phasing flux data to %s" % src) current_tgs = self.d_uv_data["WW"] if generate_uvw is True: self.generateUVW(src, update_src=True) freqs = self.formatFreqs() w = 2 * np.pi * freqs # Angular freq # Note WW *is* the geometric delay tg new_tgs = self.d_uv_data["WW"] try: assert self.d_uv_data["FLUX"].dtype == 'float32' except AssertionError: raise RuntimeError("Unexpected data type for FLUX: %s" % str(self.d_uv_data["FLUX"].dtype)) flux = self.d_uv_data["FLUX"].view('complex64') bls = set(self.d_uv_data["BASELINE"]) if not 257 in bls: bls, ant_arr = coords.generateBaselineIds(self.n_ant, autocorrs=False) else: bls, ant_arr = coords.generateBaselineIds(self.n_ant, autocorrs=True) n_int = len(flux) / len(bls) for nn in range(n_int): for ii in range(len(bls)): # Compute phases for X and Y pol on antennas A and B tg = new_tgs[nn * len(bls) + ii] - current_tgs[nn * len(bls) + ii] #if ant1 < ant2: # tg *= -1 # Compensate for geometry p = np.exp(-1j * w * tg) # Needs to be -ve as compensating delay phase_corrs = np.column_stack((p, p, p, p)).flatten() flux[nn * len(bls) + ii] = flux[nn * len(bls) + ii] * phase_corrs # Now we have applied geometric delays, we need to # convert from viewing as complex to viewing as floats assert flux.dtype == 'complex64' self.d_uv_data["FLUX"] = flux.view('float32')
def apply_cable_delays(self, debug=True): """ Apply antenna cable delays Each cable introduces a phase shift of phi = 2 pi f t Visibility is VpVq*, so we need to apply exp(-i (phip - phiq)) to compensate for cable delay """ self.pp.h1("Applying cable delays") #t0 = time.time() # Load antenna Electrical Lengths sol = ledafits_config.SPEED_OF_LIGHT try: els = self.z_elength["EL"] except: raise RuntimeError("No cable delay data for telescope '%s'" % self.telescope) els = np.array(els) tdelts = els / sol if debug: print "X-POL (ns) \tY-POL (ns)" for line in tdelts: print "%2.2f \t%2.2f" % (line[0] * 1e9, line[1] * 1e9) # Generate frequency array from metadata freqs = self.formatFreqs() # Compute phase delay for each antenna pair try: assert self.d_uv_data["FLUX"].dtype == 'float32' except AssertionError: raise RuntimeError("Unexpected data type for FLUX: %s" % str(self.d_uv_data["FLUX"].dtype)) # Convert the data to complex values flux = self.d_uv_data["FLUX"].view('complex64') # Pre-compute the phasing information bls, ant_arr = coords.generateBaselineIds(self.n_ant) w = 2 * np.pi * freqs # Angular freq delay_corrs = np.zeros((4, len(bls), len(freqs)), dtype=flux.dtype) for ii in range(len(bls)): ant1, ant2 = ant_arr[ii] bl = bls[ii] td1, td2 = tdelts[ant1 - 1, :], tdelts[ant2 - 1, :] # Compute phases for X and Y pol on antennas A and B pxa, pya, pxb, pyb = w * td1[0], w * td1[1], w * td2[0], w * td2[1] # Corrections require negative sign (otherwise reapplying delays) delay_corrs[0, ii, :] = np.exp(1j * (pxa - pxb)) # XX delay_corrs[1, ii, :] = np.exp(1j * (pya - pyb)) # YY delay_corrs[2, ii, :] = np.exp(1j * (pxa - pyb)) # XY delay_corrs[3, ii, :] = np.exp(1j * (pya - pxb)) # YX n_int = len(flux) / len(bls) for nn in range(n_int): for ii in range(len(bls)): e_xx = delay_corrs[0, ii, :].flatten() e_yy = delay_corrs[1, ii, :].flatten() e_xy = delay_corrs[2, ii, :].flatten() e_yx = delay_corrs[3, ii, :].flatten() phase_corrs = np.column_stack((e_xx, e_yy, e_xy, e_yx)).flatten() flux[nn * len(bls) + ii] = flux[nn * len(bls) + ii] * phase_corrs assert flux.dtype == 'complex64' self.d_uv_data["FLUX"] = flux.view('float32')
def generateUVW(self, src='ZEN', update_src=True, conjugate=False, use_stored=False): """ Generate UVW coordinates based on timestamps and array geometry Updates UVW coordinates to phase to a given source. Uses pyEphem observer along with methods is lib.uvw for computations src (str): Source to phase to. Sources are three capital letters: ZEN: Zenith (RA will be computed from timestamps) CYG: Cygnus A CAS: Cassiopeia A TAU: Taurus A VIR: Virgo A use_stored (bool): If True, uses stored UVW coordinates (does not recompute). this is faster than recomputing. update_src (bool): Default True, update the SOURCE table. conjugate (bool): Conjuagte UVW coordinates? Do this if things are flipped in map. """ self.pp.h1("Generating UVW coordinates") ra_deg, dec_deg, lst_deg, ha_deg = self._compute_lst_ha(src) H = np.deg2rad(ha_deg) d = np.deg2rad(dec_deg) self.pp.pp("LST: %2.3f deg" % lst_deg) self.pp.pp("Source RA: %2.3f deg" % ra_deg) self.pp.pp("Source DEC: %2.3f deg" % dec_deg) self.pp.pp("HA: %2.3f deg" % np.rad2deg(H)) try: assert H < 2 * np.pi and d < 2 * np.pi except AssertionError: raise ValueError("HA and DEC are too large (may not be in radians).") # Recreate list of baselines self.pp.h2("Computing UVW coordinates for %s" % src) xyz = self.d_array_geometry['STABXYZ'] if 257 in set(self.d_uv_data["BASELINE"]): bl_ids, ant_arr = coords.generateBaselineIds(self.n_ant) bl_vecs = coords.computeBaselineVectors(xyz) else: bl_ids, ant_arr = coords.generateBaselineIds(self.n_ant, autocorrs=False) bl_vecs = coords.computeBaselineVectors(xyz, autocorrs=False) n_iters = int(len(self.d_uv_data["BASELINE"]) / len(bl_ids)) self.pp.h2("Generating timestamps") dd, tt = [], [] for ii in range(n_iters): jd, jt = coords.convertToJulianTuple(self.date_obs) tdelta = self.t_int * ii / 86400.0 # In days jds = [jd for jj in range(len(ant_arr))] jts = [jt + tdelta for jj in range(len(ant_arr))] dd.append(jds) tt.append(jts) self.d_uv_data["DATE"] = np.array(dd, dtype='float64').ravel() self.d_uv_data["TIME"] = np.array(tt, dtype='float64').ravel() if use_stored: self.pp.h2("Loading stored values") self.loadUVW() else: uvw = coords.computeUVW(bl_vecs, H, d, conjugate=conjugate) # Fill with data # TODO: update this so that it can lock to zenith or phase to src uu, vv, ww = [], [], [] for ii in range(n_iters): uu.append(uvw[:, 0]) vv.append(uvw[:, 1]) ww.append(uvw[:, 2]) self.d_uv_data["UU"] = np.array(uu).ravel() self.d_uv_data["VV"] = np.array(vv).ravel() self.d_uv_data["WW"] = np.array(ww).ravel() if update_src: self.pp.h2("Updating SOURCE table") self.d_source["SOURCE"] = self.s2arr(src) self.d_source["RAEPO"] = self.s2arr(ra_deg) self.d_source["DECEPO"] = self.s2arr(dec_deg) self.source = src
def _vis_matrix_to_flux(self, vis, remap=False): """Convert a visibility matrix to FITS-IDI flux standard Notes ----- Visibility matrix should have shape: (n_int, ant1, ant2, chans, pola, polb) FITS-IDI is a flattened row on a per-baseline basis: (xx, yy, xy, yx) where each xx is (re_chan0, im_chan0, ... re_chanX, im_chanX). We only read one triangle of the visibility matrix, with ant1 >= ant2 """ # h2("Generating baseline IDs") n_int = vis.shape[0] n_ant = vis.shape[1] n_chans = vis.shape[3] n_stk = 4 n_bls = n_ant * (n_ant - 1) / 2 + n_ant bls, ant_arr = coords.generateBaselineIds(n_ant) ant_arr0 = np.array(ant_arr) - 1 # Zero indexed flux = np.zeros([n_bls * n_int, n_chans * n_stk * 2], dtype='float32') try: assert vis.dtype == 'complex64' except AssertionError: raise RuntimeError('Vis data is not complex64, but is instead %s' % vis.dtype) for int_num in xrange(n_int): idx = int_num * n_bls vis_int = vis[int_num, ...] for ii in xrange(n_bls): ant1, ant2 = ant_arr0[ii] vv = vis_int[ant1, ant2, ...] xx = vv[:, 0, 0] yy = vv[:, 1, 1] xy = vv[:, 0, 1] yx = vv[:, 1, 0] flux[idx + ii] = np.column_stack((xx, yy, xy, yx)).flatten().view('float32') if remap: self.pp.h2("Remapping antennas") mapping = { "255A": "238A", "255B": "240B", "242B": "253B", "240B": "252B", "252A": "254A", "252B": "244B", "256B": "248B", "248B": "256B", "245B": "255B", "253B": "245B", "253A": "255A", "244B": "254B", "238A": "252A", "250B": "242B", "250A": "253A", "254B": "250B", "254A": "250A" } map_keys = set(mapping.keys()) bls_all, ants = coords.generateBaselineIds(self.n_ant, autocorrs=True) for int_num in xrange(n_int): idx = int_num * n_bls vis_int = vis[int_num, ...] # For every antenna remapping for k in map_keys: ant_old, pol_id = int(k[:3]), 0 if k[3] == 'B' else 1 ant_new = int(mapping[k][:3]) # Find affected baselines bls_old = self.search_baselines(ant_old) bls_new = self.search_baselines(ant_new) # For every baseline affected for bb in range(len(bls_old)): # Find new antenna pair indexes bl_old, bl_new = bls_old[bb], bls_new[bb] bl_idx = bls_all.index(bl_old) if bl_new >= 65536: a1, a2 = (bl_new - 65536) / 2048 - 1, (bl_new - 65536) % 2048 - 1 else: a1, a2 = bl_new / 256 - 1, bl_new % 256 - 1 # Grab all the visibility data for this baseline xx = vis_int[a1, a2, :, 0, 0] xy = vis_int[a1, a2, :, 0, 1] yx = vis_int[a1, a2, :, 1, 0] yy = vis_int[a1, a2, :, 1, 1] # Now we need to figure out what we need to update # Are we updating pol A or pol B? Ant1 or Ant2? data = flux[idx + bl_idx] sp = len(xx) * 2 if ant_new == a1: if pol_id == 0: data[0:sp] = xx.flatten().view('float32') data[2 * sp:3 * sp] = xy.flatten().view('float32') else: data[1 * sp:2 * sp] = yy.flatten().view('float32') data[3 * sp:] = yx.flatten().view('float32') else: if pol_id == 0: data[0:sp] = xx.flatten().view('float32') data[2 * sp:3 * sp] = yx.flatten().view('float32') else: data[1 * sp:2 * sp] = yy.flatten().view('float32') data[3 * sp:] = xy.flatten().view('float32') # Write this back into the right baseline flux[idx + bl_idx] = data return flux
def readDada(self, n_int=None, xmlbase=None, header_dict=None, data_arr=None, inspectOnly=False): """ Read a LEDA DADA file. header_dict (dict): psrdada header. Defaults to None. If a dict is passed, then instead of loading data from file, data will be loaded from data_arr data_arr (np.ndarray): data array. This should be a preformatted FLUX data array. """ self.pp.h1("Loading DADA data") if type(header_dict) is dict: self.pp.h2("Loading from shared memory") d = HeaderDataUnit(header_dict, data_arr) flux = data_arr self.pp.h2("Generating baseline IDs") bls, ant_arr = coords.generateBaselineIds(d.n_ant) bl_lower = [] while len(bl_lower) < len(flux): bl_lower += bls else: self.pp.h2("Loading visibility data") d = dada.DadaReader(self.filename, n_int, inspectOnly=inspectOnly) vis = d.data self.dada_header = d.header try: n_ant = d.n_ant n_int = d.n_int self.n_ant = n_ant except ValueError: raise RuntimeError("Cannot load NCHAN / NPOL / NSTATION from dada file") if not header_dict: self.pp.h2("Converting visibilities to FLUX columns") do_remap = False if d.header["TELESCOPE"] in ('LEDA', 'LWAOVRO', 'LWA-OVRO', 'LEDAOVRO', 'LEDA512', 'LEDA-OVRO'): do_remap = False flux = self._vis_matrix_to_flux(vis, remap=do_remap) bls, ant_arr = coords.generateBaselineIds(n_ant) bl_lower = [] for dd in range(vis.shape[0] / n_int): bl_lower += bls self.d_uv_data["BASELINE"] = np.array([bl_lower for ii in range(n_int)]).flatten() self.d_uv_data["FLUX"] = flux self.pp.h1("Generating FITS-IDI schema from XML") if xmlbase is None: dirname, filename = os.path.split(os.path.abspath(__file__)) xmlbase = os.path.join(dirname, 'config/config.xml') self.xmlData = etree.parse(xmlbase) hdu_primary = make_primary(config=self.xmlData) tbl_array_geometry = make_array_geometry(config=self.xmlData, num_rows=n_ant) tbl_antenna = make_antenna(config=self.xmlData, num_rows=n_ant) tbl_frequency = make_frequency(config=self.xmlData, num_rows=1) tbl_source = make_source(config=self.xmlData, num_rows=1) #h1('Creating HDU list') hdulist = pf.HDUList( [hdu_primary, tbl_array_geometry, tbl_frequency, tbl_antenna, tbl_source]) self.fits = hdulist self.stokes_vals = [-5, -6, -7, -8] self.readFitsidi(from_file=False, load_uv_data=False) #hdulist.verify() self.pp.h2("Populating interfits dictionaries") self.setDefaults(n_uv_rows=len(bl_lower * n_int)) self.obs_code = '' self.correlator = d.header["INSTRUMENT"] self.instrument = d.header["INSTRUMENT"] self.telescope = d.header["TELESCOPE"] # Compute the integration time tsamp = float(d.header["TSAMP"]) * 1e-6 # Sampling time per channel, in microseconds navg = int(d.header["NAVG"]) # Number of averages per integration int_tim = tsamp * navg # Integration time is tsamp * navg self.t_int = d.t_int # Compute time offset self.pp.h2("Computing UTC offsets") dt_obj = datetime.strptime(d.header["UTC_START"], "%Y-%m-%d-%H:%M:%S") time_offset = d.t_offset # Time offset since observation began dt_obj = dt_obj + timedelta(seconds=time_offset) date_obs = dt_obj.strftime("%Y-%m-%dT%H:%M:%S") dd_obs = dt_obj.strftime("%Y-%m-%d") self.pp.pp("UTC START: %s" % d.header["UTC_START"]) self.pp.pp("TIME OFFSET: %s" % timedelta(seconds=time_offset)) self.pp.pp("NEW START: %s" % date_obs) self.date_obs = date_obs self.h_params["NSTOKES"] = 4 self.h_params["NBAND"] = 1 self.h_params["NCHAN"] = d.n_chans self.h_common["NO_CHAN"] = d.n_chans self.h_common["REF_FREQ"] = d.c_freq_mhz * 1e6 self.h_common["CHAN_BW"] = d.chan_bw_mhz * 1e6 self.h_common["REF_PIXL"] = d.n_chans / 2 + 1 self.h_common["RDATE"] = dd_obs # Ignore time component self.h_common["STK_1"] = -5 self.d_frequency["CH_WIDTH"] = d.chan_bw_mhz * 1e6 self.d_frequency["TOTAL_BANDWIDTH"] = d.bandwidth_mhz * 1e6 self.stokes_axis = ['XX', 'YY', 'XY', 'YX'] self.stokes_vals = [-5, -6, -7, -8] self.d_array_geometry["ANNAME"] = ["Stand%03d" % i for i in range(len(self.d_array_geometry["ANNAME"]))] self.d_array_geometry["NOSTA"] = [i for i in range(len(self.d_array_geometry["NOSTA"]))] self.d_uv_data["INTTIM"] = np.ones_like(self.d_uv_data["INTTIM"]) * d.t_int # Recreate list of baselines bl_ids, ant_arr = coords.generateBaselineIds(self.n_ant, autocorrs=True) n_iters = int(len(self.d_uv_data["BASELINE"]) / len(bl_ids)) self.pp.h2("Generating timestamps") dd, tt = [], [] for ii in range(n_iters): jd, jt = coords.convertToJulianTuple(self.date_obs) tdelta = int_tim * ii / 86400.0 # In days jds = [jd for jj in range(len(ant_arr))] jts = [jt + tdelta for jj in range(len(ant_arr))] dd.append(jds) tt.append(jts) self.d_uv_data["DATE"] = np.array(dd, dtype='float64').ravel() self.d_uv_data["TIME"] = np.array(tt, dtype='float64').ravel() # Load array geometry from file, based on TELESCOP name self.loadAntArr()
def readLfile(self, n_ant=32, n_chans=600, n_stk=4, config_xml=None): """ Read a LEDA L-file filename: str name of L-file n_ant: int Number of antennas. Defaults to 256 n_pol: int Number of polarizations. Defaults to 2 (dual-pol) n_chans: int Number of channels in file. Defaults to 109 (LEDA-512 default) n_stk: int Number of stokes parameters in file. Defaults to 4 config_xml: str Filename of XML schema file. If None, will default to [filename].xml Notes ----- .LA and .LC are binary data streams. .LA files store autocorrelations in the following way: t0 |Ant1 109 chans XX | Ant1 109 chans YY| Ant2 ... | AntN ... t1 |Ant1 109 chans XX | Ant1 109 chans YY| Ant2 ... | AntN ... These are REAL VALUED (1x float) .LC files store ant1 XY and are upper triangular, so 1x1y| 1x2x | 1x2y | ... | 1x32y | 2x2y | 2x3x | ... | ... | 3x3y | ... | ... | These are COMPLEX VALUED (2xfloats) """ self.pp.h1("Opening L-file") filename = self.filename.rstrip('.LA').rstrip('.LC') if config_xml is None: config_xml = filename + '.xml' try: self.xmlData = etree.parse(config_xml) except IOError: self.pp.err("ERROR: Cannot open %s" % config_xml) exit() # Load visibility data self.pp.h2("Loading visibility data") vis = self._readLfile(n_ant=n_ant, n_pol=2, n_chans=n_chans) self.pp.h2("Generating baseline IDs") # Create baseline IDs using MIRIAD >255 antenna format (which sucks) bls, ant_arr = coords.generateBaselineIds(n_ant) bl_lower = [] for dd in range(vis.shape[0]): bl_lower += bls self.pp.h2("Converting visibilities to FLUX columns") flux = np.zeros([len(bl_lower), n_chans * n_stk * 2], dtype='float32') for ii in range(len(bl_lower)): ant1, ant2 = ant_arr[ii % len(ant_arr)] idx1, idx2 = 2 * (ant1 - 1), 2 * (ant2 - 1) xx = vis[0, idx1, idx2] yy = vis[0, idx1 + 1, idx2 + 1] xy = vis[0, idx1, idx2 + 1] yx = vis[0, idx1 + 1, idx2] flux[ii] = np.column_stack((xx, yy, xy, yx)).flatten() self.d_uv_data["BASELINE"] = bl_lower self.d_uv_data["FLUX"] = flux self.pp.h1("Generating FITS-IDI schema from XML") hdu_primary = make_primary(config=config_xml) tbl_array_geometry = make_array_geometry(config=config_xml, num_rows=n_ant) tbl_antenna = make_antenna(config=config_xml, num_rows=n_ant) tbl_frequency = make_frequency(config=config_xml, num_rows=1) tbl_source = make_source(config=config_xml, num_rows=1) #h1('Creating HDU list') hdulist = pf.HDUList( [hdu_primary, tbl_array_geometry, tbl_frequency, tbl_antenna, tbl_source]) # We are now ready to back-fill Interfits dictionaries using readfitsidi self.fits = hdulist self.readFitsidi(from_file=False, load_uv_data=False) self.pp.h2("Populating interfits dictionaries") # Create interfits dictionary entries self.setDefaultsLeda(n_uv_rows=len(bl_lower)) self.telescope = self.h_uv_data["TELESCOP"] self.source = self.d_source["SOURCE"][0]