def test_phasing_funcs(): # these tests are based on a notebook where I tested against the mwa_tools phasing code ra_hrs = 12.1 dec_degs = -42.3 mjd = 55780.1 array_center_xyz = np.array([-2559454.08, 5095372.14, -2849057.18]) lat_lon_alt = uvutils.LatLonAlt_from_XYZ(array_center_xyz) obs_time = Time(mjd, format='mjd', location=(lat_lon_alt[1], lat_lon_alt[0])) icrs_coord = SkyCoord(ra=Angle(ra_hrs, unit='hr'), dec=Angle(dec_degs, unit='deg'), obstime=obs_time) gcrs_coord = icrs_coord.transform_to('gcrs') # in east/north/up frame (relative to array center) in meters: (Nants, 3) ants_enu = np.array([-101.94, 0156.41, 0001.24]) ant_xyz_abs = uvutils.ECEF_from_ENU(ants_enu, lat_lon_alt[0], lat_lon_alt[1], lat_lon_alt[2]) ant_xyz_rel_itrs = ant_xyz_abs - array_center_xyz ant_xyz_rel_rot = uvutils.rotECEF_from_ECEF(ant_xyz_rel_itrs, lat_lon_alt[1]) array_center_coord = SkyCoord(x=array_center_xyz[0] * units.m, y=array_center_xyz[1] * units.m, z=array_center_xyz[2] * units.m, frame='itrs', obstime=obs_time) itrs_coord = SkyCoord(x=ant_xyz_abs[0] * units.m, y=ant_xyz_abs[1] * units.m, z=ant_xyz_abs[2] * units.m, frame='itrs', obstime=obs_time) gcrs_array_center = array_center_coord.transform_to('gcrs') gcrs_from_itrs_coord = itrs_coord.transform_to('gcrs') gcrs_rel = (gcrs_from_itrs_coord.cartesian - gcrs_array_center.cartesian).get_xyz().T gcrs_uvw = uvutils.phase_uvw(gcrs_coord.ra.rad, gcrs_coord.dec.rad, gcrs_rel.value) mwa_tools_calcuvw_u = -97.122828 mwa_tools_calcuvw_v = 50.388281 mwa_tools_calcuvw_w = -151.27976 assert np.allclose(gcrs_uvw[0, 0], mwa_tools_calcuvw_u, atol=1e-3) assert np.allclose(gcrs_uvw[0, 1], mwa_tools_calcuvw_v, atol=1e-3) assert np.allclose(gcrs_uvw[0, 2], mwa_tools_calcuvw_w, atol=1e-3) # also test unphasing temp2 = uvutils.unphase_uvw(gcrs_coord.ra.rad, gcrs_coord.dec.rad, np.squeeze(gcrs_uvw)) assert np.allclose(gcrs_rel.value, temp2)
def test_mwa_ecef_conversion(): ''' Test based on comparing the antenna locations in a Cotter uvfits file to the antenna locations in MWA_tools. ''' test_data_file = os.path.join(DATA_PATH, 'mwa128_ant_layouts.npz') f = np.load(test_data_file) # From the STABXYZ table in a cotter-generated uvfits file, obsid = 1066666832 xyz = f['stabxyz'] # From the East/North/Height columns in a cotter-generated metafits file, obsid = 1066666832 enh = f['ENH'] # From a text file antenna_locations.txt in MWA_Tools/scripts txt_topo = f['txt_topo'] # From the unphased uvw coordinates of obsid 1066666832, positions relative to antenna 0 # these aren't used in the current test, but are interesting and might help with phasing diagnosis in the future uvw_topo = f['uvw_topo'] # Sky coordinates are flipped for uvw derived values uvw_topo = -uvw_topo uvw_topo += txt_topo[0] # transpose these arrays to get them into the right shape txt_topo = txt_topo.T uvw_topo = uvw_topo.T enh = enh.T # ARRAYX, ARRAYY, ARRAYZ in ECEF frame from Cotter file arrcent = f['arrcent'] lat, lon, alt = uvutils.LatLonAlt_from_XYZ(arrcent) # The STABXYZ coordinates are defined with X through the local meridian, # so rotate back to the prime meridian new_xyz = uvutils.ECEF_from_rotECEF(xyz.T, lon) # add in array center to get real ECEF ecef_xyz = new_xyz + arrcent enu = uvutils.ENU_from_ECEF(ecef_xyz.T, lat, lon, alt) nt.assert_true(np.allclose(enu, enh)) # test other direction of ECEF rotation rot_xyz = uvutils.rotECEF_from_ECEF(new_xyz, lon) nt.assert_true(np.allclose(rot_xyz.T, xyz))
station_types=geo.station_types) fnd = fnd_list[0] lat = fnd.lat lon = fnd.lon alt = fnd.elevation ant_nums.append(ant_number) longitudes.append(lon) latitudes.append(lat) elevations.append(alt) # convert to ecef (in meters) ecef_positions = uvutils.XYZ_from_LatLonAlt( np.array(latitudes) * np.pi / 180., np.array(longitudes) * np.pi / 180., elevations) rotecef_positions = uvutils.rotECEF_from_ECEF(ecef_positions.T, cofa_loc.lon * np.pi / 180.) # make an array of antenna positions in the form miriad is expecting c_ns = const.c.to('m/ns').value nants = max(ant_nums) + 1 antpos = np.zeros([nants, 3]) for i, ant in enumerate(ant_nums): antpos[ant, :] = rotecef_positions[i, :] / c_ns # make an aa object freqs = np.array([0.15]) beam = aipy.phs.Beam(freqs) ants = [aipy.phs.Antenna(a[0], a[1], a[2], beam) for a in antpos] aa = aipy.phs.AntennaArray(ants=ants, location=location) # loop over miriad files
def test_miriad_location_handling(): uv_in = UVData() uv_out = UVData() miriad_file = os.path.join(DATA_PATH, 'zen.2456865.60537.xy.uvcRREAA') testdir = os.path.join(DATA_PATH, 'test/') testfile = os.path.join(DATA_PATH, 'test/outtest_miriad.uv') aipy_uv = aipy.miriad.UV(miriad_file) if os.path.exists(testfile): shutil.rmtree(testfile) # Test for using antenna positions to get telescope position uvtest.checkWarnings( uv_in.read_miriad, [miriad_file], message=[ 'Altitude is not present in Miriad file, using known ' 'location values for PAPER.' ]) # extract antenna positions and rotate them for miriad nants = aipy_uv['nants'] rel_ecef_antpos = np.zeros((nants, 3), dtype=uv_in.antenna_positions.dtype) for ai, num in enumerate(uv_in.antenna_numbers): rel_ecef_antpos[num, :] = uv_in.antenna_positions[ai, :] # find zeros so antpos can be zeroed there too antpos_length = np.sqrt(np.sum(np.abs(rel_ecef_antpos)**2, axis=1)) ecef_antpos = rel_ecef_antpos + uv_in.telescope_location longitude = uv_in.telescope_location_lat_lon_alt[1] antpos = uvutils.rotECEF_from_ECEF(ecef_antpos, longitude) # zero out bad locations (these are checked on read) antpos[np.where(antpos_length == 0), :] = [0, 0, 0] antpos = antpos.T.flatten() / const.c.to('m/ns').value # make new file aipy_uv2 = aipy.miriad.UV(testfile, status='new') # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use absolute antenna positions aipy_uv2.init_from_uv(aipy_uv, override={ 'telescop': 'foo', 'antpos': antpos }) # copy data from old file aipy_uv2.pipe(aipy_uv) # close file properly del (aipy_uv2) uvtest.checkWarnings( uv_out.read_miriad, [testfile], nwarnings=3, message=[ 'Altitude is not present in Miriad file, and ' 'telescope foo is not in known_telescopes. ' 'Telescope location will be set using antenna positions.', 'Telescope location is not set, but antenna ' 'positions are present. Mean antenna latitude ' 'and longitude values match file values, so ' 'telescope_position will be set using the mean ' 'of the antenna altitudes', 'Telescope foo is not in known_telescopes.' ]) # Test for handling when antenna positions have a different mean latitude than the file latitude # make new file if os.path.exists(testfile): shutil.rmtree(testfile) aipy_uv = aipy.miriad.UV(miriad_file) aipy_uv2 = aipy.miriad.UV(testfile, status='new') # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use absolute antenna positions, change file latitude new_lat = aipy_uv['latitud'] * 1.5 aipy_uv2.init_from_uv(aipy_uv, override={ 'telescop': 'foo', 'antpos': antpos, 'latitud': new_lat }) # copy data from old file aipy_uv2.pipe(aipy_uv) # close file properly del (aipy_uv2) uvtest.checkWarnings( uv_out.read_miriad, [testfile], nwarnings=3, message=[ 'Altitude is not present in Miriad file, and ' 'telescope foo is not in known_telescopes. ' 'Telescope location will be set using antenna positions.', 'Telescope location is set at sealevel at the ' 'file lat/lon coordinates. Antenna positions ' 'are present, but the mean antenna latitude ' 'value does not match', 'Telescope foo is not in known_telescopes.' ]) # Test for handling when antenna positions have a different mean longitude than the file longitude # this is harder because of the rotation that's done on the antenna positions # make new file if os.path.exists(testfile): shutil.rmtree(testfile) aipy_uv = aipy.miriad.UV(miriad_file) aipy_uv2 = aipy.miriad.UV(testfile, status='new') # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use absolute antenna positions, change file longitude new_lon = aipy_uv['longitu'] + np.pi aipy_uv2.init_from_uv(aipy_uv, override={ 'telescop': 'foo', 'antpos': antpos, 'longitu': new_lon }) # copy data from old file aipy_uv2.pipe(aipy_uv) # close file properly del (aipy_uv2) uvtest.checkWarnings( uv_out.read_miriad, [testfile], nwarnings=3, message=[ 'Altitude is not present in Miriad file, and ' 'telescope foo is not in known_telescopes. ' 'Telescope location will be set using antenna positions.', 'Telescope location is set at sealevel at the ' 'file lat/lon coordinates. Antenna positions ' 'are present, but the mean antenna longitude ' 'value does not match', 'Telescope foo is not in known_telescopes.' ]) # Test for handling when antenna positions have a different mean longitude & # latitude than the file longitude # make new file if os.path.exists(testfile): shutil.rmtree(testfile) aipy_uv = aipy.miriad.UV(miriad_file) aipy_uv2 = aipy.miriad.UV(testfile, status='new') # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use absolute antenna positions, change file latitude and longitude aipy_uv2.init_from_uv(aipy_uv, override={ 'telescop': 'foo', 'antpos': antpos, 'latitud': new_lat, 'longitu': new_lon }) # copy data from old file aipy_uv2.pipe(aipy_uv) # close file properly del (aipy_uv2) uvtest.checkWarnings( uv_out.read_miriad, [testfile], nwarnings=3, message=[ 'Altitude is not present in Miriad file, and ' 'telescope foo is not in known_telescopes. ' 'Telescope location will be set using antenna positions.', 'Telescope location is set at sealevel at the ' 'file lat/lon coordinates. Antenna positions ' 'are present, but the mean antenna latitude and ' 'longitude values do not match', 'Telescope foo is not in known_telescopes.' ]) # Test for handling when antenna positions are far enough apart to make the # mean position inside the earth good_antpos = np.where(antpos_length > 0)[0] rot_ants = good_antpos[:len(good_antpos) / 2] rot_antpos = uvutils.rotECEF_from_ECEF(ecef_antpos[rot_ants, :], longitude + np.pi) modified_antpos = uvutils.rotECEF_from_ECEF(ecef_antpos, longitude) modified_antpos[rot_ants, :] = rot_antpos # zero out bad locations (these are checked on read) modified_antpos[np.where(antpos_length == 0), :] = [0, 0, 0] modified_antpos = modified_antpos.T.flatten() / const.c.to('m/ns').value # make new file if os.path.exists(testfile): shutil.rmtree(testfile) aipy_uv = aipy.miriad.UV(miriad_file) aipy_uv2 = aipy.miriad.UV(testfile, status='new') # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use modified absolute antenna positions aipy_uv2.init_from_uv(aipy_uv, override={ 'telescop': 'foo', 'antpos': modified_antpos }) # copy data from old file aipy_uv2.pipe(aipy_uv) # close file properly del (aipy_uv2) uvtest.checkWarnings( uv_out.read_miriad, [testfile], nwarnings=3, message=[ 'Altitude is not present in Miriad file, and ' 'telescope foo is not in known_telescopes. ' 'Telescope location will be set using antenna positions.', 'Telescope location is set at sealevel at the ' 'file lat/lon coordinates. Antenna positions ' 'are present, but the mean antenna position ' 'does not give a telescope_location on the ' 'surface of the earth.', 'Telescope foo is not in known_telescopes.' ])
def test_miriad_location_handling(paper_miriad_master, tmp_path): uv_in = paper_miriad_master uv_out = UVData() testfile = str(tmp_path / "outtest_miriad.uv") aipy_uv = aipy_extracts.UV(paper_miriad_file) if os.path.exists(testfile): shutil.rmtree(testfile) # Test for using antenna positions to get telescope position # extract antenna positions and rotate them for miriad nants = aipy_uv["nants"] rel_ecef_antpos = np.zeros((nants, 3), dtype=uv_in.antenna_positions.dtype) for ai, num in enumerate(uv_in.antenna_numbers): rel_ecef_antpos[num, :] = uv_in.antenna_positions[ai, :] # find zeros so antpos can be zeroed there too antpos_length = np.sqrt(np.sum(np.abs(rel_ecef_antpos) ** 2, axis=1)) ecef_antpos = rel_ecef_antpos + uv_in.telescope_location longitude = uv_in.telescope_location_lat_lon_alt[1] antpos = uvutils.rotECEF_from_ECEF(ecef_antpos, longitude) # zero out bad locations (these are checked on read) antpos[np.where(antpos_length == 0), :] = [0, 0, 0] antpos = antpos.T.flatten() / const.c.to("m/ns").value # make new file aipy_uv2 = aipy_extracts.UV(testfile, status="new") # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use absolute antenna positions aipy_uv2.init_from_uv(aipy_uv, override={"telescop": "foo", "antpos": antpos}) # copy data from old file aipy_uv2.pipe(aipy_uv) aipy_uv2.close() with uvtest.check_warnings( UserWarning, [ "Altitude is not present in Miriad file, and " "telescope foo is not in known_telescopes. " "Telescope location will be set using antenna positions.", "Altitude is not present ", "Telescope location is not set, but antenna " "positions are present. Mean antenna latitude " "and longitude values match file values, so " "telescope_position will be set using the mean " "of the antenna altitudes", "Telescope foo is not in known_telescopes.", "The uvw_array does not match the expected values given the antenna " "positions.", ], ): uv_out.read(testfile) # Test for handling when antenna positions have a different mean latitude # than the file latitude # make new file if os.path.exists(testfile): shutil.rmtree(testfile) aipy_uv = aipy_extracts.UV(paper_miriad_file) aipy_uv2 = aipy_extracts.UV(testfile, status="new") # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use absolute antenna positions, change file latitude new_lat = aipy_uv["latitud"] * 1.5 aipy_uv2.init_from_uv( aipy_uv, override={"telescop": "foo", "antpos": antpos, "latitud": new_lat} ) # copy data from old file aipy_uv2.pipe(aipy_uv) aipy_uv2.close() with uvtest.check_warnings( UserWarning, [ "Altitude is not present in Miriad file, and " "telescope foo is not in known_telescopes. " "Telescope location will be set using antenna positions.", "Altitude is not present in Miriad file, and " "telescope foo is not in known_telescopes. " "Telescope location will be set using antenna positions.", "Telescope location is set at sealevel at the " "file lat/lon coordinates. Antenna positions " "are present, but the mean antenna latitude " "value does not match", "drift RA, Dec is off from lst, latitude by more than 1.0 deg", "Telescope foo is not in known_telescopes.", "The uvw_array does not match the expected values given the antenna " "positions.", ], ): uv_out.read(testfile) # Test for handling when antenna positions have a different mean longitude # than the file longitude # this is harder because of the rotation that's done on the antenna positions # make new file if os.path.exists(testfile): shutil.rmtree(testfile) aipy_uv = aipy_extracts.UV(paper_miriad_file) aipy_uv2 = aipy_extracts.UV(testfile, status="new") # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use absolute antenna positions, change file longitude new_lon = aipy_uv["longitu"] + np.pi aipy_uv2.init_from_uv( aipy_uv, override={"telescop": "foo", "antpos": antpos, "longitu": new_lon} ) # copy data from old file aipy_uv2.pipe(aipy_uv) aipy_uv2.close() with uvtest.check_warnings( UserWarning, [ "Altitude is not present in Miriad file, and " "telescope foo is not in known_telescopes. " "Telescope location will be set using antenna positions.", "Altitude is not present in Miriad file, and " "telescope foo is not in known_telescopes. " "Telescope location will be set using antenna positions.", "Telescope location is set at sealevel at the " "file lat/lon coordinates. Antenna positions " "are present, but the mean antenna longitude " "value does not match", "drift RA, Dec is off from lst, latitude by more than 1.0 deg", "Telescope foo is not in known_telescopes.", "The uvw_array does not match the expected values given the antenna " "positions.", ], ): uv_out.read(testfile) # Test for handling when antenna positions have a different mean longitude & # latitude than the file longitude # make new file if os.path.exists(testfile): shutil.rmtree(testfile) aipy_uv = aipy_extracts.UV(paper_miriad_file) aipy_uv2 = aipy_extracts.UV(testfile, status="new") # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use absolute antenna positions, change file latitude and longitude aipy_uv2.init_from_uv( aipy_uv, override={ "telescop": "foo", "antpos": antpos, "latitud": new_lat, "longitu": new_lon, }, ) # copy data from old file aipy_uv2.pipe(aipy_uv) aipy_uv2.close() with uvtest.check_warnings( UserWarning, [ "Altitude is not present in Miriad file, and " "telescope foo is not in known_telescopes. " "Telescope location will be set using antenna positions.", "Altitude is not present in Miriad file, and " "telescope foo is not in known_telescopes. " "Telescope location will be set using antenna positions.", "Telescope location is set at sealevel at the " "file lat/lon coordinates. Antenna positions " "are present, but the mean antenna latitude and " "longitude values do not match", "drift RA, Dec is off from lst, latitude by more than 1.0 deg", "Telescope foo is not in known_telescopes.", "The uvw_array does not match the expected values given the antenna " "positions.", ], ): uv_out.read(testfile) # Test for handling when antenna positions are far enough apart to make the # mean position inside the earth good_antpos = np.where(antpos_length > 0)[0] rot_ants = good_antpos[: len(good_antpos) // 2] rot_antpos = uvutils.rotECEF_from_ECEF(ecef_antpos[rot_ants, :], longitude + np.pi) modified_antpos = uvutils.rotECEF_from_ECEF(ecef_antpos, longitude) modified_antpos[rot_ants, :] = rot_antpos # zero out bad locations (these are checked on read) modified_antpos[np.where(antpos_length == 0), :] = [0, 0, 0] modified_antpos = modified_antpos.T.flatten() / const.c.to("m/ns").value # make new file if os.path.exists(testfile): shutil.rmtree(testfile) aipy_uv = aipy_extracts.UV(paper_miriad_file) aipy_uv2 = aipy_extracts.UV(testfile, status="new") # initialize headers from old file # change telescope name (so the position isn't set from known_telescopes) # and use modified absolute antenna positions aipy_uv2.init_from_uv( aipy_uv, override={"telescop": "foo", "antpos": modified_antpos} ) # copy data from old file aipy_uv2.pipe(aipy_uv) aipy_uv2.close() with uvtest.check_warnings( UserWarning, [ "Altitude is not present in Miriad file, and " "telescope foo is not in known_telescopes. " "Telescope location will be set using antenna positions.", "Altitude is not present ", "Telescope location is set at sealevel at the " "file lat/lon coordinates. Antenna positions " "are present, but the mean antenna position " "does not give a telescope_location on the " "surface of the earth.", "Telescope foo is not in known_telescopes.", "The uvw_array does not match the expected values given the antenna " "positions.", ], ): uv_out.read(testfile) # cleanup aipy_uv.close()
def get_aa_from_uv(uvd, freqs=[0.15]): ''' Generate an AntennaArray object from a pyuvdata UVData object. This function creates an AntennaArray object from the metadata contained in a UVData object. It assumes that the antenna_positions array in the UVData object is in earth-centered, earth-fixed (ECEF) coordinates, relative to the center of the array, also given in ECEF coordinates. We must add these together, and then rotate so that the x-axis is aligned with the local meridian (rotECEF). rotECEF is the coordinate system for Antenna objects in the AntennaArray object (which inherits this behavior from MIRIAD). It is also expected that distances are given in nanoseconds, rather than meters, also because of the default behavior in MIRIAD. Arguments ================= uvd: a pyuvdata UVData object containing the data. freqs (optional): list of frequencies to pass to aa object. Defaults to single frequency (150 MHz), suitable for computing redundancy and uvw info. Returns ==================== aa: AntennaArray object that can be used to calculate redundancies from antenna positions. ''' assert AIPY, "you need aipy to run this function" # center of array values from file cofa_lat, cofa_lon, cofa_alt = uvd.telescope_location_lat_lon_alt location = (cofa_lat, cofa_lon, cofa_alt) # get antenna positions from file antpos = {} for i, antnum in enumerate(uvd.antenna_numbers): # we need to add the CofA location to the relative coordinates pos = uvd.antenna_positions[i, :] + uvd.telescope_location # convert from meters to nanoseconds c_ns = const.c.to('m/ns').value pos = pos / c_ns # rotate from ECEF -> rotECEF rotECEF = uvutils.rotECEF_from_ECEF(pos, cofa_lon) # make a dict for parameter-setting purposes later antpos[antnum] = {'x': rotECEF[0], 'y': rotECEF[1], 'z': rotECEF[2]} # make antpos_ideal array nants = np.max(list(antpos.keys())) + 1 antpos_ideal = np.zeros(shape=(nants, 3), dtype=float) - 1 # unpack from dict -> numpy array for k in list(antpos.keys()): antpos_ideal[k, :] = np.array([antpos[k]['x'], antpos[k]['y'], antpos[k]['z']]) freqs = np.asarray(freqs) # Make list of antennas. # These are values for a zenith-pointing antenna, with a dummy Gaussian beam. antennas = [] for i in range(nants): beam = aipy.fit.Beam(freqs) phsoff = {'x': [0., 0.], 'y': [0., 0.]} amp = 1. amp = {'x': amp, 'y': amp} bp_r = [1.] bp_r = {'x': bp_r, 'y': bp_r} bp_i = [0., 0., 0.] bp_i = {'x': bp_i, 'y': bp_i} twist = 0. antennas.append(aipy.pol.Antenna(0., 0., 0., beam, phsoff=phsoff, amp=amp, bp_r=bp_r, bp_i=bp_i, pointing=(0., np.pi / 2, twist))) # Make the AntennaArray and set position parameters aa = AntennaArray(location, antennas, antpos_ideal=antpos_ideal) pos_prms = {} for i in list(antpos.keys()): pos_prms[str(i)] = antpos[i] aa.set_params(pos_prms) return aa