def gen_vis_table(self): """Generate visibility table and fill payload.""" baselines = [] ant_pos = self.config.get_antenna_positions() loc = self.config.get_loc() for cal_vis in self.v_array: ra, dec = self.phase_center.radec(cal_vis.vis.timestamp) # v.rotate(skyloc.Skyloc(ra, dec)) uu_a, vv_a, ww_a = cal_vis.get_all_uvw() bls = cal_vis.get_baselines() datestamp = tart_util.get_julian_date(cal_vis.vis.timestamp) - int( tart_util.get_julian_date(cal_vis.vis.timestamp) + 0.5 ) for uu, vv, ww, b in zip(uu_a, vv_a, ww_a, bls): baseline = {} [i, j] = b # print((np.array(a1.enu) - np.array(a0.enu)), uu, vv, ww) # arcane units of UVFITS require u,v,w in nanoseconds baseline["UU"] = uu * constants.L1_WAVELENGTH / constants.V_LIGHT baseline["VV"] = vv * constants.L1_WAVELENGTH / constants.V_LIGHT baseline["WW"] = ww * constants.L1_WAVELENGTH / constants.V_LIGHT baseline["BASELINE"] = encode_baseline(i + 1, j + 1) baseline["DATE"] = datestamp # DATE FIXME ? baselines.append(baseline) freqs = np.array([1545.0]) pols = np.array(["+"]) data = np.zeros( (len(self.v_array) * self.n_baselines, 1, 1, len(freqs), len(pols), 3) ) for i, v in enumerate(self.v_array): for k, b in enumerate(cal_vis.get_baselines()): for l, _ in enumerate(freqs): for j, _ in enumerate(pols): vis = v.get_visibility(b[0], b[1]) re = vis.real img = vis.imag w = np.ones(1) data[i * self.n_baselines + k, 0, 0, l, j, :] = [re, img, w] hdu = fits.GroupsHDU( fits.GroupData( data, parnames=["UU", "VV", "WW", "BASELINE", "DATE"], bitpix=-32, pardata=[ [b["UU"] for b in baselines], [b["VV"] for b in baselines], [b["WW"] for b in baselines], [b["BASELINE"] for b in baselines], [b["DATE"] for b in baselines], ], ) ) return hdu
def test_select_read_nospw_pol(casa_uvfits, tmp_path): # this requires writing a new file because the no spw file we have has only 1 pol with fits.open(casa_tutorial_uvfits, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data raw_data_array = raw_data_array[:, :, :, 0, :, :, :] vis_hdr["NAXIS"] = 6 vis_hdr["NAXIS5"] = vis_hdr["NAXIS6"] vis_hdr["CTYPE5"] = vis_hdr["CTYPE6"] vis_hdr["CRVAL5"] = vis_hdr["CRVAL6"] vis_hdr["CDELT5"] = vis_hdr["CDELT6"] vis_hdr["CRPIX5"] = vis_hdr["CRPIX6"] vis_hdr["CROTA5"] = vis_hdr["CROTA6"] vis_hdr["NAXIS6"] = vis_hdr["NAXIS7"] vis_hdr["CTYPE6"] = vis_hdr["CTYPE7"] vis_hdr["CRVAL6"] = vis_hdr["CRVAL7"] vis_hdr["CDELT6"] = vis_hdr["CDELT7"] vis_hdr["CRPIX6"] = vis_hdr["CRPIX7"] vis_hdr["CROTA6"] = vis_hdr["CROTA7"] vis_hdr.pop("NAXIS7") vis_hdr.pop("CTYPE7") vis_hdr.pop("CRVAL7") vis_hdr.pop("CDELT7") vis_hdr.pop("CRPIX7") vis_hdr.pop("CROTA7") par_names = vis_hdu.data.parnames group_parameter_list = [ vis_hdu.data.par(ind) for ind in range(len(par_names)) ] vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames["AIPS AN"]] write_file = str(tmp_path / "outtest_casa.uvfits") hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) pols_to_keep = [-1, -2] uvfits_uv = UVData() uvfits_uv.read(write_file, polarizations=pols_to_keep) uvfits_uv2 = casa_uvfits uvfits_uv2.select(polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2
def test_multisource_error(tmp_path): # make a file with multiple sources to test error condition uv_in = UVData() testfile = os.path.join(DATA_PATH, "day2_TDEM0003_10s_norx_1src_1spw.uvfits") write_file = str(tmp_path / "outtest_casa.uvfits") uv_in.read(testfile) uv_in.write_uvfits(write_file) with fits.open(write_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data par_names = vis_hdu.data.parnames group_parameter_list = [] lst_ind = 0 for index, name in enumerate(par_names): par_value = vis_hdu.data.par(name) # lst_array needs to be split in 2 parts to get high enough accuracy if name.lower() == "lst": if lst_ind == 0: # first lst entry, par_value has full lst value # (astropy adds the 2 values) lst_array_1 = np.float32(par_value) lst_array_2 = np.float32(par_value - np.float64(lst_array_1)) par_value = lst_array_1 lst_ind = 1 else: par_value = lst_array_2 # need to account for PZERO values group_parameter_list.append(par_value - vis_hdr["PZERO" + str(index + 1)]) par_names.append("SOURCE") source_array = np.ones_like(vis_hdu.data.par("BASELINE")) mid_index = source_array.shape[0] // 2 source_array[mid_index:] = source_array[mid_index:] * 2 group_parameter_list.append(source_array) vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames["AIPS AN"]] hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) with pytest.raises(ValueError) as cm: uv_in.read(write_file) assert str(cm.value).startswith("This file has multiple sources")
def test_source_group_params(): # make a file with a single source to test that it works uv_in = UVData() testfile = os.path.join(DATA_PATH, 'day2_TDEM0003_10s_norx_1src_1spw.uvfits') write_file = os.path.join(DATA_PATH, 'test/outtest_casa.uvfits') uvtest.checkWarnings(uv_in.read, [testfile], message='Telescope EVLA is not') uv_in.write_uvfits(write_file) with fits.open(write_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data par_names = vis_hdu.data.parnames group_parameter_list = [] lst_ind = 0 for index, name in enumerate(par_names): par_value = vis_hdu.data.par(name) # lst_array needs to be split in 2 parts to get high enough accuracy if name.lower() == 'lst': if lst_ind == 0: # first lst entry, par_value has full lst value (astropy adds the 2 values) lst_array_1 = np.float32(par_value) lst_array_2 = np.float32(par_value - np.float64(lst_array_1)) par_value = lst_array_1 lst_ind = 1 else: par_value = lst_array_2 # need to account for PZERO values group_parameter_list.append(par_value - vis_hdr['PZERO' + str(index + 1)]) par_names.append('SOURCE') source_array = np.ones_like(vis_hdu.data.par('BASELINE')) group_parameter_list.append(source_array) vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames['AIPS AN']] hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) uv_out = UVData() uvtest.checkWarnings(uv_out.read, [write_file], message='Telescope EVLA is not') assert uv_in == uv_out
def test_readwriteread_error_single_time(tmp_path, casa_uvfits): uv_in = casa_uvfits uv_out = UVData() write_file = str(tmp_path / "outtest_casa.uvfits") write_file2 = str(tmp_path / "outtest_casa2.uvfits") # check error if one time & no inttime specified uv_singlet = uv_in.select(times=uv_in.time_array[0], inplace=False) uv_singlet.write_uvfits(write_file) with fits.open(write_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data par_names = np.array(vis_hdu.data.parnames) pars_use = np.where(par_names != "INTTIM")[0] par_names = par_names[pars_use].tolist() group_parameter_list = [vis_hdu.data.par(name) for name in par_names] vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames["AIPS AN"]] hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file2, overwrite=True) with pytest.raises(ValueError) as cm: uvtest.checkWarnings( uv_out.read, func_args=[write_file2], message=[ "Telescope EVLA is not", 'ERFA function "utcut1" yielded 1 of "dubious year (Note 3)"', 'ERFA function "utctai" yielded 1 of "dubious year (Note 3)"', "LST values stored in this file are not self-consistent", ], nwarnings=4, category=[ UserWarning, astropy._erfa.core.ErfaWarning, astropy._erfa.core.ErfaWarning, UserWarning, ], ) assert str(cm.value).startswith( "integration time not specified and only one time present") return
def test_source_group_params(casa_uvfits, tmp_path): # make a file with a single source to test that it works uv_in = casa_uvfits write_file = str(tmp_path / "outtest_casa.uvfits") write_file2 = str(tmp_path / "outtest_casa2.uvfits") uv_in.write_uvfits(write_file) with fits.open(write_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data par_names = vis_hdu.data.parnames group_parameter_list = [] lst_ind = 0 for index, name in enumerate(par_names): par_value = vis_hdu.data.par(name) # lst_array needs to be split in 2 parts to get high enough accuracy if name.lower() == "lst": if lst_ind == 0: # first lst entry, par_value has full lst value # (astropy adds the 2 values) lst_array_1 = np.float32(par_value) lst_array_2 = np.float32(par_value - np.float64(lst_array_1)) par_value = lst_array_1 lst_ind = 1 else: par_value = lst_array_2 # need to account for PZERO values group_parameter_list.append(par_value - vis_hdr["PZERO" + str(index + 1)]) par_names.append("SOURCE") source_array = np.ones_like(vis_hdu.data.par("BASELINE")) group_parameter_list.append(source_array) vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames["AIPS AN"]] hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file2, overwrite=True) hdulist.close() uv_out = UVData() uv_out.read(write_file2) assert uv_in == uv_out
def save_obs_uvfits(obs, fname): """Save observation data to uvfits. """ # Open template UVFITS dir_path = os.path.dirname(os.path.realpath(__file__)) #hdulist = fits.open(dir_path+'/template.UVP') hdulist_new = fits.HDUList() hdulist_new.append(fits.GroupsHDU()) ######################################################################## # Data table # Data header (based on the BU format) #header = fits.Header() #header = hdulist[0].header header = hdulist_new['PRIMARY'].header header['OBSRA'] = obs.ra * 180. / 12. header['OBSDEC'] = obs.dec header['OBJECT'] = obs.source header['MJD'] = float(obs.mjd) header['BUNIT'] = 'JY' header['VELREF'] = 3 # !AC TODO ?? header['ALTRPIX'] = 1.e0 header['TELESCOP'] = 'ALMA' # !AC TODO Can we change this field? header['INSTRUME'] = 'ALMA' header['CTYPE2'] = 'COMPLEX' header['CRVAL2'] = 1.e0 header['CDELT2'] = 1.e0 header['CRPIX2'] = 1.e0 header['CROTA2'] = 0.e0 header['CTYPE3'] = 'STOKES' header['CRVAL3'] = -1.e0 header['CDELT3'] = -1.e0 header['CRPIX3'] = 1.e0 header['CROTA3'] = 0.e0 header['CTYPE4'] = 'FREQ' header['CRVAL4'] = obs.rf header['CDELT4'] = obs.bw header['CRPIX4'] = 1.e0 header['CROTA4'] = 0.e0 header['CTYPE6'] = 'RA' header['CRVAL6'] = header['OBSRA'] header['CDELT6'] = 1.e0 header['CRPIX6'] = 1.e0 header['CROTA6'] = 0.e0 header['CTYPE7'] = 'DEC' header['CRVAL7'] = header['OBSDEC'] header['CDELT7'] = 1.e0 header['CRPIX7'] = 1.e0 header['CROTA7'] = 0.e0 header['PTYPE1'] = 'UU---SIN' header['PSCAL1'] = 1.0 / obs.rf header['PZERO1'] = 0.e0 header['PTYPE2'] = 'VV---SIN' header['PSCAL2'] = 1.0 / obs.rf header['PZERO2'] = 0.e0 header['PTYPE3'] = 'WW---SIN' header['PSCAL3'] = 1.0 / obs.rf header['PZERO3'] = 0.e0 header['PTYPE4'] = 'BASELINE' header['PSCAL4'] = 1.e0 header['PZERO4'] = 0.e0 header['PTYPE5'] = 'DATE' header['PSCAL5'] = 1.e0 header['PZERO5'] = 0.e0 header['PTYPE6'] = 'DATE' header['PSCAL6'] = 1.e0 header['PZERO6'] = 0.0 header['PTYPE7'] = 'INTTIM' header['PSCAL7'] = 1.e0 header['PZERO7'] = 0.e0 header['PTYPE8'] = 'TAU1' header['PSCAL8'] = 1.e0 header['PZERO8'] = 0.e0 header['PTYPE9'] = 'TAU2' header['PSCAL9'] = 1.e0 header['PZERO9'] = 0.e0 # Get data obsdata = obs.unpack([ 'time', 'tint', 'u', 'v', 'vis', 'qvis', 'uvis', 'vvis', 'sigma', 'qsigma', 'usigma', 'vsigma', 't1', 't2', 'tau1', 'tau2' ]) ndat = len(obsdata['time']) # times and tints #jds = (obs.mjd + 2400000.5) * np.ones(len(obsdata)) #fractimes = (obsdata['time'] / 24.0) jds = (2400000.5 + obs.mjd) * np.ones(len(obsdata)) fractimes = (obsdata['time'] / 24.0) #jds = jds + fractimes #fractimes = np.zeros(len(obsdata)) tints = obsdata['tint'] # Baselines t1 = [obs.tkey[scope] + 1 for scope in obsdata['t1']] t2 = [obs.tkey[scope] + 1 for scope in obsdata['t2']] bl = 256 * np.array(t1) + np.array(t2) # opacities tau1 = obsdata['tau1'] tau2 = obsdata['tau2'] # uv are in lightseconds u = obsdata['u'] v = obsdata['v'] # rr, ll, lr, rl, weights rr = obsdata['vis'] + obsdata['vvis'] ll = obsdata['vis'] - obsdata['vvis'] rl = obsdata['qvis'] + 1j * obsdata['uvis'] lr = obsdata['qvis'] - 1j * obsdata['uvis'] weightrr = 1.0 / (obsdata['sigma']**2 + obsdata['vsigma']**2) weightll = 1.0 / (obsdata['sigma']**2 + obsdata['vsigma']**2) weightrl = 1.0 / (obsdata['qsigma']**2 + obsdata['usigma']**2) weightlr = 1.0 / (obsdata['qsigma']**2 + obsdata['usigma']**2) # Data array outdat = np.zeros((ndat, 1, 1, 1, 1, 4, 3)) outdat[:, 0, 0, 0, 0, 0, 0] = np.real(rr) outdat[:, 0, 0, 0, 0, 0, 1] = np.imag(rr) outdat[:, 0, 0, 0, 0, 0, 2] = weightrr outdat[:, 0, 0, 0, 0, 1, 0] = np.real(ll) outdat[:, 0, 0, 0, 0, 1, 1] = np.imag(ll) outdat[:, 0, 0, 0, 0, 1, 2] = weightll outdat[:, 0, 0, 0, 0, 2, 0] = np.real(rl) outdat[:, 0, 0, 0, 0, 2, 1] = np.imag(rl) outdat[:, 0, 0, 0, 0, 2, 2] = weightrl outdat[:, 0, 0, 0, 0, 3, 0] = np.real(lr) outdat[:, 0, 0, 0, 0, 3, 1] = np.imag(lr) outdat[:, 0, 0, 0, 0, 3, 2] = weightlr # Save data pars = [ 'UU---SIN', 'VV---SIN', 'WW---SIN', 'BASELINE', 'DATE', 'DATE', 'INTTIM', 'TAU1', 'TAU2' ] x = fits.GroupData( outdat, parnames=pars, pardata=[u, v, np.zeros(ndat), bl, jds, fractimes, tints, tau1, tau2], bitpix=-32) #hdulist[0].data = x #hdulist[0].header = header hdulist_new['PRIMARY'].data = x hdulist_new[ 'PRIMARY'].header = header # TODO necessary, or is it a pointer? ######################################################################## # Antenna table # Load the array data tarr = obs.tarr tnames = tarr['site'] tnums = np.arange(1, len(tarr) + 1) xyz = np.array([[tarr[i]['x'], tarr[i]['y'], tarr[i]['z']] for i in np.arange(len(tarr))]) sefd = tarr['sefdr'] nsta = len(tnames) col1 = fits.Column(name='ANNAME', format='8A', array=tnames) col2 = fits.Column(name='STABXYZ', format='3D', unit='METERS', array=xyz) col3 = fits.Column(name='NOSTA', format='1J', array=tnums) colfin = fits.Column(name='SEFD', format='1D', array=sefd) #!AC TODO these antenna fields+header are questionable - look into them col4 = fits.Column(name='MNTSTA', format='1J', array=np.zeros(nsta)) col5 = fits.Column(name='STAXOF', format='1E', unit='METERS', array=np.zeros(nsta)) col6 = fits.Column(name='POLTYA', format='1A', array=np.array(['R' for i in range(nsta)], dtype='|S1')) col7 = fits.Column(name='POLAA', format='1E', unit='DEGREES', array=np.zeros(nsta)) col8 = fits.Column(name='POLCALA', format='3E', array=np.zeros((nsta, 3))) col9 = fits.Column(name='POLTYB', format='1A', array=np.array(['L' for i in range(nsta)], dtype='|S1')) col10 = fits.Column(name='POLAB', format='1E', unit='DEGREES', array=(90. * np.ones(nsta))) col11 = fits.Column(name='POLCALB', format='3E', array=np.zeros((nsta, 3))) col25 = fits.Column(name='ORBPARM', format='1E', array=np.zeros(0)) #Antenna Header params - do I need to change more of these?? #head = fits.Header() tbhdu = fits.BinTableHDU.from_columns(fits.ColDefs([ col1, col2, col25, col3, col4, col5, col6, col7, col8, col9, col10, col11, colfin ]), name='AIPS AN') hdulist_new.append(tbhdu) #head = hdulist['AIPS AN'].header head = hdulist_new['AIPS AN'].header head['EXTNAME'] = 'AIPS AN' head['EXTVER'] = 1 head['RDATE'] = '2000-01-01T00:00:00.0' head['GSTIA0'] = 114.38389781355 # !AC TODO ?? for jan 1 2000 head['UT1UTC'] = 0.e0 head['DATUTC'] = 0.e0 head['TIMESYS'] = 'UTC' head['DEGPDY'] = 360.9856 head['FREQ'] = obs.rf head['FREQID'] = 1 head['ARRNAM'] = 'ALMA' #!AC TODO Can we change this field? head['XYZHAND'] = 'RIGHT' head['ARRAYX'] = 0.e0 head['ARRAYY'] = 0.e0 head['ARRAYZ'] = 0.e0 head['POLARX'] = 0.e0 head['POLARY'] = 0.e0 head['NUMORB'] = 0 head['NO_IF'] = 1 head['NOPCAL'] = 0 #!AC changed from 1 head['POLTYPE'] = 'APPROX' hdulist_new['AIPS AN'].header = head # TODO necessary, or is it a pointer? #tbhdu = fits.BinTableHDU.from_columns(fits.ColDefs([col1,col2,col25,col3,col4,col5,col6,col7,col8,col9,col10,col11,colfin]), name='AIPS AN', header=head) #hdulist['AIPS AN'] = tbhdu ################################################################################## # AIPS FQ TABLE -- Thanks to Kazu # Convert types & columns nif = 1 col1 = np.array(1, dtype=np.int32).reshape([nif]) #frqsel col2 = np.array(0.0, dtype=np.float64).reshape([nif]) #iffreq col3 = np.array([obs.bw], dtype=np.float32).reshape([nif]) #chwidth col4 = np.array([obs.bw], dtype=np.float32).reshape([nif]) #bw col5 = np.array([1], dtype=np.int32).reshape([nif]) #sideband col1 = fits.Column(name="FRQSEL", format="1J", array=col1) col2 = fits.Column(name="IF FREQ", format="%dD" % (nif), array=col2) col3 = fits.Column(name="CH WIDTH", format="%dE" % (nif), array=col3) col4 = fits.Column(name="TOTAL BANDWIDTH", format="%dE" % (nif), array=col4) col5 = fits.Column(name="SIDEBAND", format="%dJ" % (nif), array=col5) cols = fits.ColDefs([col1, col2, col3, col4, col5]) # create table tbhdu = fits.BinTableHDU.from_columns(cols) # add header information tbhdu.header.append(("NO_IF", nif, "Number IFs")) tbhdu.header.append(("EXTNAME", "AIPS FQ")) #hdulist.append(tbhdu) hdulist_new.append(tbhdu) # Write final HDUList to file #hdulist.writeto(fname, overwrite=True) hdulist_new.writeto(fname, overwrite=True) return
def write_uvfits( self, filename, spoof_nonessential=False, write_lst=True, force_phase=False, run_check=True, check_extra=True, run_check_acceptability=True, ): """ Write the data to a uvfits file. Parameters ---------- filename : str The uvfits file to write to. spoof_nonessential : bool Option to spoof the values of optional UVParameters that are not set but are required for uvfits files. write_lst : bool Option to write the LSTs to the metadata (random group parameters). force_phase : bool Option to automatically phase drift scan data to zenith of the first timestamp. run_check : bool Option to check for the existence and proper shapes of parameters before writing the file. check_extra : bool Option to check optional parameters as well as required ones. run_check_acceptability : bool Option to check acceptable range of the values of parameters before writing the file. Raises ------ ValueError The `phase_type` of the object is "drift" and the `force_phase` keyword is not set. The `phase_type` of the object is "unknown". If the frequencies are not evenly spaced or are separated by more than their channel width. The polarization values are not evenly spaced. Any of ['antenna_positions', 'gst0', 'rdate', 'earth_omega', 'dut1', 'timesys'] are not set on the object and `spoof_nonessential` is False. If the `timesys` parameter is not set to "UTC". TypeError If any entry in extra_keywords is not a single string or number. """ if run_check: self.check( check_extra=check_extra, run_check_acceptability=run_check_acceptability, check_freq_spacing=True, ) if self.phase_type == "phased": pass elif self.phase_type == "drift": if force_phase: print( "The data are in drift mode and do not have a " "defined phase center. Phasing to zenith of the first " "timestamp." ) phase_time = Time(self.time_array[0], format="jd") self.phase_to_time(phase_time) else: raise ValueError( "The data are in drift mode. " "Set force_phase to true to phase the data " "to zenith of the first timestamp before " "writing a uvfits file." ) else: raise ValueError( "The phasing type of the data is unknown. " "Set the phase_type to drift or phased to " "reflect the phasing status of the data" ) if self.Nfreqs > 1: freq_spacing = np.diff(self.freq_array, axis=1) freq_spacing = freq_spacing[0, 0] else: freq_spacing = self.channel_width if self.Npols > 1: pol_spacing = np.diff(self.polarization_array) if np.min(pol_spacing) < np.max(pol_spacing): raise ValueError( "The polarization values are not evenly spaced (probably " "because of a select operation). The uvfits format " "does not support unevenly spaced polarizations." ) pol_spacing = pol_spacing[0] else: pol_spacing = 1 for p in self.extra(): param = getattr(self, p) if param.name in self.uvfits_required_extra: if param.value is None: if spoof_nonessential: param.apply_spoof() setattr(self, p, param) else: raise ValueError( "Required attribute {attribute} " "for uvfits not defined. Define or " "set spoof_nonessential to True to " "spoof this attribute.".format(attribute=p) ) # check for unflagged data with nsample = 0. Warn if any found wh_nsample0 = np.where(self.nsample_array == 0) if np.any(~self.flag_array[wh_nsample0]): warnings.warn( "Some unflagged data has nsample = 0. Flags and " "nsamples are combined in uvfits files such that " "these data will appear to be flagged." ) weights_array = self.nsample_array * np.where(self.flag_array, -1, 1) # FITS uvw direction convention is opposite ours and Miriad's. # So conjugate the visibilities and flip the uvws: data_array = np.conj( self.data_array[:, np.newaxis, np.newaxis, :, :, :, np.newaxis] ) weights_array = weights_array[:, np.newaxis, np.newaxis, :, :, :, np.newaxis] # uvfits_array_data shape will be (Nblts,1,1,[Nspws],Nfreqs,Npols,3) uvfits_array_data = np.concatenate( [data_array.real, data_array.imag, weights_array], axis=6 ) # FITS uvw direction convention is opposite ours and Miriad's. # So conjugate the visibilities and flip the uvws: uvw_array_sec = -1 * self.uvw_array / const.c.to("m/s").value # jd_midnight = np.floor(self.time_array[0] - 0.5) + 0.5 tzero = np.float32(self.time_array[0]) # uvfits convention is that time_array + relevant PZERO = actual JD # We are setting PZERO4 = float32(first time of observation) time_array = np.float32(self.time_array - np.float64(tzero)) int_time_array = self.integration_time baselines_use = self.antnums_to_baseline( self.ant_1_array, self.ant_2_array, attempt256=True ) # Set up dictionaries for populating hdu # Note that uvfits antenna arrays are 1-indexed so we add 1 # to our 0-indexed arrays group_parameter_dict = { "UU ": uvw_array_sec[:, 0], "VV ": uvw_array_sec[:, 1], "WW ": uvw_array_sec[:, 2], "DATE ": time_array, "BASELINE": baselines_use, "ANTENNA1": self.ant_1_array + 1, "ANTENNA2": self.ant_2_array + 1, "SUBARRAY": np.ones_like(self.ant_1_array), "INTTIM ": int_time_array, } pscal_dict = { "UU ": 1.0, "VV ": 1.0, "WW ": 1.0, "DATE ": 1.0, "BASELINE": 1.0, "ANTENNA1": 1.0, "ANTENNA2": 1.0, "SUBARRAY": 1.0, "INTTIM ": 1.0, } pzero_dict = { "UU ": 0.0, "VV ": 0.0, "WW ": 0.0, "DATE ": tzero, "BASELINE": 0.0, "ANTENNA1": 0.0, "ANTENNA2": 0.0, "SUBARRAY": 0.0, "INTTIM ": 0.0, } if write_lst: # lst is a non-standard entry (it's not in the AIPS memo) # but storing it can be useful (e.g. can avoid recalculating it on read) # need to store it in 2 parts to get enough accuracy # angles in uvfits files are stored in degrees, so first convert to degrees lst_array_deg = np.rad2deg(self.lst_array) lst_array_1 = np.float32(lst_array_deg) lst_array_2 = np.float32(lst_array_deg - np.float64(lst_array_1)) group_parameter_dict["LST "] = lst_array_1 pscal_dict["LST "] = 1.0 pzero_dict["LST "] = 0.0 # list contains arrays of [u,v,w,date,baseline]; # each array has shape (Nblts) parnames_use = ["UU ", "VV ", "WW ", "DATE "] if np.max(self.ant_1_array) < 255 and np.max(self.ant_2_array) < 255: # if the number of antennas is less than 256 then include both the # baseline array and the antenna arrays in the group parameters. # Otherwise just use the antenna arrays parnames_use.append("BASELINE") parnames_use += ["ANTENNA1", "ANTENNA2", "SUBARRAY", "INTTIM "] if write_lst: parnames_use.append("LST ") group_parameter_list = [ group_parameter_dict[parname] for parname in parnames_use ] if write_lst: # add second LST array part parnames_use.append("LST ") group_parameter_list.append(lst_array_2) hdu = fits.GroupData( uvfits_array_data, parnames=parnames_use, pardata=group_parameter_list, bitpix=-32, ) hdu = fits.GroupsHDU(hdu) for i, key in enumerate(parnames_use): hdu.header["PSCAL" + str(i + 1) + " "] = pscal_dict[key] hdu.header["PZERO" + str(i + 1) + " "] = pzero_dict[key] # ISO string of first time in self.time_array hdu.header["DATE-OBS"] = Time(self.time_array[0], scale="utc", format="jd").isot hdu.header["CTYPE2 "] = "COMPLEX " hdu.header["CRVAL2 "] = 1.0 hdu.header["CRPIX2 "] = 1.0 hdu.header["CDELT2 "] = 1.0 # Note: This axis is called STOKES to comply with the AIPS memo 117 # However, this confusing because it is NOT a true Stokes axis, # it is really the polarization axis. hdu.header["CTYPE3 "] = "STOKES " hdu.header["CRVAL3 "] = self.polarization_array[0] hdu.header["CRPIX3 "] = 1.0 hdu.header["CDELT3 "] = pol_spacing hdu.header["CTYPE4 "] = "FREQ " hdu.header["CRVAL4 "] = self.freq_array[0, 0] hdu.header["CRPIX4 "] = 1.0 hdu.header["CDELT4 "] = freq_spacing hdu.header["CTYPE5 "] = "IF " hdu.header["CRVAL5 "] = 1.0 hdu.header["CRPIX5 "] = 1.0 hdu.header["CDELT5 "] = 1.0 hdu.header["CTYPE6 "] = "RA" hdu.header["CRVAL6 "] = self.phase_center_ra_degrees hdu.header["CTYPE7 "] = "DEC" hdu.header["CRVAL7 "] = self.phase_center_dec_degrees hdu.header["BUNIT "] = self.vis_units hdu.header["BSCALE "] = 1.0 hdu.header["BZERO "] = 0.0 hdu.header["OBJECT "] = self.object_name hdu.header["TELESCOP"] = self.telescope_name hdu.header["LAT "] = self.telescope_location_lat_lon_alt_degrees[0] hdu.header["LON "] = self.telescope_location_lat_lon_alt_degrees[1] hdu.header["ALT "] = self.telescope_location_lat_lon_alt[2] hdu.header["INSTRUME"] = self.instrument hdu.header["EPOCH "] = float(self.phase_center_epoch) if self.phase_center_frame is not None: hdu.header["PHSFRAME"] = self.phase_center_frame if self.x_orientation is not None: hdu.header["XORIENT"] = self.x_orientation if self.blt_order is not None: blt_order_str = ", ".join(self.blt_order) hdu.header["BLTORDER"] = blt_order_str for line in self.history.splitlines(): hdu.header.add_history(line) # end standard keywords; begin user-defined keywords for key, value in self.extra_keywords.items(): # header keywords have to be 8 characters or less if len(str(key)) > 8: warnings.warn( "key {key} in extra_keywords is longer than 8 " "characters. It will be truncated to 8 as required " "by the uvfits file format.".format(key=key) ) keyword = key[:8].upper() if isinstance(value, (dict, list, np.ndarray)): raise TypeError( "Extra keyword {keyword} is of {keytype}. " "Only strings and numbers are " "supported in uvfits.".format(keyword=key, keytype=type(value)) ) if keyword == "COMMENT": for line in value.splitlines(): hdu.header.add_comment(line) else: hdu.header[keyword] = value # ADD the ANTENNA table staxof = np.zeros(self.Nants_telescope) # 0 specifies alt-az, 6 would specify a phased array mntsta = np.zeros(self.Nants_telescope) # beware, X can mean just about anything poltya = np.full((self.Nants_telescope), "X", dtype=np.object_) polaa = [90.0] + np.zeros(self.Nants_telescope) poltyb = np.full((self.Nants_telescope), "Y", dtype=np.object_) polab = [0.0] + np.zeros(self.Nants_telescope) col1 = fits.Column(name="ANNAME", format="8A", array=self.antenna_names) # AIPS memo #117 says that antenna_positions should be relative to # the array center, but in a rotated ECEF frame so that the x-axis # goes through the local meridian. longitude = self.telescope_location_lat_lon_alt[1] rot_ecef_positions = uvutils.rotECEF_from_ECEF( self.antenna_positions, longitude ) col2 = fits.Column(name="STABXYZ", format="3D", array=rot_ecef_positions) # convert to 1-indexed from 0-indexed indicies col3 = fits.Column(name="NOSTA", format="1J", array=self.antenna_numbers + 1) col4 = fits.Column(name="MNTSTA", format="1J", array=mntsta) col5 = fits.Column(name="STAXOF", format="1E", array=staxof) col6 = fits.Column(name="POLTYA", format="1A", array=poltya) col7 = fits.Column(name="POLAA", format="1E", array=polaa) # col8 = fits.Column(name='POLCALA', format='3E', array=polcala) col9 = fits.Column(name="POLTYB", format="1A", array=poltyb) col10 = fits.Column(name="POLAB", format="1E", array=polab) # col11 = fits.Column(name='POLCALB', format='3E', array=polcalb) # note ORBPARM is technically required, but we didn't put it in col_list = [col1, col2, col3, col4, col5, col6, col7, col9, col10] if self.antenna_diameters is not None: col12 = fits.Column( name="DIAMETER", format="1E", array=self.antenna_diameters ) col_list.append(col12) cols = fits.ColDefs(col_list) ant_hdu = fits.BinTableHDU.from_columns(cols) ant_hdu.header["EXTNAME"] = "AIPS AN" ant_hdu.header["EXTVER"] = 1 # write XYZ coordinates if not already defined ant_hdu.header["ARRAYX"] = self.telescope_location[0] ant_hdu.header["ARRAYY"] = self.telescope_location[1] ant_hdu.header["ARRAYZ"] = self.telescope_location[2] ant_hdu.header["FRAME"] = "ITRF" ant_hdu.header["GSTIA0"] = self.gst0 ant_hdu.header["FREQ"] = self.freq_array[0, 0] ant_hdu.header["RDATE"] = self.rdate ant_hdu.header["UT1UTC"] = self.dut1 ant_hdu.header["TIMSYS"] = self.timesys if self.timesys != "UTC": raise ValueError( "This file has a time system {tsys}. " 'Only "UTC" time system files are supported'.format(tsys=self.timesys) ) ant_hdu.header["ARRNAM"] = self.telescope_name ant_hdu.header["NO_IF"] = self.Nspws ant_hdu.header["DEGPDY"] = self.earth_omega # ant_hdu.header['IATUTC'] = 35. # set mandatory parameters which are not supported by this object # (or that we just don't understand) ant_hdu.header["NUMORB"] = 0 # note: Bart had this set to 3. We've set it 0 after aips 117. -jph ant_hdu.header["NOPCAL"] = 0 ant_hdu.header["POLTYPE"] = "X-Y LIN" # note: we do not support the concept of "frequency setups" # -- lists of spws given in a SU table. ant_hdu.header["FREQID"] = -1 # if there are offsets in images, this could be the culprit ant_hdu.header["POLARX"] = 0.0 ant_hdu.header["POLARY"] = 0.0 ant_hdu.header["DATUTC"] = 0 # ONLY UTC SUPPORTED # we always output right handed coordinates ant_hdu.header["XYZHAND"] = "RIGHT" # ADD the FQ table # skipping for now and limiting to a single spw # write the file hdulist = fits.HDUList(hdus=[hdu, ant_hdu]) hdulist.writeto(filename, overwrite=True)
def uvf_if_flatten(uvf, outp='merged.uvfits'): # THIS IS TO ADD NEW MERGING MODE: IF_FLATTEN # ASSUMING THE SINGLE UVF IS WELL BEHAVED uvf_hdul = pyfits.open(uvf) f0 = uvf_hdul[0].header['CRVAL4'] freqs = uvf_hdul['AIPS FQ'].data['IF FREQ'].flatten() + f0 bw = freqs[-1] - freqs[0] Nif = len(freqs) freq0 = freqs.mean() data = uvf_hdul[0].data exp_keys = ['UU', 'VV', 'WW', 'BASELINE', 'DATE', 'INTTIM'] keys = data.parnames idx, iek = index_uvf_keys(keys, exp_keys) comb_data = [[] for i in range(7)] comb_data.append(np.empty((0, 1, 1, 1, 1, 4, 3))) for i in range(Nif): for j in range(7): # FILLINF UU, VV and WW, BASELINE, DATE, DATE, INTTIM AND DATA if j == 5: # ADD A ONE-SEC OFFSET TO TIMESTAMPS comb_data[j].append(data.par(idx[j]) + i * 1.0 / 86400) else: comb_data[j].append(data.par(idx[j])) comb_data[7] = np.append(comb_data[7], data.data[:, :, :, [i], :, :, :], axis=0) # DATA IS READY for i in range(len(iek) - 1): comb_data[i] = np.array(comb_data[i], dtype=np.float64).flatten() mjd = comb_data[4] + comb_data[5] idx_tsort = mjd.argsort() for i in range(len(iek)): comb_data[i] = comb_data[i][idx_tsort] print(comb_data[-1].shape) gdata = pyfits.GroupData(input=comb_data[-1], parnames=iek[:-1], pardata=comb_data[:-1], bscale=1.0, bzero=0.0, bitpix=-32) ghdu = pyfits.GroupsHDU(gdata) ghdu.header = uvf_hdul[0].header ghdu.header['CRVAL4'] = freq0 ghdu.header['CDELT4'] = bw antab = uvf_hdul['AIPS AN'] fq_keys = ['FRQSEL', 'IF FREQ', 'CH WIDTH', 'TOTAL BANDWIDTH', 'SIDEBAND'] fq_formats = ['1J', '1D', '1E', '1E', '1J'] fq_data = [np.array([1]), [0], [bw], [bw], [1]] cols = [pyfits.Column(name=fq_keys[k], format=fq_formats[k], \ array=fq_data[k]) for k in range(len(fq_keys))] fqtab = pyfits.BinTableHDU.from_columns(cols, name='AIPS FQ') fh = fqtab.header fh['NO_IF'] = 1 comb_hdu = pyfits.HDUList([ghdu, antab, fqtab]) comb_hdu.writeto(outp, output_verify='silentfix', overwrite=True)
def write_uvfits(self, filename, spoof_nonessential=False, force_phase=False, run_check=True, check_extra=True, run_check_acceptability=True): """ Write the data to a uvfits file. Args: filename: The uvfits file to write to. spoof_nonessential: Option to spoof the values of optional UVParameters that are not set but are required for uvfits files. Default is False. force_phase: Option to automatically phase drift scan data to zenith of the first timestamp. Default is False. run_check: Option to check for the existence and proper shapes of parameters before writing the file. Default is True. check_extra: Option to check optional parameters as well as required ones. Default is True. run_check_acceptability: Option to check acceptable range of the values of parameters before writing the file. Default is True. """ if run_check: self.check(check_extra=check_extra, run_check_acceptability=run_check_acceptability) if self.phase_type == 'phased': pass elif self.phase_type == 'drift': if force_phase: print('The data are in drift mode and do not have a ' 'defined phase center. Phasing to zenith of the first ' 'timestamp.') phase_time = Time(self.time_array[0], format='jd') self.phase_to_time(phase_time) else: raise ValueError('The data are in drift mode. ' 'Set force_phase to true to phase the data ' 'to zenith of the first timestamp before ' 'writing a uvfits file.') else: raise ValueError('The phasing type of the data is unknown. ' 'Set the phase_type to drift or phased to ' 'reflect the phasing status of the data') if self.Nfreqs > 1: freq_spacing = self.freq_array[0, 1:] - self.freq_array[0, :-1] if not np.isclose(np.min(freq_spacing), np.max(freq_spacing), rtol=self._freq_array.tols[0], atol=self._freq_array.tols[1]): raise ValueError( 'The frequencies are not evenly spaced (probably ' 'because of a select operation). The uvfits format ' 'does not support unevenly spaced frequencies.') if not np.isclose(freq_spacing[0], self.channel_width, rtol=self._freq_array.tols[0], atol=self._freq_array.tols[1]): raise ValueError( 'The frequencies are separated by more than their ' 'channel width (probably because of a select operation). ' 'The uvfits format does not support frequencies ' 'that are spaced by more than their channel width.') freq_spacing = freq_spacing[0] else: freq_spacing = self.channel_width if self.Npols > 1: pol_spacing = np.diff(self.polarization_array) if np.min(pol_spacing) < np.max(pol_spacing): raise ValueError( 'The polarization values are not evenly spaced (probably ' 'because of a select operation). The uvfits format ' 'does not support unevenly spaced polarizations.') pol_spacing = pol_spacing[0] else: pol_spacing = 1 for p in self.extra(): param = getattr(self, p) if param.name in self.uvfits_required_extra: if param.value is None: if spoof_nonessential: # spoof extra keywords required for uvfits if isinstance(param, uvp.AntPositionParameter): param.apply_spoof(self, 'Nants_telescope') else: param.apply_spoof() setattr(self, p, param) else: raise ValueError( 'Required attribute {attribute} ' 'for uvfits not defined. Define or ' 'set spoof_nonessential to True to ' 'spoof this attribute.'.format(attribute=p)) # check for unflagged data with nsample = 0. Warn if any found wh_nsample0 = np.where(self.nsample_array == 0) if np.any(~self.flag_array[wh_nsample0]): warnings.warn('Some unflagged data has nsample = 0. Flags and ' 'nsamples are combined in uvfits files such that ' 'these data will appear to be flagged.') weights_array = self.nsample_array * \ np.where(self.flag_array, -1, 1) # FITS uvw direction convention is opposite ours and Miriad's. # So conjugate the visibilities and flip the uvws: data_array = np.conj(self.data_array[:, np.newaxis, np.newaxis, :, :, :, np.newaxis]) weights_array = weights_array[:, np.newaxis, np.newaxis, :, :, :, np.newaxis] # uvfits_array_data shape will be (Nblts,1,1,[Nspws],Nfreqs,Npols,3) uvfits_array_data = np.concatenate( [data_array.real, data_array.imag, weights_array], axis=6) # FITS uvw direction convention is opposite ours and Miriad's. # So conjugate the visibilities and flip the uvws: uvw_array_sec = -1 * self.uvw_array / const.c.to('m/s').value # jd_midnight = np.floor(self.time_array[0] - 0.5) + 0.5 tzero = np.float32(self.time_array[0]) # uvfits convention is that time_array + relevant PZERO = actual JD # We are setting PZERO4 = float32(first time of observation) time_array = np.float32(self.time_array - np.float64(tzero)) int_time_array = self.integration_time baselines_use = self.antnums_to_baseline(self.ant_1_array, self.ant_2_array, attempt256=True) # Set up dictionaries for populating hdu # Note that uvfits antenna arrays are 1-indexed so we add 1 # to our 0-indexed arrays group_parameter_dict = { 'UU ': uvw_array_sec[:, 0], 'VV ': uvw_array_sec[:, 1], 'WW ': uvw_array_sec[:, 2], 'DATE ': time_array, 'BASELINE': baselines_use, 'ANTENNA1': self.ant_1_array + 1, 'ANTENNA2': self.ant_2_array + 1, 'SUBARRAY': np.ones_like(self.ant_1_array), 'INTTIM': int_time_array } pscal_dict = { 'UU ': 1.0, 'VV ': 1.0, 'WW ': 1.0, 'DATE ': 1.0, 'BASELINE': 1.0, 'ANTENNA1': 1.0, 'ANTENNA2': 1.0, 'SUBARRAY': 1.0, 'INTTIM': 1.0 } pzero_dict = { 'UU ': 0.0, 'VV ': 0.0, 'WW ': 0.0, 'DATE ': tzero, 'BASELINE': 0.0, 'ANTENNA1': 0.0, 'ANTENNA2': 0.0, 'SUBARRAY': 0.0, 'INTTIM': 0.0 } # list contains arrays of [u,v,w,date,baseline]; # each array has shape (Nblts) if (np.max(self.ant_1_array) < 255 and np.max(self.ant_2_array) < 255): # if the number of antennas is less than 256 then include both the # baseline array and the antenna arrays in the group parameters. # Otherwise just use the antenna arrays parnames_use = [ 'UU ', 'VV ', 'WW ', 'DATE ', 'BASELINE', 'ANTENNA1', 'ANTENNA2', 'SUBARRAY', 'INTTIM' ] else: parnames_use = [ 'UU ', 'VV ', 'WW ', 'DATE ', 'ANTENNA1', 'ANTENNA2', 'SUBARRAY', 'INTTIM' ] group_parameter_list = [ group_parameter_dict[parname] for parname in parnames_use ] hdu = fits.GroupData(uvfits_array_data, parnames=parnames_use, pardata=group_parameter_list, bitpix=-32) hdu = fits.GroupsHDU(hdu) for i, key in enumerate(parnames_use): hdu.header['PSCAL' + str(i + 1) + ' '] = pscal_dict[key] hdu.header['PZERO' + str(i + 1) + ' '] = pzero_dict[key] # ISO string of first time in self.time_array hdu.header['DATE-OBS'] = Time(self.time_array[0], scale='utc', format='jd').isot hdu.header['CTYPE2 '] = 'COMPLEX ' hdu.header['CRVAL2 '] = 1.0 hdu.header['CRPIX2 '] = 1.0 hdu.header['CDELT2 '] = 1.0 # Note: This axis is called STOKES to comply with the AIPS memo 117 # However, this confusing because it is NOT a true Stokes axis, # it is really the polarization axis. hdu.header['CTYPE3 '] = 'STOKES ' hdu.header['CRVAL3 '] = self.polarization_array[0] hdu.header['CRPIX3 '] = 1.0 hdu.header['CDELT3 '] = pol_spacing hdu.header['CTYPE4 '] = 'FREQ ' hdu.header['CRVAL4 '] = self.freq_array[0, 0] hdu.header['CRPIX4 '] = 1.0 hdu.header['CDELT4 '] = freq_spacing hdu.header['CTYPE5 '] = 'IF ' hdu.header['CRVAL5 '] = 1.0 hdu.header['CRPIX5 '] = 1.0 hdu.header['CDELT5 '] = 1.0 hdu.header['CTYPE6 '] = 'RA' hdu.header['CRVAL6 '] = self.phase_center_ra_degrees hdu.header['CTYPE7 '] = 'DEC' hdu.header['CRVAL7 '] = self.phase_center_dec_degrees hdu.header['BUNIT '] = self.vis_units hdu.header['BSCALE '] = 1.0 hdu.header['BZERO '] = 0.0 hdu.header['OBJECT '] = self.object_name hdu.header['TELESCOP'] = self.telescope_name hdu.header['LAT '] = self.telescope_location_lat_lon_alt_degrees[0] hdu.header['LON '] = self.telescope_location_lat_lon_alt_degrees[1] hdu.header['ALT '] = self.telescope_location_lat_lon_alt[2] hdu.header['INSTRUME'] = self.instrument hdu.header['EPOCH '] = float(self.phase_center_epoch) if self.phase_center_frame is not None: hdu.header['PHSFRAME'] = self.phase_center_frame if self.x_orientation is not None: hdu.header['XORIENT'] = self.x_orientation for line in self.history.splitlines(): hdu.header.add_history(line) # end standard keywords; begin user-defined keywords for key, value in self.extra_keywords.items(): # header keywords have to be 8 characters or less if len(str(key)) > 8: warnings.warn( 'key {key} in extra_keywords is longer than 8 ' 'characters. It will be truncated to 8 as required ' 'by the uvfits file format.'.format(key=key)) keyword = key[:8].upper() if isinstance(value, (dict, list, np.ndarray)): raise TypeError('Extra keyword {keyword} is of {keytype}. ' 'Only strings and numbers are ' 'supported in uvfits.'.format( keyword=key, keytype=type(value))) if keyword == 'COMMENT': for line in value.splitlines(): hdu.header.add_comment(line) else: hdu.header[keyword] = value # ADD the ANTENNA table staxof = np.zeros(self.Nants_telescope) # 0 specifies alt-az, 6 would specify a phased array mntsta = np.zeros(self.Nants_telescope) # beware, X can mean just about anything poltya = np.full((self.Nants_telescope), 'X', dtype=np.object_) polaa = [90.0] + np.zeros(self.Nants_telescope) poltyb = np.full((self.Nants_telescope), 'Y', dtype=np.object_) polab = [0.0] + np.zeros(self.Nants_telescope) col1 = fits.Column(name='ANNAME', format='8A', array=self.antenna_names) # AIPS memo #117 says that antenna_positions should be relative to # the array center, but in a rotated ECEF frame so that the x-axis # goes through the local meridian. longitude = self.telescope_location_lat_lon_alt[1] rot_ecef_positions = uvutils.rotECEF_from_ECEF(self.antenna_positions, longitude) col2 = fits.Column(name='STABXYZ', format='3D', array=rot_ecef_positions) # convert to 1-indexed from 0-indexed indicies col3 = fits.Column(name='NOSTA', format='1J', array=self.antenna_numbers + 1) col4 = fits.Column(name='MNTSTA', format='1J', array=mntsta) col5 = fits.Column(name='STAXOF', format='1E', array=staxof) col6 = fits.Column(name='POLTYA', format='1A', array=poltya) col7 = fits.Column(name='POLAA', format='1E', array=polaa) # col8 = fits.Column(name='POLCALA', format='3E', array=polcala) col9 = fits.Column(name='POLTYB', format='1A', array=poltyb) col10 = fits.Column(name='POLAB', format='1E', array=polab) # col11 = fits.Column(name='POLCALB', format='3E', array=polcalb) # note ORBPARM is technically required, but we didn't put it in col_list = [col1, col2, col3, col4, col5, col6, col7, col9, col10] if self.antenna_diameters is not None: col12 = fits.Column(name='DIAMETER', format='1E', array=self.antenna_diameters) col_list.append(col12) cols = fits.ColDefs(col_list) ant_hdu = fits.BinTableHDU.from_columns(cols) ant_hdu.header['EXTNAME'] = 'AIPS AN' ant_hdu.header['EXTVER'] = 1 # write XYZ coordinates if not already defined ant_hdu.header['ARRAYX'] = self.telescope_location[0] ant_hdu.header['ARRAYY'] = self.telescope_location[1] ant_hdu.header['ARRAYZ'] = self.telescope_location[2] ant_hdu.header['FRAME'] = 'ITRF' ant_hdu.header['GSTIA0'] = self.gst0 ant_hdu.header['FREQ'] = self.freq_array[0, 0] ant_hdu.header['RDATE'] = self.rdate ant_hdu.header['UT1UTC'] = self.dut1 ant_hdu.header['TIMSYS'] = self.timesys if self.timesys != 'UTC': raise ValueError( 'This file has a time system {tsys}. ' 'Only "UTC" time system files are supported'.format( tsys=self.timesys)) ant_hdu.header['ARRNAM'] = self.telescope_name ant_hdu.header['NO_IF'] = self.Nspws ant_hdu.header['DEGPDY'] = self.earth_omega # ant_hdu.header['IATUTC'] = 35. # set mandatory parameters which are not supported by this object # (or that we just don't understand) ant_hdu.header['NUMORB'] = 0 # note: Bart had this set to 3. We've set it 0 after aips 117. -jph ant_hdu.header['NOPCAL'] = 0 ant_hdu.header['POLTYPE'] = 'X-Y LIN' # note: we do not support the concept of "frequency setups" # -- lists of spws given in a SU table. ant_hdu.header['FREQID'] = -1 # if there are offsets in images, this could be the culprit ant_hdu.header['POLARX'] = 0.0 ant_hdu.header['POLARY'] = 0.0 ant_hdu.header['DATUTC'] = 0 # ONLY UTC SUPPORTED # we always output right handed coordinates ant_hdu.header['XYZHAND'] = 'RIGHT' # ADD the FQ table # skipping for now and limiting to a single spw # write the file hdulist = fits.HDUList(hdus=[hdu, ant_hdu]) if float(astropy.__version__[0:3]) < 1.3: # pragma: no cover hdulist.writeto(filename, clobber=True) else: hdulist.writeto(filename, overwrite=True)
def test_select_read(): uvfits_uv = UVData() uvfits_uv2 = UVData() uvfits_file = os.path.join(DATA_PATH, 'day2_TDEM0003_10s_norx_1src_1spw.uvfits') # select on antennas ants_to_keep = np.array([0, 19, 11, 24, 3, 23, 1, 20, 21]) uvtest.checkWarnings(uvfits_uv.read, [uvfits_file], {'antenna_nums': ants_to_keep}, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(antenna_nums=ants_to_keep) assert uvfits_uv == uvfits_uv2 # select on frequency channels chans_to_keep = np.arange(12, 22) uvtest.checkWarnings(uvfits_uv.read, [uvfits_file], {'freq_chans': chans_to_keep}, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(freq_chans=chans_to_keep) assert uvfits_uv == uvfits_uv2 # check writing & reading single frequency files uvfits_uv.select(freq_chans=[0]) testfile = os.path.join(DATA_PATH, 'test/outtest_casa.uvfits') uvfits_uv.write_uvfits(testfile) uvtest.checkWarnings(uvfits_uv2.read, [testfile], message='Telescope EVLA is not') assert uvfits_uv == uvfits_uv2 # select on pols pols_to_keep = [-1, -2] uvtest.checkWarnings(uvfits_uv.read, [uvfits_file], {'polarizations': pols_to_keep}, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2 # select on read using time_range unique_times = np.unique(uvfits_uv.time_array) uvtest.checkWarnings(uvfits_uv.read, [uvfits_file], {'time_range': [unique_times[0], unique_times[1]]}, nwarnings=2, message=[ 'Warning: "time_range" keyword is set', 'Telescope EVLA is not' ]) uvtest.checkWarnings(uvfits_uv2.read, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(times=unique_times[0:2]) assert uvfits_uv == uvfits_uv2 # now test selecting on multiple axes # frequencies first uvtest.checkWarnings(uvfits_uv.read, [uvfits_file], { 'antenna_nums': ants_to_keep, 'freq_chans': chans_to_keep, 'polarizations': pols_to_keep }, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2 # baselines first ants_to_keep = np.array([0, 1]) uvtest.checkWarnings(uvfits_uv.read, [uvfits_file], { 'antenna_nums': ants_to_keep, 'freq_chans': chans_to_keep, 'polarizations': pols_to_keep }, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2 # polarizations first ants_to_keep = np.array([0, 1, 2, 3, 6, 7, 8, 11, 14, 18, 19, 20, 21, 22]) chans_to_keep = np.arange(12, 64) uvtest.checkWarnings(uvfits_uv.read, [uvfits_file], { 'antenna_nums': ants_to_keep, 'freq_chans': chans_to_keep, 'polarizations': pols_to_keep }, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2 # repeat with no spw file uvfitsfile_no_spw = os.path.join(DATA_PATH, 'zen.2456865.60537.xy.uvcRREAAM.uvfits') # select on antennas ants_to_keep = np.array([2, 4, 5]) uvtest.checkWarnings(uvfits_uv.read, [uvfitsfile_no_spw], {'antenna_nums': ants_to_keep}, known_warning='paper_uvfits') uvtest.checkWarnings(uvfits_uv2.read, [uvfitsfile_no_spw], known_warning='paper_uvfits') uvfits_uv2.select(antenna_nums=ants_to_keep) assert uvfits_uv == uvfits_uv2 # select on frequency channels chans_to_keep = np.arange(4, 8) uvtest.checkWarnings(uvfits_uv.read, [uvfitsfile_no_spw], {'freq_chans': chans_to_keep}, known_warning='paper_uvfits') uvtest.checkWarnings(uvfits_uv2.read, [uvfitsfile_no_spw], known_warning='paper_uvfits') uvfits_uv2.select(freq_chans=chans_to_keep) assert uvfits_uv == uvfits_uv2 # select on pols # this requires writing a new file because the no spw file we have has only 1 pol with fits.open(uvfits_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data raw_data_array = raw_data_array[:, :, :, 0, :, :, :] vis_hdr['NAXIS'] = 6 vis_hdr['NAXIS5'] = vis_hdr['NAXIS6'] vis_hdr['CTYPE5'] = vis_hdr['CTYPE6'] vis_hdr['CRVAL5'] = vis_hdr['CRVAL6'] vis_hdr['CDELT5'] = vis_hdr['CDELT6'] vis_hdr['CRPIX5'] = vis_hdr['CRPIX6'] vis_hdr['CROTA5'] = vis_hdr['CROTA6'] vis_hdr['NAXIS6'] = vis_hdr['NAXIS7'] vis_hdr['CTYPE6'] = vis_hdr['CTYPE7'] vis_hdr['CRVAL6'] = vis_hdr['CRVAL7'] vis_hdr['CDELT6'] = vis_hdr['CDELT7'] vis_hdr['CRPIX6'] = vis_hdr['CRPIX7'] vis_hdr['CROTA6'] = vis_hdr['CROTA7'] vis_hdr.pop('NAXIS7') vis_hdr.pop('CTYPE7') vis_hdr.pop('CRVAL7') vis_hdr.pop('CDELT7') vis_hdr.pop('CRPIX7') vis_hdr.pop('CROTA7') par_names = vis_hdu.data.parnames group_parameter_list = [ vis_hdu.data.par(ind) for ind in range(len(par_names)) ] vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames['AIPS AN']] write_file = os.path.join(DATA_PATH, 'test/outtest_casa.uvfits') hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) pols_to_keep = [-1, -2] uvtest.checkWarnings(uvfits_uv.read, [write_file], {'polarizations': pols_to_keep}, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read, [write_file], message='Telescope EVLA is not') uvfits_uv2.select(polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2
def test_readwriteread(): """ CASA tutorial uvfits loopback test. Read in uvfits file, write out new uvfits file, read back in and check for object equality. """ uv_in = UVData() uv_out = UVData() testfile = os.path.join(DATA_PATH, 'day2_TDEM0003_10s_norx_1src_1spw.uvfits') write_file = os.path.join(DATA_PATH, 'test/outtest_casa.uvfits') uvtest.checkWarnings(uv_in.read, [testfile], message='Telescope EVLA is not') uv_in.write_uvfits(write_file) uvtest.checkWarnings(uv_out.read, [write_file], message='Telescope EVLA is not') assert uv_in == uv_out # test that it works with write_lst = False uv_in.write_uvfits(write_file, write_lst=False) uvtest.checkWarnings(uv_out.read, [write_file], message='Telescope EVLA is not') assert uv_in == uv_out # check that if x_orientation is set, it's read back out properly uv_in.x_orientation = 'east' uv_in.write_uvfits(write_file) uvtest.checkWarnings(uv_out.read, [write_file], message='Telescope EVLA is not') assert uv_in == uv_out # check that if antenna_diameters is set, it's read back out properly uvtest.checkWarnings(uv_in.read, [testfile], message='Telescope EVLA is not') uv_in.antenna_diameters = np.zeros( (uv_in.Nants_telescope, ), dtype=np.float) + 14.0 uv_in.write_uvfits(write_file) uvtest.checkWarnings(uv_out.read, [write_file], message='Telescope EVLA is not') assert uv_in == uv_out # check that if antenna_numbers are > 256 everything works uvtest.checkWarnings(uv_in.read, [testfile], message='Telescope EVLA is not') uv_in.antenna_numbers = uv_in.antenna_numbers + 256 uv_in.ant_1_array = uv_in.ant_1_array + 256 uv_in.ant_2_array = uv_in.ant_2_array + 256 uv_in.baseline_array = uv_in.antnums_to_baseline(uv_in.ant_1_array, uv_in.ant_2_array) uvtest.checkWarnings( uv_in.write_uvfits, [write_file], message='antnums_to_baseline: found > 256 antennas, using 2048 baseline' ) uvtest.checkWarnings(uv_out.read, [write_file], message='Telescope EVLA is not') assert uv_in == uv_out # check missing telescope_name, timesys vs timsys spelling, xyz_telescope_frame=???? with fits.open(write_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() vis_hdr.pop('TELESCOP') vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames['AIPS AN']] ant_hdr = ant_hdu.header.copy() time_sys = ant_hdr.pop('TIMSYS') ant_hdr['TIMESYS'] = time_sys ant_hdr['FRAME'] = '????' ant_hdu.header = ant_hdr hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) uvtest.checkWarnings(uv_out.read, [write_file], message='Telescope EVLA is not') assert uv_out.telescope_name == 'EVLA' assert uv_out.timesys == time_sys # check error if timesys is 'IAT' uvtest.checkWarnings(uv_in.read, [testfile], message='Telescope EVLA is not') uv_in.timesys = 'IAT' pytest.raises(ValueError, uv_in.write_uvfits, write_file) uv_in.timesys = 'UTC' # check error if one time & no inttime specified uv_singlet = uv_in.select(times=uv_in.time_array[0], inplace=False) uv_singlet.write_uvfits(write_file) with fits.open(write_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data par_names = np.array(vis_hdu.data.parnames) pars_use = np.where(par_names != 'INTTIM')[0] par_names = par_names[pars_use].tolist() group_parameter_list = [vis_hdu.data.par(name) for name in par_names] vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames['AIPS AN']] hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) uvtest.checkWarnings( pytest.raises, [ValueError, uv_out.read, write_file], message=[ 'Telescope EVLA is not', 'ERFA function "utcut1" yielded 1 of "dubious year (Note 3)"', 'ERFA function "utctai" yielded 1 of "dubious year (Note 3)"', 'LST values stored in this file are not self-consistent' ], nwarnings=4, category=[ UserWarning, astropy._erfa.core.ErfaWarning, astropy._erfa.core.ErfaWarning, UserWarning ]) # check that unflagged data with nsample = 0 will cause warnings uv_in.nsample_array[list(range(11, 22))] = 0 uv_in.flag_array[list(range(11, 22))] = False uvtest.checkWarnings(uv_in.write_uvfits, [write_file], message='Some unflagged data has nsample = 0') del (uv_in) del (uv_out)
def test_select_read(tmp_path): uvfits_uv = UVData() uvfits_uv2 = UVData() uvfits_file = os.path.join(DATA_PATH, "day2_TDEM0003_10s_norx_1src_1spw.uvfits") # select on antennas ants_to_keep = np.array([0, 19, 11, 24, 3, 23, 1, 20, 21]) uvfits_uv.read(uvfits_file, antenna_nums=ants_to_keep) uvfits_uv2.read(uvfits_file) uvfits_uv2.select(antenna_nums=ants_to_keep) assert uvfits_uv == uvfits_uv2 # select on frequency channels chans_to_keep = np.arange(12, 22) uvfits_uv.read(uvfits_file, freq_chans=chans_to_keep) uvfits_uv2.read(uvfits_file) uvfits_uv2.select(freq_chans=chans_to_keep) assert uvfits_uv == uvfits_uv2 # check writing & reading single frequency files uvfits_uv.select(freq_chans=[0]) testfile = str(tmp_path / "outtest_casa.uvfits") uvfits_uv.write_uvfits(testfile) uvfits_uv2.read(testfile) assert uvfits_uv == uvfits_uv2 # select on pols pols_to_keep = [-1, -2] uvfits_uv.read(uvfits_file, polarizations=pols_to_keep) uvfits_uv2.read(uvfits_file) uvfits_uv2.select(polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2 # select on read using time_range unique_times = np.unique(uvfits_uv.time_array) uvfits_uv.read(uvfits_file, time_range=[unique_times[0], unique_times[1]]) uvfits_uv2.read(uvfits_file) uvfits_uv2.select(times=unique_times[0:2]) assert uvfits_uv == uvfits_uv2 # now test selecting on multiple axes # frequencies first uvfits_uv.read( uvfits_file, antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep, ) uvfits_uv2.read(uvfits_file) uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2 # baselines first ants_to_keep = np.array([0, 1]) uvfits_uv.read( uvfits_file, antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep, ) uvfits_uv2.read(uvfits_file) uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2 # polarizations first ants_to_keep = np.array([0, 1, 2, 3, 6, 7, 8, 11, 14, 18, 19, 20, 21, 22]) chans_to_keep = np.arange(12, 64) uvfits_uv.read( uvfits_file, antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep, ) uvfits_uv2.read(uvfits_file) uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2 # repeat with no spw file uvfitsfile_no_spw = os.path.join(DATA_PATH, "zen.2456865.60537.xy.uvcRREAAM.uvfits") # select on antennas ants_to_keep = np.array([2, 4, 5]) uvfits_uv.read(uvfitsfile_no_spw, antenna_nums=ants_to_keep) uvfits_uv2.read(uvfitsfile_no_spw) uvfits_uv2.select(antenna_nums=ants_to_keep) assert uvfits_uv == uvfits_uv2 # select on frequency channels chans_to_keep = np.arange(4, 8) uvfits_uv.read(uvfitsfile_no_spw, freq_chans=chans_to_keep) uvfits_uv2.read(uvfitsfile_no_spw) uvfits_uv2.select(freq_chans=chans_to_keep) assert uvfits_uv == uvfits_uv2 # select on pols # this requires writing a new file because the no spw file we have has only 1 pol with fits.open(uvfits_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data raw_data_array = raw_data_array[:, :, :, 0, :, :, :] vis_hdr["NAXIS"] = 6 vis_hdr["NAXIS5"] = vis_hdr["NAXIS6"] vis_hdr["CTYPE5"] = vis_hdr["CTYPE6"] vis_hdr["CRVAL5"] = vis_hdr["CRVAL6"] vis_hdr["CDELT5"] = vis_hdr["CDELT6"] vis_hdr["CRPIX5"] = vis_hdr["CRPIX6"] vis_hdr["CROTA5"] = vis_hdr["CROTA6"] vis_hdr["NAXIS6"] = vis_hdr["NAXIS7"] vis_hdr["CTYPE6"] = vis_hdr["CTYPE7"] vis_hdr["CRVAL6"] = vis_hdr["CRVAL7"] vis_hdr["CDELT6"] = vis_hdr["CDELT7"] vis_hdr["CRPIX6"] = vis_hdr["CRPIX7"] vis_hdr["CROTA6"] = vis_hdr["CROTA7"] vis_hdr.pop("NAXIS7") vis_hdr.pop("CTYPE7") vis_hdr.pop("CRVAL7") vis_hdr.pop("CDELT7") vis_hdr.pop("CRPIX7") vis_hdr.pop("CROTA7") par_names = vis_hdu.data.parnames group_parameter_list = [ vis_hdu.data.par(ind) for ind in range(len(par_names)) ] vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames["AIPS AN"]] write_file = str(tmp_path / "outtest_casa.uvfits") hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) pols_to_keep = [-1, -2] uvfits_uv.read(write_file, polarizations=pols_to_keep) uvfits_uv2.read(uvfits_file) uvfits_uv2.select(polarizations=pols_to_keep) assert uvfits_uv == uvfits_uv2
def test_readwriteread(tmp_path): """ CASA tutorial uvfits loopback test. Read in uvfits file, write out new uvfits file, read back in and check for object equality. """ uv_in = UVData() uv_out = UVData() testfile = os.path.join(DATA_PATH, "day2_TDEM0003_10s_norx_1src_1spw.uvfits") write_file = str(tmp_path / "outtest_casa.uvfits") uv_in.read(testfile) uv_in.write_uvfits(write_file) uv_out.read(write_file) assert uv_in == uv_out # test that it works with write_lst = False uv_in.write_uvfits(write_file, write_lst=False) uv_out.read(write_file) assert uv_in == uv_out # check that if x_orientation is set, it's read back out properly uv_in.x_orientation = "east" uv_in.write_uvfits(write_file) uv_out.read(write_file) assert uv_in == uv_out # check that if antenna_diameters is set, it's read back out properly uv_in.read(testfile) uv_in.antenna_diameters = np.zeros( (uv_in.Nants_telescope, ), dtype=np.float) + 14.0 uv_in.write_uvfits(write_file) uv_out.read(write_file) assert uv_in == uv_out # check that if antenna_numbers are > 256 everything works uv_in.read(testfile) uv_in.antenna_numbers = uv_in.antenna_numbers + 256 uv_in.ant_1_array = uv_in.ant_1_array + 256 uv_in.ant_2_array = uv_in.ant_2_array + 256 uv_in.baseline_array = uv_in.antnums_to_baseline(uv_in.ant_1_array, uv_in.ant_2_array) uvtest.checkWarnings( uv_in.write_uvfits, [write_file], message= "antnums_to_baseline: found > 256 antennas, using 2048 baseline", ) uv_out.read(write_file) assert uv_in == uv_out # check missing telescope_name, timesys vs timsys spelling, xyz_telescope_frame=???? with fits.open(write_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() vis_hdr.pop("TELESCOP") vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames["AIPS AN"]] ant_hdr = ant_hdu.header.copy() time_sys = ant_hdr.pop("TIMSYS") ant_hdr["TIMESYS"] = time_sys ant_hdr["FRAME"] = "????" ant_hdu.header = ant_hdr hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) uv_out.read(write_file) assert uv_out.telescope_name == "EVLA" assert uv_out.timesys == time_sys # check error if timesys is 'IAT' uv_in.read(testfile) uv_in.timesys = "IAT" with pytest.raises(ValueError) as cm: uv_in.write_uvfits(write_file) assert str(cm.value).startswith( "This file has a time system IAT. " 'Only "UTC" time system files are supported') uv_in.timesys = "UTC" # check error if one time & no inttime specified uv_singlet = uv_in.select(times=uv_in.time_array[0], inplace=False) uv_singlet.write_uvfits(write_file) with fits.open(write_file, memmap=True) as hdu_list: hdunames = uvutils._fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data par_names = np.array(vis_hdu.data.parnames) pars_use = np.where(par_names != "INTTIM")[0] par_names = par_names[pars_use].tolist() group_parameter_list = [vis_hdu.data.par(name) for name in par_names] vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames["AIPS AN"]] hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) hdulist.writeto(write_file, overwrite=True) with pytest.raises(ValueError) as cm: uvtest.checkWarnings( uv_out.read, func_args=[write_file], message=[ "Telescope EVLA is not", 'ERFA function "utcut1" yielded 1 of "dubious year (Note 3)"', 'ERFA function "utctai" yielded 1 of "dubious year (Note 3)"', "LST values stored in this file are not self-consistent", ], nwarnings=4, category=[ UserWarning, astropy._erfa.core.ErfaWarning, astropy._erfa.core.ErfaWarning, UserWarning, ], ) assert str(cm.value).startswith( "integration time not specified and only one time present") # check that unflagged data with nsample = 0 will cause warnings uv_in.nsample_array[list(range(11, 22))] = 0 uv_in.flag_array[list(range(11, 22))] = False uvtest.checkWarnings(uv_in.write_uvfits, [write_file], message="Some unflagged data has nsample = 0")
def uvf_combine(uvf_list, outp='merged.uvfits', mode='normal'): uvf_list = check_uvf_antab(uvf_list) r_bands, if_pos = check_uvf_ifs(uvf_list) Nif = len(r_bands) Nuvf = len(uvf_list) # UU, VV and WW, BASELINE, DATE, DATE, INTTIM AND DATA exp_keys = ['UU', 'VV', 'WW', 'BASELINE', 'DATE', 'INTTIM'] comb_data = [[] for i in range(8)] uvf_hdul = [0 for i in range(Nuvf)] for (i, uvf) in enumerate(uvf_list): uvf_hdul[i] = pyfits.open(uvf) # GET THE TIMESTAMPS print('Computing time and baseline stamps ...') all_rjds = [] for i in range(Nuvf): data = uvf_hdul[i][0].data keys = data.parnames idx, iek = index_uvf_keys(keys, exp_keys) jds = np.float64(data.par(idx[4])) + np.float64(data.par(idx[5])) all_rjds += list(set(jds)) all_rjds = list(set(all_rjds)) all_rjds.sort() # SORTED UNION OF TIMESTAMPS Ntime = len(all_rjds) print('T-B stamps done!') # GET THE BASELINES AT ALL TIMESTAMPS print('Segmenting datasets ...') all_bsls = [0 for i in range(Ntime)] for (i, rjd) in enumerate(all_rjds): # ST_BSLS: BASELINES AT A SINGLE TIMESTAMP (FOR EACH UVFIT, LIST OF LIST). # THIS LIST WILL BE USED TO GET THE UVW AND INTTIM st_bsls = [] # T_BSLS: BASELINES AT A SINGLE TIMESTAMP (FOR ALL UVFITS) t_bsls = [] st_intts = [] st_uus, st_vvs, st_wws = [], [], [] st_data = [] for j in range(Nuvf): data = uvf_hdul[j][0].data keys = data.parnames idx, iek = index_uvf_keys(keys, exp_keys) jds = np.float64(data.par(idx[4])) + np.float64(data.par(idx[5])) flt = jds == rjd bsl = list(data.par(idx[3])[flt]) t_bsls += bsl st_bsls.append(bsl) # THE ORDER OF BSL DOES NOT EFFECT THE ORDER OF U, V, W AND INTTS st_uus.append(list(data.par(idx[0])[flt])) st_vvs.append(list(data.par(idx[1])[flt])) st_wws.append(list(data.par(idx[2])[flt])) st_intts.append(list(data.par(idx[6])[flt])) st_data.append(data.par(idx[7])[flt]) # print data.par(idx[7])[flt].shape if mode == 'check_scan': st_data[-1][:, :, :, :, :, :, 0] = j + 1.0 st_data[-1][:, :, :, :, :, :, 1] = 0.0 st_data[-1][:, :, :, :, :, :, 2] = 2.0 t_bsls = list(set(t_bsls)) t_bsls.sort() # SORTED UNION BASELINES AT A GIVEN TIME # PREPARE THE UVW AND INTTIM for t_bsl in t_bsls: filled = False _arr_st_data = np.zeros((1, 1, Nif, 1, 4, 3), dtype=np.float64) # ITERATE ON UVFITS for (j, st_bsl) in enumerate(st_bsls): if t_bsl in st_bsl: k = st_bsl.index(t_bsl) if not filled: comb_data[0].append(st_uus[j][k]) comb_data[1].append(st_vvs[j][k]) comb_data[2].append(st_wws[j][k]) comb_data[6].append(st_intts[j][k]) filled = True # NOW IT IS TIME TO WORK ON IFS !!! for (l, ip) in enumerate(if_pos[j]): _arr_st_data[:, :, ip, :, :, :] = st_data[j][k][:, :, l, :, :, :] comb_data[7].append(_arr_st_data) del _arr_st_data all_bsls[i] = t_bsls del data, jds, bsl, flt, st_bsls, st_intts, t_bsls, del t_bsl, st_bsl, st_uus, st_vvs, st_wws print('Segmenting done!') print('Merging datasets ...') Nvis = len(comb_data[0]) # WHEN TIMESTAMPS, BASELINES AND INTTIM ARE READY for (i, rjd) in enumerate(all_rjds): t_nbsl = len(all_bsls[i]) comb_data[3] += all_bsls[i] comb_data[4] += [int(rjd)] * t_nbsl comb_data[5] += [rjd - int(rjd)] * t_nbsl # DATA IS READY for i in range(len(iek)): comb_data[i] = np.array(comb_data[i], dtype=np.float64) gdata = pyfits.GroupData(input=comb_data[-1], parnames=iek[:-1], pardata=comb_data[:-1], bscale=1.0, bzero=0.0, bitpix=-32) ghdu = pyfits.GroupsHDU(gdata) # FORMATTING THE FITS HEADER. FOR SOME CASES THE KEY-VALUES FROM ORIGINAL # HEADER ARE BORROWED. NORMALLY THIS SHOULD BE FINE, IF NOT, PLEASE JUST LOAD # THE ORIGINAL DATASET INTO DIFMAP AND THEN SAVE IT OUT. SO THAT THE HEADERS # ARE AUTOMATICALLY FORMATTED. print('Formatting FITS header ...') hdr0 = uvf_hdul[0][0].header cards = [] # Complex cards.append(('CTYPE2', 'COMPLEX', '')) cards.append(('CRPIX2', 1.0, '')) cards.append(('CRVAL2', 1.0, '')) cards.append(('CDELT2', 1.0, '')) cards.append(('CROTA2', 0.0, '')) # Stokes cards.append(('CTYPE3', 'STOKES', '')) cards.append(('CRPIX3', 1.0, '')) cards.append(('CRVAL3', hdr0['CRVAL3'], '')) cards.append(('CDELT3', hdr0['CDELT3'], '')) cards.append(('CROTA3', 0.0, '')) # FREQ if_freq = (r_bands.real + r_bands.imag) * 0.5 ch_width = (r_bands.imag - r_bands.real) cards.append(('CTYPE4', 'FREQ', '')) cards.append(('CRPIX4', 1.0, '')) cards.append(('CRVAL4', if_freq[0], '')) cards.append(('CDELT4', ch_width[0], '')) cards.append(('CROTA4', 0.0, '')) del if_freq, ch_width # IF cards.append(('CTYPE5', 'IF', '')) cards.append(('CRPIX5', 1.0, '')) cards.append(('CRVAL5', 1.0, '')) cards.append(('CDELT5', 1.0, '')) cards.append(('CROTA5', 0.0, '')) # RA & Dec cards.append(('CTYPE6', 'RA', '')) cards.append(('CRPIX6', 1.0, '')) cards.append(('CRVAL6', hdr0['CRVAL6'], '')) cards.append(('CDELT6', 1.0, '')) cards.append(('CROTA6', 0.0, '')) cards.append(('CTYPE7', 'DEC', '')) cards.append(('CRPIX7', 1.0, '')) cards.append(('CRVAL7', hdr0['CRVAL7'], '')) cards.append(('CDELT7', 1.0, '')) cards.append(('CROTA7', 0.0, '')) for card in cards: ghdu.header.append(card) # PTYPE HEADER cards = [] pcount = 7 # DIFMAP DEFAULTS, SHOULD NOT BE A PROBLEM FOR MOST CASES for i in range(1, pcount + 1): pk = 'PTYPE%d' % i cards.append((pk, hdr0[pk])) cards.append(('PSCAL%d' % i, 1.0)) cards.append(('PZERO%d' % i, 0.0)) for card in cards: ghdu.header.append(card) # Other Header utc = Time(all_rjds[0], scale='utc', format='jd') cards = [] cards.append(('DATE-OBS', utc.isot[:10])) brr_keys = ['TELESCOP', 'INSTRUME', 'OBSERVER', 'OBJECT', 'BUNIT'] for bk in brr_keys: cards.append((bk, borrow_cards(hdr0, bk))) eq_keys = ['EQUINOX', 'EPOCH'] eq_keys = [ek for ek in eq_keys if ek in hdr0.keys()] if len(eq_keys) >= 1: ek = eq_keys[0] try: cards.append(('EPOCH', float(hdr0[ek]))) except: cards.append(('EPOCH', 2000.0)) else: cards.append(('EPOCH', 2000.0)) cards.append(('BSCALE', 1.0)) cards.append(('BSZERO', 0.0)) cards.append(('VELREF', 3)) cards.append(('ALTRVAL', 0.0)) cards.append(('ALTRPIX', 1.0)) cards.append(('OBSRA', hdr0['CRVAL6'], '')) cards.append(('OBSDEC', hdr0['CRVAL7'], '')) cards.append(('RESTFREQ', 0.0)) for card in cards: ghdu.header.append(card) # THE VISIBILITIES SHOULD BE ALREADY TB SORTED. # ADD IT TO HISTORY SO THAT AIPS CAN RECOGNIZE IT. ghdu.header['HISTORY'] = "AIPS SORT ORDER = 'TB'" # REFORMAT AN AND FQ TABLE # IN THE CASE OF SINGLE IF, THE ARRAY HAS TO BE TRANSPOSED print('Appending AN and FQ Tables ...') antab = uvf_hdul[0]['AIPS AN'] if_freq = (r_bands.real + r_bands.imag) * 0.5 if_freq = (if_freq - if_freq[0])[np.newaxis] ch_width = (r_bands.imag - r_bands.real)[np.newaxis] tot_bw = ch_width * 1.0 sb = np.ones_like(tot_bw) fq_keys = ['FRQSEL', 'IF FREQ', 'CH WIDTH', 'TOTAL BANDWIDTH', 'SIDEBAND'] fq_formats = ['1J', '%dD' % Nif, '%dE' % Nif, '%dE' % Nif, '%dJ' % Nif] fq_data = [np.array([1]), if_freq, ch_width, tot_bw, sb] cols = [pyfits.Column(name=fq_keys[k], format=fq_formats[k], \ array=fq_data[k]) for k in range(len(fq_keys))] fqtab = pyfits.BinTableHDU.from_columns(cols, name='AIPS FQ') fh = fqtab.header fh['NO_IF'] = Nif comb_hdu = pyfits.HDUList([ghdu, antab, fqtab]) comb_hdu.writeto(outp, output_verify='silentfix', overwrite=True) for uvf in uvf_list: os.system('rm -rf %s' % uvf) print('Done!!! \nMerged visibility data: %s' % outp)
v_slice[0, 0, :, 0, 0] = real(xx_visibilities[i, :n_freq]) v_slice[0, 0, :, 0, 1] = imag(xx_visibilities[i, :n_freq]) v_slice[0, 0, :, 0, 2] = 1.0 v_slice[0, 0, :, 1, 0] = real(yy_visibilities[i, :n_freq]) v_slice[0, 0, :, 1, 1] = imag(yy_visibilities[i, :n_freq]) v_slice[0, 0, :, 1, 2] = 1.0 v_container[i] = v_slice #uvparnames = ['UU','VV','WW','BASELINE_ARR','TIME'] uvparnames = ['UU', 'VV', 'WW', 'BASELINE_ARR', 'DATE'] parvals = [uu, vv, ww, baseline, date] #pzeros = [0.0,0.0,0.0,0.0,date_0] #pscales = [1.0] * 5 hdu10 = fits.GroupData(v_container, parnames=uvparnames, pardata=parvals, bitpix=-32) # hdu10 = fits.GroupsHDU(hdu10) hdu10.header['CTYPE2'] = 'COMPLEX ' hdu10.header['CRVAL2'] = 1.0 hdu10.header['CRPIX2'] = 1.0 hdu10.header['CDELT2'] = 1.0 hdu10.header['CTYPE3'] = 'STOKES ' hdu10.header['CRVAL3'] = -5.0 hdu10.header['CRPIX3'] = 1.0 hdu10.header['CDELT3'] = -1.0
def test_select_read(): uvfits_uv = UVData() uvfits_uv2 = UVData() uvfits_file = os.path.join(DATA_PATH, 'day2_TDEM0003_10s_norx_1src_1spw.uvfits') # select on antennas ants_to_keep = np.array([0, 19, 11, 24, 3, 23, 1, 20, 21]) uvtest.checkWarnings(uvfits_uv.read_uvfits, [uvfits_file], {'antenna_nums': ants_to_keep}, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(antenna_nums=ants_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2) # select on frequency channels chans_to_keep = np.arange(12, 22) uvtest.checkWarnings(uvfits_uv.read_uvfits, [uvfits_file], {'freq_chans': chans_to_keep}, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(freq_chans=chans_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2) # select on pols pols_to_keep = [-1, -2] uvtest.checkWarnings(uvfits_uv.read_uvfits, [uvfits_file], {'polarizations': pols_to_keep}, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(polarizations=pols_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2) # now test selecting on multiple axes # frequencies first uvtest.checkWarnings(uvfits_uv.read_uvfits, [uvfits_file], { 'antenna_nums': ants_to_keep, 'freq_chans': chans_to_keep, 'polarizations': pols_to_keep }, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2) # baselines first ants_to_keep = np.array([0, 1]) uvtest.checkWarnings(uvfits_uv.read_uvfits, [uvfits_file], { 'antenna_nums': ants_to_keep, 'freq_chans': chans_to_keep, 'polarizations': pols_to_keep }, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2) # polarizations first ants_to_keep = np.array([0, 1, 2, 3, 6, 7, 8, 11, 14, 18, 19, 20, 21, 22]) chans_to_keep = np.arange(12, 64) uvtest.checkWarnings(uvfits_uv.read_uvfits, [uvfits_file], { 'antenna_nums': ants_to_keep, 'freq_chans': chans_to_keep, 'polarizations': pols_to_keep }, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [uvfits_file], message='Telescope EVLA is not') uvfits_uv2.select(antenna_nums=ants_to_keep, freq_chans=chans_to_keep, polarizations=pols_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2) # repeat with no spw file uvfitsfile_no_spw = os.path.join(DATA_PATH, 'zen.2456865.60537.xy.uvcRREAAM.uvfits') # select on antennas ants_to_keep = np.array([2, 4, 5]) uvtest.checkWarnings(uvfits_uv.read_uvfits, [uvfitsfile_no_spw], {'antenna_nums': ants_to_keep}, known_warning='paper_uvfits') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [uvfitsfile_no_spw], known_warning='paper_uvfits') uvfits_uv2.select(antenna_nums=ants_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2) # select on frequency channels chans_to_keep = np.arange(4, 8) uvtest.checkWarnings(uvfits_uv.read_uvfits, [uvfitsfile_no_spw], {'freq_chans': chans_to_keep}, known_warning='paper_uvfits') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [uvfitsfile_no_spw], known_warning='paper_uvfits') uvfits_uv2.select(freq_chans=chans_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2) # select on pols # this requires writing a new file because the no spw file we have has only 1 pol hdu_list = fits.open(uvfits_file) hdunames = uvutils.fits_indexhdus(hdu_list) vis_hdu = hdu_list[0] vis_hdr = vis_hdu.header.copy() raw_data_array = vis_hdu.data.data raw_data_array = raw_data_array[:, :, :, 0, :, :, :] vis_hdr['NAXIS'] = 6 vis_hdr['NAXIS5'] = vis_hdr['NAXIS6'] vis_hdr['CTYPE5'] = vis_hdr['CTYPE6'] vis_hdr['CRVAL5'] = vis_hdr['CRVAL6'] vis_hdr['CDELT5'] = vis_hdr['CDELT6'] vis_hdr['CRPIX5'] = vis_hdr['CRPIX6'] vis_hdr['CROTA5'] = vis_hdr['CROTA6'] vis_hdr['NAXIS6'] = vis_hdr['NAXIS7'] vis_hdr['CTYPE6'] = vis_hdr['CTYPE7'] vis_hdr['CRVAL6'] = vis_hdr['CRVAL7'] vis_hdr['CDELT6'] = vis_hdr['CDELT7'] vis_hdr['CRPIX6'] = vis_hdr['CRPIX7'] vis_hdr['CROTA6'] = vis_hdr['CROTA7'] vis_hdr.pop('NAXIS7') vis_hdr.pop('CTYPE7') vis_hdr.pop('CRVAL7') vis_hdr.pop('CDELT7') vis_hdr.pop('CRPIX7') vis_hdr.pop('CROTA7') par_names = vis_hdu.data.parnames group_parameter_list = [ vis_hdu.data.par(ind) for ind in range(len(par_names)) ] vis_hdu = fits.GroupData(raw_data_array, parnames=par_names, pardata=group_parameter_list, bitpix=-32) vis_hdu = fits.GroupsHDU(vis_hdu) vis_hdu.header = vis_hdr ant_hdu = hdu_list[hdunames['AIPS AN']] write_file = os.path.join(DATA_PATH, 'test/outtest_casa.uvfits') hdulist = fits.HDUList(hdus=[vis_hdu, ant_hdu]) if float(astropy.__version__[0:3]) < 1.3: hdulist.writeto(write_file, clobber=True) else: hdulist.writeto(write_file, overwrite=True) pols_to_keep = [-1, -2] uvtest.checkWarnings(uvfits_uv.read_uvfits, [write_file], {'polarizations': pols_to_keep}, message='Telescope EVLA is not') uvtest.checkWarnings(uvfits_uv2.read_uvfits, [write_file], message='Telescope EVLA is not') uvfits_uv2.select(polarizations=pols_to_keep) nt.assert_equal(uvfits_uv, uvfits_uv2)
def create_uvfits(template_uvfits=None, freq_cent=None, ra_point=None, dec_point=None, oskar_vis_tag=None, output_uvfits_name=None, date=None): '''Takes OSKAR uv data and converts into a uvfits file''' int_jd, float_jd = calc_jdcal(date) template_file = fits.open(template_uvfits) template_data = template_file[0].data antenna_table = template_file[1].data # Create uv structure by hand, probably there is a better way of doing this but the uvfits structure is kind of finicky n_freq = 1 # only one frequency per uvfits file as read by the RTS n_data = len(template_data) v_container = zeros((n_data, 1, 1, 1, n_freq, 4, 3)) uu = zeros(n_data) vv = zeros(n_data) ww = zeros(n_data) baseline = zeros(n_data) date_array = zeros(n_data) xx_us, xx_vs, xx_ws, xx_res, xx_ims = get_osk_data( oskar_vis_tag=oskar_vis_tag, polarisation='XX') yy_us, yy_vs, yy_ws, yy_res, yy_ims = get_osk_data( oskar_vis_tag=oskar_vis_tag, polarisation='YY') xy_us, xy_vs, xy_ws, xy_res, xy_ims = get_osk_data( oskar_vis_tag=oskar_vis_tag, polarisation='XY') yx_us, yx_vs, yx_ws, yx_res, yx_ims = get_osk_data( oskar_vis_tag=oskar_vis_tag, polarisation='YX') for i in range(len(template_data)): xx_list = [xx_res[i], xx_ims[i], 1.0] yy_list = [yy_res[i], yy_ims[i], 1.0] xy_list = [xy_res[i], xy_ims[i], 1.0] yx_list = [yx_res[i], yx_ims[i], 1.0] uvdata = [xx_list, yy_list, xy_list, yx_list] uvdata = array(uvdata) uvdata.shape = (4, 3) v_container[i] = uvdata uu[i] = xx_us[i] / freq_cent vv[i] = xx_vs[i] / freq_cent ww[i] = xx_ws[i] / freq_cent baseline[i] = template_data[i][3] date_array[i] = float_jd rotate_phase(xx_ws[i], v_container[i][0, 0, 0, 0, :, :]) ##UU, VV, WW don't actually get read in by RTS - might be an issue with ##miriad/wsclean however, as it looks like oskar w = negative maps w uvparnames = ['UU', 'VV', 'WW', 'BASELINE', 'DATE'] parvals = [uu, vv, ww, baseline, date_array] uvhdu = fits.GroupData(v_container, parnames=uvparnames, pardata=parvals, bitpix=-32) uvhdu = fits.GroupsHDU(uvhdu) ###Try to copy MAPS as sensibly as possible uvhdu.header['CTYPE2'] = 'COMPLEX ' uvhdu.header['CRVAL2'] = 1.0 uvhdu.header['CRPIX2'] = 1.0 uvhdu.header['CDELT2'] = 1.0 ##This means it's linearly polarised uvhdu.header['CTYPE3'] = 'STOKES ' uvhdu.header['CRVAL3'] = -5.0 uvhdu.header['CRPIX3'] = 1.0 uvhdu.header['CDELT3'] = -1.0 uvhdu.header['CTYPE4'] = 'FREQ' ###Oskar/CASA for some reason adds half of the frequency specified in the ###simulation setup. I think this is happens because CASA is unsure ###what 'channel' the data is - when you run with multiple channels, they ###are all set to spw = 0, but the output freq is correct. Somethig funky anyway ###For one channel, set by hand uvhdu.header['CRVAL4'] = freq_cent ##(sim freq + half channel width) uvhdu.header['CRPIX4'] = template_file[0].header['CRPIX4'] uvhdu.header['CDELT4'] = template_file[0].header['CDELT4'] uvhdu.header['CTYPE5'] = template_file[0].header['CTYPE5'] uvhdu.header['CRVAL5'] = template_file[0].header['CRVAL5'] uvhdu.header['CRPIX5'] = template_file[0].header['CRPIX5'] uvhdu.header['CDELT5'] = template_file[0].header['CDELT5'] uvhdu.header['CTYPE6'] = template_file[0].header['CTYPE6'] uvhdu.header['CRVAL6'] = template_file[0].header['CRVAL6'] uvhdu.header['CRPIX6'] = template_file[0].header['CRPIX6'] uvhdu.header['CDELT6'] = template_file[0].header['CDELT6'] uvhdu.header['CTYPE7'] = template_file[0].header['CTYPE7'] uvhdu.header['CRVAL7'] = template_file[0].header['CRVAL7'] uvhdu.header['CRPIX7'] = template_file[0].header['CRPIX7'] uvhdu.header['CDELT7'] = template_file[0].header['CDELT7'] ## Write the parameters scaling explictly because they are omitted if default 1/0 uvhdu.header['PSCAL1'] = 1.0 uvhdu.header['PZERO1'] = 0.0 uvhdu.header['PSCAL2'] = 1.0 uvhdu.header['PZERO2'] = 0.0 uvhdu.header['PSCAL3'] = 1.0 uvhdu.header['PZERO3'] = 0.0 uvhdu.header['PSCAL4'] = 1.0 uvhdu.header['PZERO4'] = 0.0 uvhdu.header['PSCAL5'] = 1.0 uvhdu.header['PZERO5'] = float(int_jd) uvhdu.header['OBJECT'] = 'Undefined' uvhdu.header['OBSRA'] = ra_point uvhdu.header['OBSDEC'] = dec_point ##ANTENNA TABLE MODS====================================================================== template_file[1].header['FREQ'] = freq_cent ##MAJICK uses this date to set the LST dmy, hms = date.split() day, month, year = map(int, dmy.split('-')) hour, mins, secs = map(float, hms.split(':')) rdate = "%d-%02d-%2dT%2d:%2d:%.2f" % (year, month, day, hour, mins, secs) template_file[1].header['RDATE'] = rdate ## Create hdulist and write out file hdulist = fits.HDUList(hdus=[uvhdu, template_file[1]]) hdulist.writeto(output_uvfits_name, clobber=True) template_file.close() hdulist.close()
def save_obs_uvfits(obs, fname, force_singlepol=None, polrep_out='circ'): """Save observation data to uvfits. To save Stokes I as a single polarization (e.g., only RR) set force_singlepol='R' or 'L' """ # output times must be in utc obs = obs.switch_timetype(timetype_out='UTC') if polrep_out == 'circ': obs = obs.switch_polrep('circ') elif polrep_out == 'stokes': obs = obs.switch_polrep('stokes') else: raise Exception( "'polrep_out' in 'save_obs_uvfits' must be 'circ' or 'stokes'!") hdulist_new = fits.HDUList() hdulist_new.append(fits.GroupsHDU()) ##################### # AIPS Data TABLE ##################### # Data header (based on the BU format) MJD_0 = 2400000.5 header = hdulist_new['PRIMARY'].header header['OBSRA'] = obs.ra * 180. / 12. header['OBSDEC'] = obs.dec header['OBJECT'] = obs.source header['MJD'] = float(obs.mjd) header['DATE-OBS'] = Time(obs.mjd + MJD_0, format='jd', scale='utc', out_subfmt='date').iso header['BSCALE'] = 1.0 header['BZERO'] = 0.0 header['BUNIT'] = 'JY' header['VELREF'] = 3 # TODO ?? header['EQUINOX'] = 'J2000' header['ALTRPIX'] = 1.e0 header['ALTRVAL'] = 0.e0 header['TELESCOP'] = 'VLBA' # TODO Can we change this field? header['INSTRUME'] = 'VLBA' header['OBSERVER'] = 'EHT' header['CTYPE2'] = 'COMPLEX' header['CRVAL2'] = 1.e0 header['CDELT2'] = 1.e0 header['CRPIX2'] = 1.e0 header['CROTA2'] = 0.e0 header['CTYPE3'] = 'STOKES' if polrep_out == 'circ': header['CRVAL3'] = -1.e0 header['CDELT3'] = -1.e0 elif polrep_out == 'stokes': header['CRVAL3'] = 1.e0 header['CDELT3'] = 1.e0 header['CRPIX3'] = 1.e0 header['CROTA3'] = 0.e0 header['CTYPE4'] = 'FREQ' header['CRVAL4'] = obs.rf header['CDELT4'] = obs.bw header['CRPIX4'] = 1.e0 header['CROTA4'] = 0.e0 header['CTYPE6'] = 'RA' header['CRVAL6'] = header['OBSRA'] header['CDELT6'] = 1.e0 header['CRPIX6'] = 1.e0 header['CROTA6'] = 0.e0 header['CTYPE7'] = 'DEC' header['CRVAL7'] = header['OBSDEC'] header['CDELT7'] = 1.e0 header['CRPIX7'] = 1.e0 header['CROTA7'] = 0.e0 header['PTYPE1'] = 'UU---SIN' header['PSCAL1'] = 1.0 / obs.rf header['PZERO1'] = 0.e0 header['PTYPE2'] = 'VV---SIN' header['PSCAL2'] = 1.0 / obs.rf header['PZERO2'] = 0.e0 header['PTYPE3'] = 'WW---SIN' header['PSCAL3'] = 1.0 / obs.rf header['PZERO3'] = 0.e0 header['PTYPE4'] = 'BASELINE' header['PSCAL4'] = 1.e0 header['PZERO4'] = 0.e0 header['PTYPE5'] = 'DATE' header['PSCAL5'] = 1.e0 header['PZERO5'] = 0.e0 header['PTYPE6'] = 'DATE' header['PSCAL6'] = 1.e0 header['PZERO6'] = 0.0 header['PTYPE7'] = 'INTTIM' header['PSCAL7'] = 1.e0 header['PZERO7'] = 0.e0 header['PTYPE8'] = 'TAU1' header['PSCAL8'] = 1.e0 header['PZERO8'] = 0.e0 header['PTYPE9'] = 'TAU2' header['PSCAL9'] = 1.e0 header['PZERO9'] = 0.e0 header['history'] = "AIPS SORT ORDER='TB'" # Get data if polrep_out == 'circ': obsdata = obs.unpack([ 'time', 'tint', 'u', 'v', 'rrvis', 'llvis', 'rlvis', 'lrvis', 'rrsigma', 'llsigma', 'rlsigma', 'lrsigma', 't1', 't2', 'tau1', 'tau2' ]) elif polrep_out == 'stokes': obsdata = obs.unpack([ 'time', 'tint', 'u', 'v', 'vis', 'qvis', 'uvis', 'vvis', 'sigma', 'qsigma', 'usigma', 'vsigma', 't1', 't2', 'tau1', 'tau2' ]) ndat = len(obsdata['time']) # times and tints jds = (2400000.5 + obs.mjd) * np.ones(len(obsdata)) fractimes = (obsdata['time'] / 24.0) tints = obsdata['tint'] # Baselines t1 = [obs.tkey[scope] + 1 for scope in obsdata['t1']] t2 = [obs.tkey[scope] + 1 for scope in obsdata['t2']] bl = 256 * np.array(t1) + np.array(t2) # opacities tau1 = obsdata['tau1'] tau2 = obsdata['tau2'] # uv are in lightseconds u = obsdata['u'] v = obsdata['v'] # rr, ll, lr, rl, weights if polrep_out == 'circ': rr = obsdata['rrvis'] ll = obsdata['llvis'] rl = obsdata['rlvis'] lr = obsdata['lrvis'] weightrr = 1.0 / (obsdata['rrsigma']**2) weightll = 1.0 / (obsdata['llsigma']**2) weightrl = 1.0 / (obsdata['rlsigma']**2) weightlr = 1.0 / (obsdata['lrsigma']**2) # If necessary, enforce single polarization if force_singlepol == 'L': if obs.polrep == 'stokes': raise Exception( "force_singlepol only works with obs.polrep=='stokes'!") print( "force_singlepol='L': treating Stokes 'I' as LL and ignoring Q,U,V!!" ) ll = obsdata['vis'] rr = rr * 0.0 rl = rl * 0.0 lr = lr * 0.0 weightrr = weightrr * 0.0 weightrl = weightrl * 0.0 weightlr = weightlr * 0.0 elif force_singlepol == 'R': if obs.polrep == 'stokes': raise Exception( "force_singlepol only works with obs.polrep=='stokes'!") print( "force_singlepol='R': treating Stokes 'I' as RR and ignoring Q,U,V!!" ) rr = obsdata['vis'] ll = rr * 0.0 rl = rl * 0.0 lr = lr * 0.0 weightll = weightll * 0.0 weightrl = weightrl * 0.0 weightlr = weightlr * 0.0 dat1 = rr dat2 = ll dat3 = rl dat4 = lr weight1 = weightrr weight2 = weightll weight3 = weightrl weight4 = weightlr elif polrep_out == 'stokes': dat1 = obsdata['vis'] dat2 = obsdata['qvis'] dat3 = obsdata['uvis'] dat4 = obsdata['vvis'] weight1 = 1.0 / (obsdata['sigma']**2) weight2 = 1.0 / (obsdata['qsigma']**2) weight3 = 1.0 / (obsdata['usigma']**2) weight4 = 1.0 / (obsdata['vsigma']**2) # Replace nans by zeros (including zero weights) dat1 = np.nan_to_num(dat1) dat2 = np.nan_to_num(dat2) dat3 = np.nan_to_num(dat3) dat4 = np.nan_to_num(dat4) weight1 = np.nan_to_num(weight1) weight2 = np.nan_to_num(weight2) weight3 = np.nan_to_num(weight3) weight4 = np.nan_to_num(weight4) # Data array outdat = np.zeros((ndat, 1, 1, 1, 1, 4, 3)) outdat[:, 0, 0, 0, 0, 0, 0] = np.real(dat1) outdat[:, 0, 0, 0, 0, 0, 1] = np.imag(dat1) outdat[:, 0, 0, 0, 0, 0, 2] = weight1 outdat[:, 0, 0, 0, 0, 1, 0] = np.real(dat2) outdat[:, 0, 0, 0, 0, 1, 1] = np.imag(dat2) outdat[:, 0, 0, 0, 0, 1, 2] = weight2 outdat[:, 0, 0, 0, 0, 2, 0] = np.real(dat3) outdat[:, 0, 0, 0, 0, 2, 1] = np.imag(dat3) outdat[:, 0, 0, 0, 0, 2, 2] = weight3 outdat[:, 0, 0, 0, 0, 3, 0] = np.real(dat4) outdat[:, 0, 0, 0, 0, 3, 1] = np.imag(dat4) outdat[:, 0, 0, 0, 0, 3, 2] = weight4 # Save data pars = [ 'UU---SIN', 'VV---SIN', 'WW---SIN', 'BASELINE', 'DATE', 'DATE', 'INTTIM', 'TAU1', 'TAU2' ] x = fits.GroupData( outdat, parnames=pars, pardata=[u, v, np.zeros(ndat), bl, jds, fractimes, tints, tau1, tau2], bitpix=-32) hdulist_new['PRIMARY'].data = x hdulist_new['PRIMARY'].header = header # TODO necessary ?? ##################### # AIPS AN TABLE ##################### # Load the array data tarr = obs.tarr tnames = tarr['site'] tnums = np.arange(1, len(tarr) + 1) xyz = np.array([[tarr[i]['x'], tarr[i]['y'], tarr[i]['z']] for i in np.arange(len(tarr))]) sefd = tarr['sefdr'] nsta = len(tnames) col1 = fits.Column(name='ANNAME', format='8A', array=tnames) col2 = fits.Column(name='STABXYZ', format='3D', unit='METERS', array=xyz) col3 = fits.Column(name='NOSTA', format='1J', array=tnums) colfin = fits.Column(name='SEFD', format='1D', array=sefd) # TODO these antenna fields+header are questionable - look into them col4 = fits.Column(name='MNTSTA', format='1J', array=np.zeros(nsta)) col5 = fits.Column(name='STAXOF', format='1E', unit='METERS', array=np.zeros(nsta)) col6 = fits.Column(name='POLTYA', format='1A', array=np.array(['R' for i in range(nsta)], dtype='|S1')) col7 = fits.Column(name='POLAA', format='1E', unit='DEGREES', array=np.zeros(nsta)) col8 = fits.Column(name='POLCALA', format='3E', array=np.zeros((nsta, 3))) col9 = fits.Column(name='POLTYB', format='1A', array=np.array(['L' for i in range(nsta)], dtype='|S1')) col10 = fits.Column(name='POLAB', format='1E', unit='DEGREES', array=(90. * np.ones(nsta))) col11 = fits.Column(name='POLCALB', format='3E', array=np.zeros((nsta, 3))) col25 = fits.Column(name='ORBPARM', format='1E', array=np.zeros(0)) # Antenna Header params # TODO do we need to change more of these?? collist = [ col1, col2, col25, col3, col4, col5, col6, col7, col8, col9, col10, col11, colfin ] tbhdu = fits.BinTableHDU.from_columns(fits.ColDefs(collist), name='AIPS AN') hdulist_new.append(tbhdu) head = hdulist_new['AIPS AN'].header head['EXTVER'] = 1 head['ARRAYX'] = 0.e0 head['ARRAYY'] = 0.e0 head['ARRAYZ'] = 0.e0 # TODO change the reference date rdate_tt_new = Time(obs.mjd + MJD_0, format='jd', scale='utc', out_subfmt='date') rdate_out = rdate_tt_new.iso rdate_tt_new.out_subfmt = 'float' # TODO -- needed to fix subformat issue in astropy 4.0 rdate_jd_out = rdate_tt_new.jd rdate_gstiao_out = rdate_tt_new.sidereal_time('apparent', 'greenwich').degree rdate_offset_out = (rdate_tt_new.ut1.datetime.second - rdate_tt_new.utc.datetime.second) rdate_offset_out += 1.e-6 * (rdate_tt_new.ut1.datetime.microsecond - rdate_tt_new.utc.datetime.microsecond) head['RDATE'] = rdate_out head['GSTIA0'] = rdate_gstiao_out head['DEGPDY'] = 360.9856 head['UT1UTC'] = rdate_offset_out # difference between UT1 and UTC ? head['DATUTC'] = 0.e0 head['TIMESYS'] = 'UTC' head['FREQ'] = obs.rf head['POLARX'] = 0.e0 head['POLARY'] = 0.e0 head['ARRNAM'] = 'VLBA' # TODO must be recognized by aips/casa head['XYZHAND'] = 'RIGHT' head['FRAME'] = '????' head['NUMORB'] = 0 head['NO_IF'] = 1 # TODO nchan head['NOPCAL'] = 0 # TODO add pol cal information head['POLTYPE'] = 'VLBI' head['FREQID'] = 1 hdulist_new['AIPS AN'].header = head # TODO necessary, or is it a pointer? ##################### # AIPS FQ TABLE ##################### # Convert types & columns nif = 1 col1 = np.array(1, dtype=np.int32).reshape([nif]) # frqsel col2 = np.array(0.0, dtype=np.float64).reshape([nif]) # iffreq col3 = np.array([obs.bw], dtype=np.float32).reshape([nif]) # chwidth col4 = np.array([obs.bw], dtype=np.float32).reshape([nif]) # bw col5 = np.array([1], dtype=np.int32).reshape([nif]) # sideband col1 = fits.Column(name="FRQSEL", format="1J", array=col1) col2 = fits.Column(name="IF FREQ", format="%dD" % (nif), array=col2) col3 = fits.Column(name="CH WIDTH", format="%dE" % (nif), array=col3) col4 = fits.Column(name="TOTAL BANDWIDTH", format="%dE" % (nif), array=col4) col5 = fits.Column(name="SIDEBAND", format="%dJ" % (nif), array=col5) cols = fits.ColDefs([col1, col2, col3, col4, col5]) # create table tbhdu = fits.BinTableHDU.from_columns(cols) # add header information tbhdu.header.append(("NO_IF", nif, "Number IFs")) tbhdu.header.append(("EXTNAME", "AIPS FQ")) tbhdu.header.append(("EXTVER", 1)) hdulist_new.append(tbhdu) ##################### # AIPS NX TABLE ##################### scan_times = [] scan_time_ints = [] start_vis = [] stop_vis = [] # TODO make sure jds AND scan_info MUST be time sorted!! jj = 0 ROUND_SCAN_INT = 5 comp_fac = 3600 * 24 * 100 # compare to 100th of a second scan_arr = obs.scans print('Building NX table') if (scan_arr is None or len(scan_arr) == 0): print("No NX table in saved uvfits") else: try: scan_arr = scan_arr / 24. for scan in scan_arr: scan_start = round(scan[0], ROUND_SCAN_INT) scan_stop = round(scan[1], ROUND_SCAN_INT) scan_dur = (scan_stop - scan_start) if jj >= len(fractimes): # print start_vis, stop_vis break # print ("%.12f %.12f %.12f" % (fractimes[jj], scan_start, scan_stop)) jd = round(fractimes[jj], ROUND_SCAN_INT) * comp_fac # TODO precision?? if ((np.floor(jd) >= np.floor(scan_start * comp_fac)) and (np.ceil(jd) <= np.ceil(comp_fac * scan_stop))): start_vis.append(jj) # TODO AIPS MEMO 117 says scan_times should be midpoint! # but AIPS data looks likes it's at the start? scan_times.append(scan_start + 0.5 * scan_dur) # - rdate_jd_out) scan_time_ints.append(scan_dur) ceilcut = np.ceil(comp_fac * scan_stop) while ((jj < len(fractimes) and np.floor( round(fractimes[jj], ROUND_SCAN_INT) * comp_fac) <= ceilcut)): jj += 1 stop_vis.append(jj - 1) else: continue if jj < len(fractimes): print(scan_arr[-1]) print(round(scan_arr[-1][0], ROUND_SCAN_INT), round(scan_arr[-1][1], ROUND_SCAN_INT)) print(jj, len(jds), round(jds[jj], ROUND_SCAN_INT)) print( "WARNING!!!: in save_uvfits NX table, " + "didn't get to all entries when computing scan start/stop!" ) print(scan_times) time_nx = fits.Column(name="TIME", format="1D", unit='DAYS', array=np.array(scan_times)) timeint_nx = fits.Column(name="TIME INTERVAL", format="1E", unit='DAYS', array=np.array(scan_time_ints)) sourceid_nx = fits.Column(name="SOURCE ID", format="1J", unit='', array=np.ones(len(scan_times))) subarr_nx = fits.Column(name="SUBARRAY", format="1J", unit='', array=np.ones(len(scan_times))) freqid_nx = fits.Column(name="FREQ ID", format="1J", unit='', array=np.ones(len(scan_times))) startvis_nx = fits.Column(name="START VIS", format="1J", unit='', array=np.array(start_vis) + 1) endvis_nx = fits.Column(name="END VIS", format="1J", unit='', array=np.array(stop_vis) + 1) cols = fits.ColDefs([ time_nx, timeint_nx, sourceid_nx, subarr_nx, freqid_nx, startvis_nx, endvis_nx ]) tbhdu = fits.BinTableHDU.from_columns(cols) # header information tbhdu.header.append(("EXTNAME", "AIPS NX")) tbhdu.header.append(("EXTVER", 1)) hdulist_new.append(tbhdu) except TypeError: print("No NX table in saved uvfits") # Write final HDUList to file hdulist_new.writeto(fname, overwrite=True) return
def create_uvfits(v_container=None,freq_cent=None, ra_point=None, dec_point=None, output_uvfits_name=None,uu=None,vv=None,ww=None, baselines_array=None,date_array=None,date=None, central_freq_chan=None,ch_width=None,template_uvfits=None, int_jd=None): '''Takes visibility date and writes out a uvfits format file''' ##UU, VV, WW don't actually get read in by RTS - might be an issue with ##miriad/wsclean however, as it looks like oskar w = negative maps w uvparnames = ['UU','VV','WW','BASELINE','DATE'] parvals = [uu,vv,ww,baselines_array,date_array] uvhdu = fits.GroupData(v_container,parnames=uvparnames,pardata=parvals,bitpix=-32) uvhdu = fits.GroupsHDU(uvhdu) ###Try to copy MAPS as sensibly as possible uvhdu.header['CTYPE2'] = 'COMPLEX ' uvhdu.header['CRVAL2'] = 1.0 uvhdu.header['CRPIX2'] = 1.0 uvhdu.header['CDELT2'] = 1.0 ##This means it's linearly polarised uvhdu.header['CTYPE3'] = 'STOKES ' uvhdu.header['CRVAL3'] = -5.0 uvhdu.header['CRPIX3'] = 1.0 uvhdu.header['CDELT3'] = -1.0 uvhdu.header['CTYPE4'] = 'FREQ' uvhdu.header['CRVAL4'] = freq_cent ##Middle pixel value in Hz uvhdu.header['CRPIX4'] = int(central_freq_chan) + 1 ##Middle pixel number uvhdu.header['CDELT4'] = ch_width uvhdu.header['CTYPE5'] = template_uvfits[0].header['CTYPE5'] uvhdu.header['CRVAL5'] = template_uvfits[0].header['CRVAL5'] uvhdu.header['CRPIX5'] = template_uvfits[0].header['CRPIX5'] uvhdu.header['CDELT5'] = template_uvfits[0].header['CDELT5'] uvhdu.header['CTYPE6'] = template_uvfits[0].header['CTYPE6'] uvhdu.header['CRVAL6'] = ra_point uvhdu.header['CRPIX6'] = template_uvfits[0].header['CRPIX6'] uvhdu.header['CDELT6'] = template_uvfits[0].header['CDELT6'] uvhdu.header['CTYPE7'] = template_uvfits[0].header['CTYPE7'] uvhdu.header['CRVAL7'] = dec_point uvhdu.header['CRPIX7'] = template_uvfits[0].header['CRPIX7'] uvhdu.header['CDELT7'] = template_uvfits[0].header['CDELT7'] ## Write the parameters scaling explictly because they are omitted if default 1/0 uvhdu.header['PSCAL1'] = 1.0 uvhdu.header['PZERO1'] = 0.0 uvhdu.header['PSCAL2'] = 1.0 uvhdu.header['PZERO2'] = 0.0 uvhdu.header['PSCAL3'] = 1.0 uvhdu.header['PZERO3'] = 0.0 uvhdu.header['PSCAL4'] = 1.0 uvhdu.header['PZERO4'] = 0.0 uvhdu.header['PSCAL5'] = 1.0 uvhdu.header['PZERO5'] = float(int_jd) uvhdu.header['OBJECT'] = 'Undefined' uvhdu.header['OBSRA'] = ra_point uvhdu.header['OBSDEC'] = dec_point ##ANTENNA TABLE MODS====================================================================== template_uvfits[1].header['FREQ'] = freq_cent template_uvfits[1].header['ARRNAM'] = 'MWA' ##MAJICK uses this date to set the LST dmy, hms = date.split() day,month,year = map(int,dmy.split('-')) hour,mins,secs = map(float,hms.split(':')) rdate = "%d-%02d-%02dT%02d:%02d:%.2f" %(year,month,day,hour,mins,secs) template_uvfits[1].header['RDATE'] = rdate ## Create hdulist and write out file hdulist = fits.HDUList(hdus=[uvhdu,template_uvfits[1]]) hdulist.writeto(output_uvfits_name,overwrite=True) hdulist.close()