diff --git a/plot_sidelobe_tracks.py b/plot_sidelobe_tracks.py index 86614117c6c707df7cffa0c2d371c6ec5b6a8925..cdf2ee54724c0f4a9d9387c51feeac6c58146a5f 100644 --- a/plot_sidelobe_tracks.py +++ b/plot_sidelobe_tracks.py @@ -3,559 +3,322 @@ import numpy as np import numpy.ma as ma import argparse import matplotlib.pyplot as plt +from matplotlib.lines import Line2D import matplotlib.dates as mdates +import matplotlib.cm as mcm import warnings warnings.filterwarnings("ignore") +import h5py + import lofarantpos.db from lofarantpos.geo import localnorth_to_etrs, geographic_array_from_xyz, xyz_from_geographic, geographic_from_xyz -import ephem +from lofar_beams import voltage_beam, skycoord_to_lmn, lmn_to_skycoord -from photutils.detection import DAOStarFinder from astropy.stats import mad_std +import astropy.units as u from astropy import log +from astropy.time import Time +from astropy.coordinates import EarthLocation, SkyCoord, AltAz, get_sun, GCRS +from astropy.coordinates import SkyOffsetFrame, CartesianRepresentation, get_icrs_coordinates -def voltage_beam(lgrid, mgrid, lbeam, mbeam, c, p, q, r, freq, weights): - """Compute the beam response.""" - # n-coordinates - ngrid = np.sqrt(1 - lgrid**2 - mgrid**2) - nbeam = np.sqrt(1 - lbeam**2 - mbeam**2) - - # lmn-uvw product - u, v, w = np.array([p, q, r]) * freq / c - prod = 2j * np.pi * (u[:, np.newaxis, np.newaxis] * (lgrid - lbeam) + - v[:, np.newaxis, np.newaxis] * (mgrid - mbeam) + - w[:, np.newaxis, np.newaxis] * (ngrid - nbeam)) - return np.sum(weights * np.exp(prod), axis=0) - -def createFixedBody(ra, dec, epoch): - """Fixed body adapted to us.""" - fixedBody = ephem.FixedBody() - fixedBody._ra = ra - fixedBody._dec = dec - fixedBody._epoch = epoch - return fixedBody - -class Target(object): - """Encapsulates celestial bodies.""" - def __init__(self, name="", body=None, elevation=[], note = ""): - self.name = name - self.body = body - self.elevation = elevation - self.note = note - -def compute_fixed_sources_altaz(station, end_of_day, source): - """Compute the azimuth and elevation for a given source on the sky. +def read_single_bst(bst_filename): + """Read a single beamlet statistics (BST) file. Parameters ---------- - station : str - 'CS001' - end_of_day : date - End of the day specified - source : str - Source name, 'CasA' etc. + bst_filename : str + 'LXXX_CS001HBA0_BST.h5' Returns ------- - az : array - Array containing the azimuth of the source - el : array - Array containing the altitude of the source - time : array - Array containing the time instant for which the calculation is performed + metadata : dict + Dictionary containing some of the BST metadata + data : list + List containing the BST data """ + # Open HDF5 file + h5 = h5py.File(bst_filename, "r") + # Store main header meta data + metadata = {} + for key in ["antenna_names", "antenna_type", "station_name"]: + metadata[key] = h5["/"].attrs[key] + # Antennafield + metadata["antennafield"] = h5["/"].attrs["antenna_field"] + + # Number of subintegrations + nsub = len(h5.keys()) + metadata["nsub"] = nsub + + # Loop over keys + data_dict = [] + for key in h5.keys(): + # Keys in group + attribute_keys = list(h5[key].attrs.keys()) + # Keys to store in dict + keys_to_store = ["frequency_band", "subbands", "timestamp", "integration_interval", "digital_beam_pointing_direction"] + # Create dict + d = {"data": np.array(h5[key])} + for key_to_store in keys_to_store: + if key_to_store in attribute_keys: + d[key_to_store] = h5[key].attrs[key_to_store] + else: + print(f"Failed to find {key_to_store} in {key}") + d[key_to_store] = None + # Store tile beam for HBA + if "HBA" in metadata["antennafield"]: + d["tile_beam_pointing_direction"] = h5[key].attrs["tile_beam_pointing_direction"] + # Store dict + data_dict.append(d) + + # Store arrays + metadata["timestamps"] = np.array([d["timestamp"] for d in data_dict]) + metadata["durations"] = np.array([d["integration_interval"] for d in data_dict]) + + # Assume first entry holds for all (ASSUMPTION!!) + metadata["subbands"] = data_dict[0]["subbands"] + metadata["frequency_bands"] = data_dict[0]["frequency_band"] + metadata["digital_beam_pointing_directions"] = data_dict[0]["digital_beam_pointing_direction"][0] + if "HBA" in metadata["antennafield"]: + metadata["tile_beam_pointing_direction"] = data_dict[0]["tile_beam_pointing_direction"][0] + else: + metadata["tile_beam_pointing_direction"] = "" + # Data shape + nchan, npol = data_dict[0]["data"].shape + metadata["nchan"] = nchan + # Store as nsub x npol x nchan + data = np.zeros((nsub, 2, nchan)) + for isub in range(nsub): + data[isub, 0] = data_dict[isub]["data"][:, 0::2].T + data[isub, 1] = data_dict[isub]["data"][:, 1::2].T - az, el, time = [], [], [] - while station.date < end_of_day - ephem.minute: - station.date += ephem.minute * timestep - source.body.compute(station) - az.append(ephem.degrees(source.body.az)) - el.append(source.body.alt * (180. / np.pi)) - time.append(station.date.tuple()[3]) - return az, el, time - -def get_dynspec_for_source(station, end_of_day, pointing, source, freq_arr, c, p, q, r): - """Construct the dynamic spectrum for a station. + # Close file + h5.close() - Parameters - ---------- - station : str - 'CS001' - end_of_day : date - End of the day specified - pointing : str - RA, DEC of the pointing direction - source : str - Source name, 'CasA' etc. - freq_arr : array - Array of frequencies for which we compute the dynamic spectrum - c : float - Speed of light, m/s - p, q, r : floats - Antenna position parameters + return metadata, data - Returns - ------- - dynspec : array - 2D array containing the dynamic spectrum - """ - dynspec = [] - for fr in freq_arr: - time = [] - station.date = start_date - while station.date < end_of_day - ephem.minute: - station.date += ephem.minute * timestep - pointing.body.compute(station) - source.body.compute(station) - az_src = ephem.degrees(source.body.az) - el_src = source.body.alt * (180. / np.pi) - az, el = ephem.degrees(pointing.body.az) * (180. / np.pi), ephem.degrees(pointing.body.alt) * (180. / np.pi) - if el < min_elevation: - continue - ltile, mtile = np.sin(az * np.pi / 180) * np.cos(el * np.pi / 180), np.cos(az * np.pi / 180) * np.cos(el * np.pi / 180) - l_src, m_src = np.sin(az_src * np.pi / 180) * np.cos(el_src * np.pi / 180), np.cos(az_src * np.pi / 180) * np.cos(el_src * np.pi / 180) - # Compute beam - log.debug('Compute beam for {} Hz'.format(fr)) - beam = voltage_beam(lgrid, mgrid, ltile, mtile, c, p, q, r, fr, 1.0) - masked_beam = ma.masked_invalid(np.abs(beam)) - # Select beam values around source position - is delta of 0.01 right? - # TODO: Normalize beam? - coord_idx = np.argwhere((lgrid > l_src - 0.01) & (lgrid < l_src + 0.01) & (mgrid > m_src - 0.01) & (mgrid < m_src + 0.01)) - time.append(np.sum(masked_beam[coord_idx[0], coord_idx[1]])) - dynspec.append(time) - return dynspec - -def compute_lobes(station, end_of_day, pointing, min_elevation, freq, c, p, q, r): - """Compute the position of the grating lobes. +def read_bst(bst_filenames): + """Read multiple beamlet statistics (BST) files. Parameters ---------- - station : str - 'e.g. CS001' - end_of_day : date - End of the day specified - pointing : Object - Pointing direction - min_elevation : float - Minimum elevation below which we do not calculate, in degrees - freq: float - Frequency for which we are performing the calculation - c : float - Speed of light, m/s - p, q, r : floats - Antenna position parameters + bst_filenames : list + ['LXXX_CS001HBA0_BST.h5', 'LXXX_CS002HBA0_BST.h5'] Returns ------- - long_lobe : array - Array containing the longitude of the lobe (azimuth or RA) - lat_lobe : array - Array containing the latitude of the lobe (altitude or DEC) - time_lobe : array - Array containing the time instant for which the calculation is performed + metadata : dict + Dictionary containing some of the BST metadata + data : list + List containing the BST data """ - - long_lobe, lat_lobe, time_lobe = [], [], [] - - while station.date < end_of_day - ephem.minute: - station.date += ephem.minute * timestep - pointing.body.compute(station) - az, el = ephem.degrees(pointing.body.az) * (180. / np.pi), ephem.degrees(pointing.body.alt) * (180. / np.pi) - if el < min_elevation: - continue - ltile, mtile = np.sin(az * np.pi / 180) * np.cos(el * np.pi / 180), np.cos(az * np.pi / 180) * np.cos(el * np.pi / 180) - x = (ltile + 1) * int(npix / 2) - y = (mtile + 1) * int(npix / 2) - - # Compute beam - beam = voltage_beam(lgrid, mgrid, ltile, mtile, c, p, q, r, freq, 1.0) - masked_beam = ma.masked_invalid(np.abs(beam)) - bkg_sigma = mad_std(masked_beam) - # Extract grating lobe maxima - daofind = DAOStarFinder(fwhm=4.0, threshold=20.0 * bkg_sigma) - sources = daofind(masked_beam) - - if sources is not None: - for col in sources.colnames: - sources[col].info.format = '%.8g' # for consistent table output - for source in sources: - l_rec, m_rec = (int(source["xcentroid"]) - int(npix / 2)) / int(npix / 2), (int(source["ycentroid"]) - int(npix / 2)) / int(npix / 2) - pix_distance = int(np.sqrt(np.power(x - source["xcentroid"], 2) + np.power(y - source["ycentroid"], 2))) - az_rec = np.arctan2(l_rec, m_rec) - el_rec = np.arccos(l_rec / np.sin(az_rec)) - - grating_distance = ephem.separation((ephem.degrees(az_rec), ephem.degrees(el_rec)),(az * (np.pi / 180.), el * (np.pi / 180.))) * (180. / np.pi) - # Convert to degrees - az_rec = az_rec * (180./np.pi) - el_rec = el_rec * (180./np.pi) - if(grating_distance > 10): - if altaz: - # Return AZ and ALT - long_lobe.append(az_rec * (np.pi / 180.)) - lat_lobe.append(el_rec) - time_lobe.append(station.date.tuple()[3]) - else: - # Return RA and DEC - ra, dec = station.radec_of(az_rec * (np.pi / 180.), el_rec * (np.pi / 180.)) - long_lobe.append(ephem.degrees(ra)) - lat_lobe.append(dec * (180. / np.pi)) - time_lobe.append(station.date.tuple()[3]) - else: - continue + first, metadata, data = True, None, None + for bst_filename in bst_filenames: + # Get data + if first: + metadata, data = read_single_bst(bst_filename) + first = False else: - continue - if len(time_lobe) == 0: - continue - return long_lobe, lat_lobe, time_lobe + newmetadata, newdata = read_single_bst(bst_filename) + # Concatenate timestamps + metadata["timestamps"] = np.hstack((metadata["timestamps"], + newmetadata["timestamps"])) + # Concatenate integration_interval + metadata["durations"] = np.hstack((metadata["durations"], + newmetadata["durations"])) + # Concatenate data + data = np.vstack((data, newdata)) + + return metadata, data + +def freq_from_sb(sb: int, band: str) -> float: + """ + Convert central frequency to subband number + + Args: + sb: subband number + band: filter band + + Returns: + float: frequency in Hz -def init_bodies(station_name, fixed_body_names, sol_system_body_names, fixed_body_coordinates, pointing_coordinates, epoch): - """Initialize the fixed bodies and get the station information. + Example: + >>> freq_from_sb(297, '30_90') + 58007812.5 + """ + if band not in ["LBA_10_90", "LBA_30_90", "HBA_110_190", "HBA_170_230", "HBA_210_250"]: + return None + + if band == "LBA_10_90" or band == "LBA_30_90": + clock, zone = 200e6, 1 + elif band == "HBA_110_190": + clock, zone = 200e6, 2 + elif band == "HBA_170_230": + clock, zone = 160e6, 3 + elif band == "HBA_210_250": + clock, zone = 200e6, 3 + + sb_bandwidth = 0.5 * clock / 512. + freq_offset = 0.5 * clock * (zone -1) + freq = (sb * sb_bandwidth) + freq_offset + return freq + +def decibel(x): + return 10 * np.log10(x) + +def plot_bst_dynamic_spectrum(metadata, data, pol, contours, plot_contours): + """Plot a dynamic spectrum of the data. Parameters ---------- - station_name : str - Name of a LOFAR station, e.g. 'CS001' - fixed_body_names : array - Array containing the names of the fixed body celestial sources - sol_system_body_names: array - Array containing the names of the Sol system body sources - the Sun in this case - fixed_body_coordinates : array - Array containing coordinate tuples (RA, DEC) of the fixed body sources - pointing_coordinates : str - Pointing direction Ra, DEC coordinates - epoch : str - Coordinate epoch, J2000 + metadata : dict + Dictionary containing some of the BST metadata + data : list + List containing the BST data + pol : string + Polarization to plot + contours: dict + Dictionary containing the computed station beam Returns ------- - a_team : array - Array containing the fixed body sources - sol_system: array - Array containing the Sol system body sources - pointing_direction : object - The object representation of the pointing direction - station : object - The object representation of a station + metadata : dict + Dictionary containing some of the BST metadata + data : list + List containing the BST data """ - - a_team, sol_system = [], [] - - station = ephem.Observer() # Set station geographic location below - geo_station_position = geographic_from_xyz(db.phase_centres[station_name]) - station.lon = str(geo_station_position['lon_rad'] * (180. / np.pi)) - station.lat = str(geo_station_position['lat_rad'] * (180. / np.pi)) - station.elevation = 15. # Could not read elevation (altitude) properly from atenna position DB, set to fixed value - station.pressure = 0. # No refraction at horizon - station.horizon = '-0:34' # Idem - - pointing_direction = Target() - pointing_direction.name = 'Target' - pointing_direction.elevation = [] - pointing_direction.body = createFixedBody(pointing_coordinates[0], pointing_coordinates[1], epoch) - pointing_direction.body.compute(station) - - for i, name in enumerate(fixed_body_names): - body = Target() - body.name = name - body.elevation = [] - body.body = createFixedBody(fixed_body_coordinates[2*i], fixed_body_coordinates[2*i + 1], epoch) - body.body.compute(station) - a_team.append(body) - - for name in sol_system_body_names: - sun = Target() - sun.name = name - sun.elevation = [] - sun.body = ephem.Sun() - sun.body.compute(station) - sol_system.append(sun) - - return a_team, sol_system, pointing_direction, station + # Timestamps + t = mdates.date2num(metadata["timestamps"]) + # Frequencies + band_x, band_y = metadata["frequency_bands"][0] + freq_x = np.array([freq_from_sb(sb, band_x) for sb in metadata["subbands"]]) + freq_y = np.array([freq_from_sb(sb, band_y) for sb in metadata["subbands"]]) + + data_mean = np.mean(data, axis=0) + data /= data_mean[np.newaxis, :, :] + #data = decibel(data) + + if pol.lower() == "x": + p = data[:, 0, :].squeeze() + elif pol.lower() == "y": + p = data[:, 1, :].squeeze() + else: + p = np.sum(data[:, :, :], axis=1).squeeze() + + fig, ax = plt.subplots(figsize=(10, 6)) + + date_format = mdates.DateFormatter("%F\n%H:%M:%S") + fig.autofmt_xdate(rotation=0, ha="center") + + vmin, vmax = np.percentile(p, (5, 99)) + if plot_contours: + legend_lines, sources = [], [] + ax.imshow(p.T, origin="lower", aspect="auto", interpolation="None", vmin=vmin, vmax=vmax, cmap="Blues_r", + extent=[np.nanmin(t), np.nanmax(t), np.min(freq_x)/1.e6, np.max(freq_x)/1.e6]) + if plot_contours: + for color, contour in contours.items(): + contours = plt.contour(t, freq_x/1.e6, contour[1], 1, linewidths=3., alpha=0.5, colors=[color]) + legend_lines.append(Line2D([],[], color=color)) + sources.append(contour[0]) + plt.legend(legend_lines, sources, bbox_to_anchor=(1, 1), loc='upper left') + ax.xaxis_date() + ax.xaxis.set_major_formatter(date_format) + ax.set_xlabel("Date (UTC)") + ax.set_ylabel("Frequency (MHz)") + plt.title("Beamlet statistics for " + metadata['station_name'] + metadata['antennafield']) + #plt.show() + plt.tight_layout() + log.info("Saving plots...") + fig.savefig(metadata['station_name'] + metadata['antennafield'] + "_dynspec.png", bbox_inches="tight") + log.info("Done.") +# Speed of light +c = 299792458.0 # m/s if __name__ == "__main__": - parser = argparse.ArgumentParser(description=' Compute the sky tracks (ALT-AZ or RA-DEC) and dynamic spectra of HBA grating lobes.') - - parser.add_argument('--pointing_coordinates', type=str, nargs='+', default=[], - help='Pointing direction coordinates, RA DEC') - parser.add_argument('--frequency_range', type=float, nargs='+', default=[], - help='The frequency range over which we compute the sidelobes, MHz, e.g. 135 144.') - parser.add_argument('--freq_step', type=float, default=None, - help='Frequency step for calculation, MHz.') - parser.add_argument('--stations', type=str, default=None, - help='Station groups: ST, CS, RS, IS (super-terp, core, remote or international).') - parser.add_argument('--min_elevation', type=int, default=None, - help='Minimum elevation to which the calculation is performed in degrees.') - parser.add_argument('--timestep', type=int, default=None, - help='Timestep for calculation, in minutes.') - parser.add_argument('--fixed_body_names', type=str, nargs='+', default=[], + desc = 'Plot bright sources in grating lobes over BST HBA dynamic spectra.' + + parser = argparse.ArgumentParser(description=desc) + + parser.add_argument("filenames", help="SST filenames", nargs="*", metavar="FILE") + parser.add_argument("-p", "--pol", help="Polarization to plot dynamic spectrum [stokes/X/Y, default: stokes]", default="stokes") + parser.add_argument("-s", "--sources", type=str, nargs='+', default=[], help='Array of names for the fixed sources, e.g. CasA CygA...') - parser.add_argument('--fixed_body_coordinates', type=str, nargs='+', default=[], - help='Array of RA, DEC coordinates for the fixed body sources ["19:59:28:3566", "+40:44:02.096", ...]') - parser.add_argument('--sol_system_body_names', type=str, nargs='+', default=[], - help='Solar system body names, "[Sun]".') - parser.add_argument('--start_date', type=str, default=None, - help='Date for which the calculation is performed e.g. 2024/05/08.') - parser.add_argument('--epoch', type=str, default='2000', - help='Epoch of the pointing direction coordinates, e.g. 2000') - parser.add_argument('--altaz', type=bool, default=False, - help='Is the plotting to be done in Equatorial or local coordinates; True/False.') - parser.add_argument('--dynspec_source', type=str, default=None, - help='Which source to plot a dynspec for; one of the fixed_body_names, e.g. "CasA".') + parser.add_argument("-c", "--contours", default=True, action='store_false') args = parser.parse_args() - # All sky grid - npix = 205 - width = 1.0 - lgrid, mgrid = np.meshgrid(np.linspace(-width, width, npix), - np.linspace(-width, width, npix)) - # Load the LOFAR antenna database db = lofarantpos.db.LofarAntennaDatabase() - # Speed of light (in m/s) - c = 299792458.0 - - # Station groups - # Super-terp stations - - st_stations =["CS002HBA0", "CS002HBA1", - "CS003HBA0", "CS003HBA1", - "CS004HBA0", "CS004HBA1", - "CS005HBA0", "CS005HBA1", - "CS006HBA0", "CS006HBA1", - "CS007HBA0", "CS007HBA1"] - - # Core minus super-terp stations - core_stations = ["CS001HBA0", "CS001HBA1", - "CS011HBA0", "CS011HBA1", - "CS013HBA0", "CS013HBA1", - "CS001HBA0", "CS001HBA1", - "CS017HBA0", "CS017HBA1", - "CS021HBA0", "CS021HBA1", - "CS024HBA0", "CS024HBA1", - "CS026HBA0", "CS026HBA1", - "CS028HBA0", "CS028HBA1", - "CS030HBA0", "CS030HBA1", - "CS031HBA0", "CS031HBA1", - "CS032HBA0", "CS032HBA1", - "CS101HBA0", "CS101HBA1", - "CS103HBA0", "CS103HBA1", - "CS201HBA0", "CS201HBA1", - "CS301HBA0", "CS301HBA1", - "CS302HBA0", "CS302HBA1", - "CS401HBA0", "CS401HBA1", - "CS501HBA0", "CS501HBA1"] - - remote_stations = ["RS106HBA", "RS205HBA", - "RS208HBA", "RS210HBA", - "RS305HBA", "RS306HBA", - "RS307HBA", "RS310HBA", - "RS406HBA", "RS407HBA", - "RS409HBA", "RS503HBA", - "RS508HBA", "RS509HBA"] - - international_stations = ["DE601HBA", "DE602HBA", - "DE603HBA", "DE604HBA", - "DE605HBA", "DE609HBA", - "FR606HBA", "IE613HBA", - "PL610HBA", "PL611HBA", - "PL612HBA", "LV614HBA", - "SE607HBA", "UK608HBA"] try: - if len(args.pointing_coordinates) == 0: - raise ValueError('Please enter a valid pointing direction, hh:mm:ss.s dd:mm:ss.s".') + if args.filenames is None or len(args.filenames) == 0: + raise ValueError('Please provide valid data file(s)') else: - pointing_coordinates = args.pointing_coordinates - log.info("Pointing: {}".format(pointing_coordinates)) - except ValueError as e: - log.error(e) - if len(args.frequency_range) == 0: - frequency_range = [135., 144.] - log.info("Setting frequency range to {} MHz".format(frequency_range)) - else: - frequency_range = args.frequency_range - log.info("Frequency range: {}".format(frequency_range)) - if args.stations is None: - stations = 'ST' - log.info("Selected superterp stations.") - else: - stations = args.stations - if args.min_elevation is None: - min_elevation = 15 - log.info("Setting minimum elevation to {} degrees".format(min_elevation)) - else: - min_elevation = args.min_elevation - if args.timestep is None: - timestep = 5 - log.info("Setting timestep to {} minutes".format(timestep)) - else: - timestep = args.timestep - if args.freq_step is None: - freq_step = 1. - log.info("Setting frequency step to {} MHz".format(freq_step)) - else: - freq_step = args.freq_step - if len(args.fixed_body_names) == 0: - fixed_body_names = ['CygA', 'CasA', 'TauA', 'VirA'] - log.info("Setting celestial sources to {}".format(fixed_body_names)) - else: - fixed_body_names = args.fixed_body_names - log.info("Provided celestial sources: {}".format(fixed_body_names)) - if len(args.fixed_body_coordinates) == 0: - fixed_body_coordinates = ['19:59:28:3566', '+40:44:02.096', - '23:23:27.94', '+58:48:42.4', - '05:34:31.971', '+22:00:52.06', - '12:30:49.4233', '+12:23:28.043'] - else: - fixed_body_coordinates = args.fixed_body_coordinates - log.info("Provided coordinates for celestial sources: {}".format(fixed_body_coordinates)) - if len(args.sol_system_body_names) == 0: - sol_system_body_names = ['Sun'] - log.info("Setting solar system bodies to {}".format(sol_system_body_names)) - else: - sol_system_body_names = args.sol_system_body_names - try: - if args.start_date is None: - raise ValueError('Please enter a valid start date.') - else: - start_date = args.start_date - log.info("Start date: {}".format(start_date)) + filenames = args.filenames + log.info("Filename(s): {}".format(filenames)) except ValueError as e: log.error(e) - if args.dynspec_source is None: - dynspec_source = 'CasA' - log.info("Creating dynspec plot for {}".format(dynspec_source)) + if len(args.sources) == 0: + sources = ['Cyg A', 'Cas A', 'Tau A', 'Vir A'] + log.info("Overplotting sidelobe contours for: {}".format(sources)) else: - dynspec_source = args.dynspec_source + sources = args.sources + log.info("Overplotting sidelobe contours for: {}".format(sources)) + log.info("Overplotting beam contours: {}".format(args.contours)) + metadata, data = read_bst(args.filenames) - if stations == 'ST': - stations = st_stations - elif stations == 'CS': - stations = core_stations - elif stations == 'RS': - stations = remote_stations - elif stations == 'IS': - stations = international_stations - else: - log.error('Invalid station group name provided.') - raise ValueError('Invalid station group name provided.') - epoch = args.epoch - altaz = args.altaz - - freq_arr = np.arange(frequency_range[0], frequency_range[1], freq_step) * 1.e6 - - rows, cols = len(stations), 1 - - if stations in ['ST', 'CS']: - cols = 2 - - fig, ax = plt.subplots(rows, cols, figsize=(10, 25), subplot_kw={'projection': 'polar'}) - ax = ax.ravel() - - fig1, ax1 = plt.subplots(rows, cols, figsize=(10, 25)) - ax1 = ax1.ravel() - - if altaz: - log.info("Plotting ALT-AZ sky plots.") - else: - log.info("Plotting RA-DEC sky plots.") + sap_pointings = metadata["digital_beam_pointing_directions"] + tile_pointing = metadata["tile_beam_pointing_direction"] + + # TODO implement logic for more than one SAP + psaps = SkyCoord(sap_pointings.split(', ')[0].split(' (')[1].split('rad')[0], sap_pointings.split(', ')[1].split('rad')[0], unit="rad", frame="icrs") + ptile = SkyCoord(tile_pointing.split(', ')[0].split(' (')[1].split('rad')[0], tile_pointing.split(', ')[1].split('rad')[0], unit="rad", frame="icrs") + + log.info("SAP pointing(s): {}".format(psaps)) + log.info("Tile pointing: {}".format(ptile)) + + log.info("Station name: {}".format(metadata['station_name']+metadata['antennafield'])) + station_name = metadata['station_name']+metadata['antennafield'] + loc = db.phase_centres[station_name] + loc = EarthLocation.from_geocentric(*loc * u.m) + + log.info("Station location: {}".format(loc)) + t = metadata["timestamps"] + ntime = len(t) + band_x, band_y = metadata["frequency_bands"][0] + freq = np.array([freq_from_sb(sb, band_x) for sb in metadata["subbands"]]) + + # Compute zenith + zenith = SkyCoord(az=np.zeros(ntime), alt=90 * np.ones(ntime), unit="deg", + obstime=t, location=loc, frame="altaz").transform_to(GCRS) - # Loop over stations - for i, station_name in enumerate(stations): - - log.info("Calculating for station {}".format(station_name)) + # Get LMN coordinates of the tile and SAP pointings, and the A-team source + ltile, mtile, _ = skycoord_to_lmn(ptile, zenith) + lsap, msap, _ = skycoord_to_lmn(psaps, zenith) # handle more SAPs + + # Get element positions within tile + p, q, r = db.hba_dipole_pqr(station_name).T + p, q, r = p[:16], q[:16], r[:16] + + # Sidelobe sources + beam_maps = {} + if args.contours: + cmap = mcm.get_cmap('Spectral') + for i in range(len(sources)): + psrc = get_icrs_coordinates(sources[i]) + lsrc, msrc, _ = skycoord_to_lmn(psrc, zenith) + tilebeam = voltage_beam(lsrc, msrc, ltile, mtile, p, q, r, freq, 1.0) + # Compute beam per source + p, q, r = db.antenna_pqr(station_name).T + beam = voltage_beam(lsrc, msrc, lsap, msap, p, q, r, freq, tilebeam) + beam_map = np.abs(beam.T)**2 + color = cmap(i / len(sources)) + beam_maps[color] = (sources[i], beam_map) - # Get antenna positions - p, q, r = db.hba_dipole_pqr(station_name).T - # Initialize - a_team, sol_system, pointing_direction, station = init_bodies(station_name, fixed_body_names, sol_system_body_names, fixed_body_coordinates, pointing_coordinates, epoch) - - log.info("Calculating positions and dynspec for A-team sources...") - for at in a_team: - log.info("Calculating for {}...".format(at.name)) - station.date = start_date - end_of_day = station.date - end_of_day += 1. - az, el, time = compute_fixed_sources_altaz(station, end_of_day, at) - - if altaz: - ax[i].plot(az, el, linestyle=None, label=at.name) - ax[i].annotate(str(time[0])+'h', (az[0], el[0]), textcoords="offset points", xytext=(0,10), ha='center') - ax[i].annotate(str(time[-2])+'h', (az[-2], el[-2]), textcoords="offset points", xytext=(0,10), ha='center') - if dynspec_source == at.name: - dynspec = get_dynspec_for_source(station, end_of_day, pointing_direction, at, freq_arr, c, p, q, r) - ax1[i].imshow(dynspec, origin='lower', extent=[time[0], time[-2], freq_arr[0] / 1.e6, freq_arr[-1] / 1.e6], aspect='equal') - else: - ax[i].scatter(ephem.degrees(at.body.ra), at.body.dec * (180. / np.pi), label=at.name) - if dynspec_source == at.name: - log.info("Calculating dynspec plot for {}".format(dynspec_source)) - dynspec = get_dynspec_for_source(station, end_of_day, pointing_direction, at, freq_arr, c, p, q, r) - ax1[i].imshow(dynspec, origin='lower', extent=[time[0], time[-2], freq_arr[0] / 1.e6, freq_arr[-1] / 1.e6], aspect='equal') - ax1[i].set_title("Dynspec for station " + station_name, pad=10) - ax1[i].set_xlabel("Time [UT]") - ax1[i].set_ylabel("Frequency [MHz]") - - log.info("Calculating positions and dynspec for Solar system sources...") - for sol in sol_system: - station.date = start_date - end_of_day = station.date - end_of_day += 1. - az, el, time = compute_fixed_sources_altaz(station, end_of_day, sol) - if altaz: - ax[i].plot(az, el, linestyle=None, label=sol.name) - ax[i].annotate(str(time[0])+'h', (az[0], el[0]), textcoords="offset points", xytext=(0,10), ha='center') - ax[i].annotate(str(time[-2])+'h', (az[-2], el[-2]), textcoords="offset points", xytext=(0,10), ha='center') - if dynspec_source == sol.name: - dynspec = get_dynspec_for_source(station, end_of_day, pointing_direction, sol, freq_arr, c, p, q, r) - ax1[i].imshow(dynspec, origin='lower', extent=[time[0], time[-2], freq_arr[0] / 1.e6, freq_arr[-1] / 1.e6], aspect='equal') - else: - ax[i].scatter(ephem.degrees(sol.body.ra), sol.body.dec * (180. /np.pi), label=sol.name) - if dynspec_source == sol.name: - dynspec = get_dynspec_for_source(station, end_of_day, pointing_direction, sol, freq_arr, c, p, q, r) - ax1[i].imshow(dynspec, origin='lower', extent=[time[0], time[-2], freq_arr[0] / 1.e6, freq_arr[-1] / 1.e6], aspect='equal') - ax1[i].set_title("Dynspec for station " + station_name, pad=10) - ax1[i].set_xlabel("Time [UT]") - ax1[i].set_ylabel("Frequency [MHz]") - - log.info("Calculating frequency dependence of grating lobe positions...") - for freq in freq_arr: - station.date = start_date - end_of_day = station.date - end_of_day += 1. - long, lat, time = compute_lobes(station, end_of_day, pointing_direction, min_elevation, freq * 1.e6, c, p, q, r) - if len(long) != 0 and len(lat) != 0 and len(time) != 0: - if altaz: - ax[i].scatter(long, lat, marker='x', s=40, label='lobe: ' + str(freq) + ' MHz') - else: - ax[i].scatter(long, lat, marker='x', s=40, label='lobe: ' + str(freq) + ' MHz') - ax[i].annotate(str(time[0])+'h', (long[0], lat[0]), textcoords="offset points", xytext=(0,10), ha='center') - if len(time) > 4: - ax[i].annotate(str(time[-2])+'h', (long[-2], lat[-2]), textcoords="offset points", xytext=(0,10), ha='center') - else: - continue - - leg_angle = np.deg2rad(10.5) - ax[i].legend(loc="lower left", bbox_to_anchor=(.5 + np.cos(leg_angle)/2, .5 + np.sin(leg_angle)/2)) - if altaz: - ax[i].set_rlim(bottom=90, top=min_elevation) - ax[i].set_title("Grating lobe positions for station " + station_name + " [ALT/AZ]", pad=10) - #ax[i].set_aspect(1.0) - else: - ax[i].set_rlim(bottom=90, top=-30) - ax[i].set_title("Grating lobe positions for station " + station_name + " [RA/DEC]", pad=10) - - - plt.tight_layout() - log.info("Saving plots...") - fig.savefig("Sky_positions.png", bbox_inches="tight", overwrite=True) - fig1.savefig("Dynamic_spectrum.png", bbox_inches="tight", overwrite=True) - log.info("Done.") - #plt.show() + plot_bst_dynamic_spectrum(metadata, data, args.pol, beam_maps, args.contours)