def test_mwa_alt_az_za(): """Test the mwa_alt_az_za function.""" # obsid, alt, az, za tests = [(1117101752, -71.10724927808731, 145.74310748819693, 161.1072492780873), (1252177744, -14.709910184536241, 264.22976419794514, 104.70991018453624), (1247832024, 68.90133642304133, 161.50105995238945, 21.09866357695867), (1188439520, 60.78396503767497, 161.03537536398974, 29.216034962325033)] for obsid, exp_alt, exp_az, exp_za in tests: alt, az, za = mwa_alt_az_za(obsid) assert_almost_equal(alt, exp_alt, decimal=3) assert_almost_equal(az, exp_az, decimal=3) assert_almost_equal(za, exp_za, decimal=3)
def get_obs_metadata(obs): beam_meta_data = getmeta(service='obs', params={'obs_id': obs}) channels = beam_meta_data[u'rfstreams'][u"0"][u'frequencies'] freqs = [float(c) * 1.28 for c in channels] xdelays = beam_meta_data[u'rfstreams'][u"0"][u'xdelays'] #pythodelays = beam_meta_data[u'rfstreams'][u"0"][u'xdelays'] ydelays = beam_meta_data[u'rfstreams'][u"0"][u'ydelays'] _, pointing_AZ, pointing_ZA = mwa_alt_az_za(obs) return { "channels": channels, "frequencies": freqs, "xdelays": xdelays, "ydelays": ydelays, "az": pointing_AZ, "za": pointing_ZA }
channels = metadata[-1] nchans = len(channels) ch_offset = channels[-1] - channels[0] if ch_offset != nchans - 1: logger.error("Picket fence observation - cannot combine picket fence incoherent sum data (with this script)") sys.exit(1) bw = nchans * 1.28 cfreq = (1.28 * min(channels) - 0.64) + bw / 2.0 logger.info("Centre frequency: {0} MHz".format(cfreq)) logger.info("Bandwidth: {0} MHz".format(bw)) logger.info("Acquiring pointing position information") ra, dec = metadata[1:3] alt, az, za = mwa_alt_az_za(args.obsID, ra=ra, dec=dec) user = getpass.getuser() os.system("mkdir -p {ics_dir}".format(ics_dir=ics_dir)) # TODO: this is bloody awful, surely there's a better way to do this? make_command = 'cd {ics_dir} && echo -e "{ics_dir}/mk_psrfits_in\n' \ '\n' \ '{obsid}\n' \ '\n' \ '{nfiles}\n' \ '{user}\n' \ '\n' \ '{obsid}\n' \ '\n' \ '\n' \
def plot_beam_pattern(obsid, obsfreq, obstime, ra, dec, cutoff=0.1): # extra imports from MWA_Tools to access database and beam models from mwa_pb import primary_beam as pb _az = np.linspace(0, 360, 3600) _za = np.linspace(0, 90, 900) az, za = np.meshgrid(_az, _za) ## TARGET TRACKING ## times = Time(obstime, format='gps') target = SkyCoord(ra, dec, unit=(u.hourangle, u.deg)) location = EarthLocation(lat=-26.7033 * u.deg, lon=116.671 * u.deg, height=377.827 * u.m) altaz = target.transform_to(AltAz(obstime=times, location=location)) targetAZ = altaz.az.deg targetZA = 90 - altaz.alt.deg colours = cm.viridis(np.linspace(1, 0, len(targetAZ))) ## MWA BEAM CALCULATIONS ## delays = get_common_obs_metadata(obsid)[ 4] # x-pol and y-pol delays for obsid _, ptAZ, ptZA = mwa_alt_az_za(obsid) #ptAZ, _, ptZA = dbq.get_beam_pointing(obsid) # Az and ZA in degrees for obsid logger.info("obs pointing: ({0}, {1})".format(ptAZ, ptZA)) xp, yp = pb.MWA_Tile_analytic(np.radians(za), np.radians(az), obsfreq * 1e6, delays=delays, zenithnorm=True) #interp=True) pattern = np.sqrt((xp + yp) / 2.0).real # sum to get "total intensity" logger.debug("Pattern: {0}".format(pattern)) pmax = pattern.max() hpp = 0.5 * pmax # half-power point logger.info("tile pattern maximum: {0:.3f}".format(pmax)) logger.info("tile pattern half-max: {0:.3f}".format(hpp)) pattern[np.where(pattern < cutoff)] = 0 # ignore everything below cutoff # figure out the fwhm fwhm_idx = np.where((pattern > 0.498 * pmax) & (pattern < 0.502 * pmax)) fwhm_az_idx = fwhm_idx[1] fwhm_za_idx = fwhm_idx[0] # collapse beam pattern along axes pattern_ZAcol = pattern.mean(axis=0) pattern_AZcol = pattern.mean(axis=1) # figure out beam pattern value at target tracking points track_lines = [] logger.info("beam power at target track points:") for ta, tz in zip(targetAZ, targetZA): xp, yp = pb.MWA_Tile_analytic(np.radians([[tz]]), np.radians([[ta]]), obsfreq * 1e6, delays=delays, zenithnorm=True) #interp=False bp = (xp + yp) / 2 track_lines.append(bp[0]) logger.info("({0:.2f},{1:.2f}) = {2:.3f}".format(ta, tz, bp[0][0])) ## PLOTTING ## fig = plt.figure(figsize=(10, 8)) gs = gridspec.GridSpec(4, 4) axP = plt.subplot(gs[1:, 0:3]) axAZ = plt.subplot(gs[0, :3]) axZA = plt.subplot(gs[1:, 3]) axtxt = plt.subplot(gs[0, 3]) # info text in right-hand corner axis axtxt.axis('off') infostr = """Obs ID: {0} Frequency: {1:.2f}MHz Beam Pmax: {2:.3f} Beam half-Pmax: {3:.3f} """.format(obsid, obsfreq, pmax, hpp) axtxt.text(0.01, 0.5, infostr, verticalalignment='center') logger.debug("az: {0}, za: {1}, pattern: {2}, pmax: {3}".format( az, za, pattern, pmax)) # plot the actual beam patter over sky p = axP.contourf(az, za, pattern, 100, cmap=plt.get_cmap('gist_yarg'), vmax=pmax) # plot beam contours axP.scatter(_az[fwhm_az_idx], _za[fwhm_za_idx], marker='.', s=1, color='r') # plot the fwhm border axP.plot(ptAZ, ptZA, marker="+", ms=8, ls="", color='C0') # plot the tile beam pointing for ta, tz, c in zip(targetAZ, targetZA, colours): axP.plot(ta, tz, marker="x", ms=8, ls="", color=c) # plot the target track through the beam axP.set_xlim(0, 360) axP.set_ylim(0, 90) axP.set_xticks(np.arange(0, 361, 60)) axP.set_xlabel("Azimuth (deg)") axP.set_ylabel("Zenith angle (deg)") # setup and configure colourbar cbar_ax = fig.add_axes([0.122, -0.01, 0.58, 0.03]) cbar = plt.colorbar(p, cax=cbar_ax, orientation='horizontal', label="Zenith normalised power") cbar.ax.plot(hpp / pmax, [0.5], 'r.') for l, c in zip(track_lines, colours): cbar.ax.plot([l / pmax, l / pmax], [0, 1], color=c, lw=1.5) # plot collapsed beam patterns: axAZ.plot(_az, pattern_ZAcol, color='k') # collapsed along ZA for ta, c in zip(targetAZ, colours): # draw tracking points axAZ.axvline(ta, color=c) axAZ.set_xlim(0, 360) axAZ.set_xticks(np.arange(0, 361, 60)) axAZ.set_xticklabels([]) axAZ.set_yscale('log') axZA.plot(pattern_AZcol, _za, color='k') # collapsed along AZ for tz, c in zip(targetZA, colours): # draw tracking points axZA.axhline(tz, color=c) axZA.set_ylim(0, 90) axZA.set_yticklabels([]) axZA.set_xscale('log') plt.savefig("{0}_{1:.2f}MHz_flattile.png".format(obsid, obsfreq), bbox_inches='tight')
def get_beam_power_over_time(names_ra_dec, common_metadata=None, dt=296, centeronly=True, verbose=False, option='analytic', degrees=False, start_time=0): """Calculates the zenith normalised power for each source over time. Parameters ---------- names_ra_dec : `list` An array in the format [[source_name, RAJ, DecJ]] common_metadata : `list`, optional The list of common metadata generated from :py:meth:`vcstools.metadb_utils.get_common_obs_metadata` dt : `int`, optional The time interval of how often powers are calculated. |br| Default: 296. centeronly : `boolean`, optional Only calculates for the centre frequency. |br| Default: `True`. verbose : `boolean`, optional If `True` will not supress the output from mwa_pb. |br| Default: `False`. option : `str`, optional The primary beam model to use out of [analytic, advanced, full_EE, hyperbeam]. |br| Default: analytic. degrees : `boolean`, optional If true assumes RAJ and DecJ are in degrees. |br| Default: `False`. start_time : `int`, optional The time in seconds from the begining of the observation to start calculating at. |br| Default: 0. Returns ------- Powers : `numpy.array`, (len(names_ra_dec), ntimes, nfreqs) The zenith normalised power for each source over time. """ if common_metadata is None: common_metadata = get_common_obs_metadata(obsid) obsid, _, _, time, delays, centrefreq, channels = common_metadata names_ra_dec = np.array(names_ra_dec) amps = [1.0] * 16 logger.debug("Calculating beam power for OBS ID: {0}".format(obsid)) if option == 'hyperbeam': if "mwa_hyperbeam" not in sys.modules: logger.error( "mwa_hyperbeam not installed so can not use hyperbeam to create a beam model. Exiting" ) sys.exit(1) beam = mwa_hyperbeam.FEEBeam(config.h5file) # Work out time steps to calculate over starttimes = np.arange(start_time, time + start_time, dt) stoptimes = starttimes + dt stoptimes[stoptimes > time] = time ntimes = len(starttimes) midtimes = float(obsid) + 0.5 * (starttimes + stoptimes) # Work out frequency steps if centeronly: if centrefreq > 1e6: logger.warning( "centrefreq is greater than 1e6, assuming input with units of Hz." ) frequencies = np.array([centrefreq]) else: frequencies = np.array([centrefreq]) * 1e6 nfreqs = 1 else: # in Hz frequencies = np.array(channels) * 1.28e6 nfreqs = len(channels) # Set up np power array PowersX = np.zeros((len(names_ra_dec), ntimes, nfreqs)) PowersY = np.zeros((len(names_ra_dec), ntimes, nfreqs)) # Convert RA and Dec to desired units if degrees: RAs = np.array(names_ra_dec[:, 1], dtype=float) Decs = np.array(names_ra_dec[:, 2], dtype=float) else: RAs, Decs = sex2deg(names_ra_dec[:, 1], names_ra_dec[:, 2]) # Then check if they're valid if len(RAs) == 0: sys.stderr.write('Must supply >=1 source positions\n') return None if not len(RAs) == len(Decs): sys.stderr.write('Must supply equal numbers of RAs and Decs\n') return None if verbose is False: #Supress print statements of the primary beam model functions sys.stdout = open(os.devnull, 'w') for itime in range(ntimes): # this differ's from the previous ephem_utils method by 0.1 degrees _, Azs, Zas = mwa_alt_az_za(midtimes[itime], ra=RAs, dec=Decs, degrees=True) # go from altitude to zenith angle theta = np.radians(Zas) phi = np.radians(Azs) for ifreq in range(nfreqs): #Decide on beam model if option == 'analytic': rX, rY = primary_beam.MWA_Tile_analytic( theta, phi, freq=frequencies[ifreq], delays=delays, zenithnorm=True, power=True) elif option == 'advanced': rX, rY = primary_beam.MWA_Tile_advanced( theta, phi, freq=frequencies[ifreq], delays=delays, zenithnorm=True, power=True) elif option == 'full_EE': rX, rY = primary_beam.MWA_Tile_full_EE(theta, phi, freq=frequencies[ifreq], delays=delays, zenithnorm=True, power=True) elif option == 'hyperbeam': jones = beam.calc_jones_array(phi, theta, int(frequencies[ifreq]), delays[0], amps, True) jones = jones.reshape(1, len(phi), 2, 2) vis = primary_beam.mwa_tile.makeUnpolInstrumentalResponse( jones, jones) rX, rY = (vis[:, :, 0, 0].real, vis[:, :, 1, 1].real) PowersX[:, itime, ifreq] = rX PowersY[:, itime, ifreq] = rY if verbose is False: sys.stdout = sys.__stdout__ Powers = 0.5 * (PowersX + PowersY) return Powers
def find_t_sys_gain(pulsar, obsid, p_ra=None, p_dec=None, dect_beg=None, dect_end=None, obs_beg=None, obs_end=None, common_metadata=None, full_metadata=None, query=None, min_z_power=0.3, trcvr=data_load.TRCVR_FILE): """Finds the system temperature and gain for an observation. Parameters ---------- pulsar : `str` The Jname of the pulsar. obsid : `int` The MWA Observation ID. p_ra, p_dec : `str`, optional The target's right ascension and declination in sexidecimals. If not supplied will use the values from the ANTF. dect_beg, dect_end : `int`, optional The beg and end GPS time of the detection to calculate over. If not supplied will estimate beam enter and exit. obs_beg, obs_end : `int`, optional Beginning and end GPS time of the observation. If not supplied will use :py:meth:`vcstools.metadb_utils.obs_max_min` to find it. common_metadata : `list`, optional The list of common metadata generated from :py:meth:`vcstools.metadb_utils.get_common_obs_metadata`. full_metadata : `dict`, optional The dictionary of metadata generated from :py:meth:`vcstools.metadb_utils.getmeta`. query : psrqpy object, optional A previous psrqpy.QueryATNF query. Can be supplied to prevent performing a new query. min_z_power : `float`, optional Zenith normalised power cut off. |br| Default: 0.3. trcvr : `str` The location of the MWA receiver temp csv file. |br| Default: <vcstools_data_dir>MWA_Trcvr_tile_56.csv Returns ------- t_sys : `float` The system temperature in K. t_sys_err : `float` The system temperature's uncertainty. gain : `float` The system gain in K/Jy. gain_err : `float` The gain's uncertainty. """ # get ra and dec if not supplied if query is None: logger.debug("Obtaining pulsar RA and Dec from ATNF") query = psrqpy.QueryATNF(psrs=[pulsar], loadfromdb=data_load.ATNF_LOC).pandas query_id = list(query['PSRJ']).index(pulsar) if not p_ra or not p_dec: p_ra = query["RAJ"][query_id] p_dec= query["DECJ"][query_id] # get metadata if not supplied if not common_metadata: logger.debug("Obtaining obs metadata") common_metadata = get_common_obs_metadata(obsid) obsid, obs_ra, obs_dec, _, delays, centrefreq, channels = common_metadata if not dect_beg or not dect_end: # Estimate integration time from when the source enters and exits the beam dect_beg, dect_end = source_beam_coverage_and_times(obsid, pulsar, p_ra=p_ra, p_dec=p_dec, obs_beg=obs_beg, obs_end=obs_end, min_z_power=min_z_power, common_metadata=common_metadata, query=query)[:2] start_time = dect_end - int(obsid) t_int = dect_end - dect_beg + 1 #Get important info ntiles = 128 #TODO actually we excluded some tiles during beamforming, so we'll need to account for that here beam_power = get_beam_power_over_time(np.array([[pulsar, p_ra, p_dec]]), common_metadata=[obsid, obs_ra, obs_dec, t_int, delays, centrefreq, channels], dt=100, start_time=start_time) mean_beam_power = np.mean(beam_power) # Usa a primary beam function to convolve the sky temperature with the primary beam # prints suppressed sys.stdout = open(os.devnull, 'w') _, _, Tsky_XX, _, _, _, Tsky_YY, _ = pbtant.make_primarybeammap(int(obsid), delays, centrefreq*1e6, 'analytic', plottype='None') sys.stdout = sys.__stdout__ #TODO can be inaccurate for coherent but is too difficult to simulate t_sky = (Tsky_XX + Tsky_YY) / 2. # Get T_sys by adding Trec and Tsky (other temperatures are assumed to be negligible t_sys_table = t_sky + get_Trec(centrefreq, trcvr_file=trcvr) t_sys = np.mean(t_sys_table) t_sys_err = t_sys*0.02 #TODO: figure out what t_sys error is logger.debug("pul_ra: {} pul_dec: {}".format(p_ra, p_dec)) _, _, zas = mwa_alt_az_za(obsid, ra=p_ra, dec=p_dec) theta = np.radians(zas) gain = from_power_to_gain(mean_beam_power, centrefreq*1e6, ntiles, coh=True) logger.debug("mean_beam_power: {} theta: {} pi: {}".format(mean_beam_power, theta, np.pi)) gain_err = gain * ((1. - mean_beam_power)*0.12 + 2.*(theta/(0.5*np.pi))**2. + 0.1) return t_sys, t_sys_err, gain, gain_err
def plot_beam(obs, target, cal, freq): metadata = get_common_obs_metadata(obs) phi = np.linspace(0, 360, 3600) theta = np.linspace(0, 90, 900) # make coordinate grid az, za = np.meshgrid(np.radians(phi), np.radians(theta)) # compute beam and plot delays = metadata[4] #x and y delays logger.debug("delays: {0}".format(delays)) logger.debug("freq*1e6: {0}".format(freq * 1e6)) logger.debug("za: {0}".format(za)) logger.debug("az: {0}".format(az)) gx, gy = pb.MWA_Tile_analytic(za, az, freq=int(freq * 1e6), delays=delays, power=True, zenithnorm=True) beam = (gx + gy) / 2.0 fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(111, polar=True, aspect='auto') # filled contour setup lower_contour = 7e-3 upper_contour = beam.max() fill_min = 1e-2 # 1% of zenith power fill_max = 0.95 * beam.max() # 95% max beam power ( != zenith power) Z = np.copy(beam) Z[Z <= fill_min] = 0 Z[Z >= fill_max] = fill_max cc_levels = np.logspace(np.log10(lower_contour), np.log10(upper_contour), num=20) cc_cmap = plt.get_cmap('gray_r') cc_norm = LogNorm(vmin=cc_levels.min(), vmax=beam.max()) cc = ax.contourf(az, za, Z, levels=cc_levels, cmap=cc_cmap, norm=cc_norm, zorder=1000) cc.cmap.set_over('black') # anything over max is black cc.cmap.set_under('white') # anything under min is white # contour lines steup cs_levels = np.logspace(np.log10(fill_min), np.log10(fill_max), num=5) cs_cmap = plt.get_cmap('gist_heat') cs_norm = LogNorm(vmin=cs_levels.min(), vmax=cs_levels.max()) cs = ax.contour(az, za, beam, levels=cs_levels, cmap=cs_cmap, norm=cs_norm, zorder=1001) # color bar setup cbarCC = plt.colorbar(cc, pad=0.08, shrink=0.9) cbarCC.set_label(label="zenith normalised power", size=20) cbarCC.set_ticks(cs_levels) cbarCC.set_ticklabels([r"${0:.2f}$".format(x) for x in cs_levels]) cbarCC.ax.tick_params(labelsize=18) #add lines from the contours to the filled color map cbarCC.add_lines(cs) # plot the pointing centre of the tile beam _, pointing_AZ, pointing_ZA = mwa_alt_az_za(obs) ax.plot(np.radians(pointing_AZ), np.radians(pointing_ZA), ls="", marker="+", ms=8, color='C3', zorder=1002, label="pointing centre") if target is not None: # color map for tracking target positions target_colors = cm.viridis(np.linspace(0, 1, len(target.obstime.gps))) logger.info("obs time length: {0} seconds".format( len(target.obstime.gps))) for t, color in zip(target, target_colors): target_az = t.altaz.az.rad target_za = np.pi / 2 - t.altaz.alt.rad # get beam power for target bpt_x, bpt_y = pb.MWA_Tile_analytic( target_za, target_az, freq=freq * 1e6, delays=[metadata[4][0], metadata[4][1]], power=True, zenithnorm=True) bpt = (bpt_x + bpt_y) / 2.0 lognormbpt = log_normalise(bpt, cc_levels.min(), beam.max()) logger.debug("bpt, cc_levels, beam: {0}, {1}, {2}".format( bpt, cc_levels.min(), beam.max())) logger.info("Beam power @ source @ gps: {0}: {1}".format( t.obstime.gps, bpt)) logger.info("log-normalised BP: {0}".format(lognormbpt)) # plot the target position on sky ax.plot(target_az, target_za, ls="", marker="o", color=color, zorder=1002, label="target @ {0} ({1})".format(t.obstime.gps, bpt)) # plot the target on the color bar cbarCC.ax.plot(0.5, lognormbpt, color=color, marker="o") if cal is not None: # color map for tracking calibrator position calibrator_colors = cm.viridis(np.linspace(0, 1, len(cal.obstime.gps))) for c, color in zip(cal, calibrator_colors): cal_az = c.altaz.az.rad cal_za = np.pi / 2 - c.altaz.alt.rad # get the beam power for calibrator bpc_x, bpc_y = pb.MWA_Tile_analytic([[cal_za]], [[cal_az]], freq=freq * 1e6, delays=metadata[4], power=True, zenithnorm=True) bpc = (bpc_x + bpc_y) / 2.0 lognormbpc = log_normalise(bpc, cc_levels.min(), beam.max()) logger.info("Beam power @ calibrator @ gps:{0}: {1:.3f}".format( c.obstime.gps, bpc[0][0])) logger.info("log-normalised BP: {0:.3f}".format(lognormbpc[0][0])) # plot the calibrator position on sky ax.plot(cal_az, cal_za, ls="", marker="^", color=color, zorder=1002, label="cal @ {0} ({1:.2f})".format(c.obstime.gps, bpc[0][0])) # plot the calibrator on the color bar cbarCC.ax.plot(0.5, lognormbpc, color=color, marker="^") # draw grid ax.grid(color='k', ls="--", lw=0.5) # azimuth labels ax.set_xticks(np.radians([0, 45, 90, 135, 180, 225, 270, 315])) ax.set_xticklabels([ r"${0:d}^\circ$".format(int(np.ceil(x))) for x in np.degrees(ax.get_xticks()) ], color='k') #Zenith angle labels ax.set_rlabel_position(250) ax.set_yticks(np.radians([20, 40, 60, 80])) ax.set_yticklabels([ r"${0:d}^\circ$".format(int(np.ceil(x))) for x in np.degrees(ax.get_yticks()) ], color='k') # Title ax.set_title( "MWA Tile beam (FEE)\naz = {0:.2f}, za = {1:.2f}, freq = {2:.2f}MHz\nobsid = {3}" .format(pointing_AZ, pointing_ZA, freq, obs)) plt.legend(bbox_to_anchor=(0.95, -0.05), ncol=2) #plt.savefig("{0}_{1:.2f}MHz_tile.eps".format(obs, freq), bbox_inches="tight", format="eps") logger.info("Saving plot as output file: {0}_{1:.2f}MHz_tile.png".format( obs, freq)) plt.savefig("{0}_{1:.2f}MHz_tile.png".format(obs, freq), bbox_inches="tight")