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)
示例#2
0
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
    }
示例#3
0
    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' \
示例#4
0
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
示例#6
0
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")