moon = kat.sources.lookup['moon'] with start_session(kat, **vars(opts)) as session: session.standard_setup(**vars(opts)) session.nd_params = nd_off session.capture_start() once = True start_time = time.time() while once or time.time() < start_time + opts.max_duration: once = False moon = katpoint.Target('Moon, special') antenna = katpoint.Antenna( 'ant1, -30:43:17.3, 21:24:38.5, 1038.0, 12.0, 18.4 -8.7 0.0, -0:05:30.6 0 -0:00:03.3 0:02:14.2 0:00:01.6 -0:01:30.6 0:08:42.1, 1.22' ) # find some way of getting this from session moon.antenna = antenna off1_azel = katpoint.construct_azel_target( wrap_angle(moon.azel()[0] + np.radians(10)), moon.azel()[1]) off1_azel.antenna = antenna off1 = katpoint.construct_radec_target(off1_azel.radec()[0], off1_azel.radec()[1]) off1.antenna = antenna off1.name = 'off1' off2_azel = katpoint.construct_azel_target( wrap_angle(moon.azel()[0] - np.radians(10)), moon.azel()[1]) off2_azel.antenna = antenna off2 = katpoint.construct_radec_target(off2_azel.radec()[0], off2_azel.radec()[1]) off2.antenna = antenna off2.name = 'off2'
def calc_pointing_offsets(session, beams, target, middle_time, temperature, pressure, humidity): """Calculate pointing offsets per receptor based on primary beam fits. Parameters ---------- session : :class:`katcorelib.observe.CaptureSession` object The active capture session beams : dict mapping receptor name to list of :class:`BeamPatternFit` Fitted primary beams, per receptor and per frequency chunk target : :class:`katpoint.Target` object The target on which offset pointings were done middle_time : float Unix timestamp at the middle of sequence of offset pointings, used to find the mean location of a moving target (and reference for weather) temperature, pressure, humidity : float Atmospheric conditions at middle time, used for refraction correction Returns ------- pointing_offsets : dict mapping receptor name to offset data (10 floats) Pointing offsets per receptor in degrees, stored as a sequence of - requested (az, el) after refraction (input to the pointing model), - full (az, el) offset, including contributions of existing pointing model, any existing adjustment and newly fitted adjustment (useful for fitting new pointing models as it is independent), - full (az, el) adjustment on top of existing pointing model, replacing any existing adjustment (useful for reference pointing), - relative (az, el) adjustment on top of existing pointing model and adjustment (useful for verifying reference pointing), and - rough uncertainty (standard deviation) of (az, el) adjustment. """ pointing_offsets = {} # Iterate over receptors for ant in sorted(session.observers): beams_freq = beams.get(ant.name, []) beams_freq = [b for b in beams_freq if b is not None and b.is_valid] if not beams_freq: user_logger.debug("%s had no valid primary beam fitted", ant.name) continue offsets_freq = np.array([b.center for b in beams_freq]) offsets_freq_std = np.array([b.std_center for b in beams_freq]) weights_freq = 1. / offsets_freq_std**2 # Do weighted average of offsets over frequency chunks results = np.average(offsets_freq, axis=0, weights=weights_freq, returned=True) pointing_offset = results[0] pointing_offset_std = np.sqrt(1. / results[1]) user_logger.debug("%s x=%+7.2f'+-%.2f\" y=%+7.2f'+-%.2f\"", ant.name, pointing_offset[0] * 60, pointing_offset_std[0] * 3600, pointing_offset[1] * 60, pointing_offset_std[1] * 3600) # Get existing pointing adjustment receptor = getattr(session.kat, ant.name) az_adjust = receptor.sensor.pos_adjust_pointm_azim.get_value() el_adjust = receptor.sensor.pos_adjust_pointm_elev.get_value() existing_adjustment = deg2rad(np.array((az_adjust, el_adjust))) # Start with requested (az, el) coordinates, as they apply # at the middle time for a moving target requested_azel = target.azel(timestamp=middle_time, antenna=ant) # Correct for refraction, which becomes the requested value # at input of pointing model rc = RefractionCorrection() def refract(az, el): # noqa: E306, E301 """Apply refraction correction as at the middle of scan.""" return [az, rc.apply(el, temperature, pressure, humidity)] refracted_azel = np.array(refract(*requested_azel)) # More stages that apply existing pointing model and/or adjustment pointed_azel = np.array(ant.pointing_model.apply(*refracted_azel)) adjusted_azel = pointed_azel + existing_adjustment # Convert fitted offset back to spherical (az, el) coordinates pointing_offset = deg2rad(np.array(pointing_offset)) beam_center_azel = target.plane_to_sphere(*pointing_offset, timestamp=middle_time, antenna=ant) # Now correct the measured (az, el) for refraction and then apply the # existing pointing model and adjustment to get a "raw" measured # (az, el) at the output of the pointing model stage beam_center_azel = refract(*beam_center_azel) beam_center_azel = ant.pointing_model.apply(*beam_center_azel) beam_center_azel = np.array(beam_center_azel) + existing_adjustment # Make sure the offset is a small angle around 0 degrees full_offset_azel = wrap_angle(beam_center_azel - refracted_azel) full_adjust_azel = wrap_angle(beam_center_azel - pointed_azel) relative_adjust_azel = wrap_angle(beam_center_azel - adjusted_azel) # Cheap 'n' cheerful way to convert cross-el uncertainty to azim form offset_azel_std = pointing_offset_std / \ np.array([np.cos(refracted_azel[1]), 1.]) # We store all variants of the pointing offset since we have it all # at our fingertips here point_data = np.r_[rad2deg(refracted_azel), rad2deg(full_offset_azel), rad2deg(full_adjust_azel), rad2deg(relative_adjust_azel), offset_azel_std] pointing_offsets[ant.name] = point_data return pointing_offsets
# Start capture session, which creates HDF5 file with start_session(kat, **vars(opts)) as session: session.standard_setup(**vars(opts)) session.capture_start() start_time = time.time() targets_observed = [] # Keep going until the time is up keep_going = True while keep_going: keep_going = (opts.max_duration is not None) and opts.repeat targets_before_loop = len(targets_observed) # Iterate through source list, picking the next one that is up for target in observation_sources.iterfilter(el_limit_deg=opts.horizon): target_future_azel = target.azel(timestamp=time.time()+opts.track_duration/2) target = katpoint.construct_azel_target(katpoint.wrap_angle(target_future_azel[0]),katpoint.wrap_angle(target_future_azel[1])) session.label('track') user_logger.info("Initiating %g-second drift scan on target '%s'" % (opts.track_duration, target.name,)) # Split the total track on one target into segments lasting as long as the noise diode period # This ensures the maximum number of noise diode firings total_track_time = 0. while total_track_time < opts.track_duration: next_track = opts.track_duration - total_track_time # Cut the track short if time ran out if opts.max_duration is not None: next_track = min(next_track, opts.max_duration - (time.time() - start_time)) if opts.nd_params['period'] > 0: next_track = min(next_track, opts.nd_params['period']) if next_track <= 0 or not session.track(target, duration=next_track, announce=False): break total_track_time += next_track
if not kat.dry_run and kat.ants.req.mode('STOP') : user_logger.info("Setting Antenna Mode to 'STOP', Powering on Antenna Drives.") time.sleep(10) else: if not kat.dry_run : user_logger.error("Unable to set Antenna mode to 'STOP'.") once = True start_time = time.time() while once or time.time() < start_time + opts.max_duration : once = False moon = katpoint.Target('Moon, special') antenna = katpoint.Antenna('ant1, -30:43:17.3, 21:24:38.5, 1038.0, 12.0, 18.4 -8.7 0.0, -0:05:30.6 0 -0:00:03.3 0:02:14.2 0:00:01.6 -0:01:30.6 0:08:42.1, 1.22') # find some way of getting this from session moon.antenna = antenna off1_azel = katpoint.construct_azel_target(wrap_angle(moon.azel()[0] + np.radians(10) ),moon.azel()[1] ) off1_azel.antenna = antenna off1 = katpoint.construct_radec_target(off1_azel.radec()[0],off1_azel.radec()[1]) off1.antenna = antenna off1.name = 'off1' off2_azel = katpoint.construct_azel_target(wrap_angle(moon.azel()[0] - np.radians(10) ),moon.azel()[1] ) off2_azel.antenna = antenna off2 = katpoint.construct_radec_target(off2_azel.radec()[0],off2_azel.radec()[1]) off2.antenna = antenna off2.name = 'off2' sources = katpoint.Catalogue(add_specials=False) sources.add(moon) sources.add(off2) sources.add(off1) txtlist = ', '.join( [ "'%s'" % (target.name,) for target in sources])
def reversescan(session, target, nd_period=None, lead_time=None, **kwargs): """Reverse scan observation. This scan is done in "Reverse" This means that it is given an area to scan (via kwargs) rather that a target and parameters Parameters ---------- session: `CaptureSession` target: katpoint.Target nd_period: float noisediode period lead_time: float noisediode trigger lead time """ # trigger noise diode if set trigger(session.kat, duration=nd_period, lead_time=lead_time) if 'radec_p1' in kwargs and 'radec_p2' in kwargs: # means that there is a target area # find lowest setting part or # highest rising part antenna = copy.copy(target.antenna) target_list = [] for i in range(1, _MAX_POINTS_IN_SCAN_AREA_POLYGON + 1): key = 'radec_p%i' % i if key in kwargs: target_list.append( katpoint.Target( 't%i,radec,%s' % (i, kwargs[key]), antenna=target.antenna, )) else: user_logger.error( "No scan area defined - require radec_p1 and radec_p2") return False direction = kwargs.get("direction", False) scan_speed = kwargs.get("scan_speed", _DEFAULT_SCAN_SPEED_ARCMIN_PER_SEC) obs_start_ts = katpoint.Timestamp(time.time()).to_ephem_date() # use 1 deg offset to pre-position >4 min in the future to take into account slewing el, az_min, az_max, t_start, t_end = _get_scan_area_extents(target_list, antenna, obs_start_ts, offset_deg=1) # TODO: get horizon limit from observation - may want to pass limits of # the "acceptable elevation extent" into _get_scan_area_extents. if _MIN_HORIZON_EL_FOR_SCAN_DEG > np.degrees(el): user_logger.warning( "Source and scan below horizon: %s < %s", np.degrees(el), _MIN_HORIZON_EL_FOR_SCAN_DEG, ) return False scan_target = katpoint.construct_azel_target(katpoint.wrap_angle(az_min), el) scan_target.name = target.name # katpoint destructively set dates and times during calculation # restore datetime before continuing scan_target.antenna = antenna scan_target.antenna.observer.date = obs_start_ts user_logger.info("Slew to scan start") # slew to target. target_visible = session.track(scan_target, duration=0.0, announce=False) if not target_visible: user_logger.warning( "Start of scan is not visible! Elevation: %.1f deg.", np.degrees(el)) return False # This is the real scan obs_start_ts = katpoint.Timestamp(time.time()).to_ephem_date() el, az_min, az_max, t_start, t_end = _get_scan_area_extents( target_list, antenna, obs_start_ts) scan_target = katpoint.construct_azel_target( katpoint.wrap_angle((az_min + az_max) / 2.), el) scan_target.name = target.name # katpoint destructively set dates and times during calculation # restore datetime before continuing scan_target.antenna = antenna scan_target.antenna.observer.date = obs_start_ts scan_start = np.degrees(az_min - (az_min + az_max) / 2.) scan_end = np.degrees(az_max - (az_min + az_max) / 2.) scanargs = {} if "projection" in kwargs: scanargs["projection"] = kwargs["projection"] # take into account projection effects of the sky and convert to degrees per second # E.g., 5 arcmin/s should translate to 5/60/cos(el) deg/s scan_speed = (scan_speed / 60.0) / np.cos(el) scanargs["duration"] = abs(scan_start - scan_end) / scan_speed # Duration in seconds user_logger.info("Scan duration is %.2f and scan speed is %.2f deg/s", scanargs["duration"], scan_speed) user_logger.info("Start Time: %s", t_start) user_logger.info("End Time: %s", t_end) num_scan_lines = 0 while time.time() <= t_end.secs: if direction: scanargs["start"] = scan_start, 0.0 scanargs["end"] = scan_end, 0.0 else: scanargs["start"] = scan_end, 0.0 scanargs["end"] = scan_start, 0.0 user_logger.info("Azimuth scan extent [%.1f, %.1f]" % (scanargs["start"][0], scanargs["end"][0])) target_visible = scan(session, scan_target, nd_period=nd_period, lead_time=lead_time, **scanargs) direction = not direction if target_visible: num_scan_lines += 1 user_logger.info("Scan completed - %s scan lines", num_scan_lines) return num_scan_lines > 0
katpoint.rad2deg(dec)) Ntarget = katpoint.construct_radec_target(ra, dec2) Ntarget.antenna = bf_ants Ntarget.name = target_name + '_R' target = Ntarget print target print target.name # Get onto beamformer target session.track(target, duration=5) # Perform a drift scan if selected if opts.drift_scan: transit_time = katpoint.Timestamp() + opts.target_duration / 2.0 # Stationary transit point becomes new target az, el = target.azel(timestamp=transit_time) target = katpoint.construct_azel_target(katpoint.wrap_angle(az), el) # Go to transit point so long session.track(target, duration=0) # Only start capturing once we are on target session.capture_start() print "sleeping 10 secs, will be removed later in dpc is not asynchronous" time.sleep(10) # for targets in list print "kat.ptuse_1.req.ptuse_target_start (" + data_product_id + ", " + beam_id + ", " + target.name + ")" reply = kat.ptuse_1.req.ptuse_target_start(data_product_id, beam_id, target.name) print "kat.ptuse_1.req.ptuse_target_start returned " + str(reply)
def fit_pointing_model(filename, opts): # declare the globals to update their values global az global el global measured_delta_az global measured_delta_el global std_delta_az global std_delta_el global keep # These fields contain strings, while the rest of the fields are assumed to contain floats string_fields = ['dataset', 'target', 'timestamp_ut', 'data_unit'] # Load old pointing model, if given old_model = None if opts.pmfilename: try: old_model = katpoint.PointingModel(file(opts.pmfilename).readline()) print("Loaded %d-parameter pointing model from '%s'" % (len(old_model), opts.pmfilename)) except IOError: raise RuntimeError("Could not load old pointing model from '%s'" % (opts.pmfilename,)) # Load data file in one shot as an array of strings data = np.loadtxt(filename, dtype='string', comments='#', delimiter=', ') # Interpret first non-comment line as header fields = data[0].tolist() # By default, all fields are assumed to contain floats formats = np.tile(np.float, len(fields)) # The string_fields are assumed to contain strings - use data's string type, as it is of sufficient length formats[[fields.index(name) for name in string_fields if name in fields]] = data.dtype # Convert to heterogeneous record array data = np.rec.fromarrays(data[1:].transpose(), dtype=list(zip(fields, formats))) # Load antenna description string from first line of file and construct antenna object from it antenna = katpoint.Antenna(file(filename).readline().strip().partition('=')[2]) # Use the pointing model contained in antenna object as the old model (if not overridden by file) # If the antenna has no model specified, a default null model will be used if old_model is None: old_model = antenna.pointing_model # Obtain desired fields and convert to radians az, el = wrap_angle(deg2rad(data['azimuth'])), deg2rad(data['elevation']) measured_delta_az, measured_delta_el = deg2rad(data['delta_azimuth']), deg2rad(data['delta_elevation']) # Uncertainties are optional min_std = deg2rad(opts.min_rms / 60. / np.sqrt(2)) std_delta_az = np.clip(deg2rad(data['delta_azimuth_std']), min_std, np.inf) \ if 'delta_azimuth_std' in data.dtype.fields and opts.use_stats else np.tile(min_std, len(az)) std_delta_el = np.clip(deg2rad(data['delta_elevation_std']), min_std, np.inf) \ if 'delta_elevation_std' in data.dtype.fields and opts.use_stats else np.tile(min_std, len(el)) targets = data['target'] keep = data['keep'].astype(np.bool) if 'keep' in data.dtype.fields else np.tile(True, len(targets)) # List of unique targets in data set and target index for each data point unique_targets = np.unique(targets).tolist() target_index = np.array([unique_targets.index(t) for t in targets]) # Initialise new pointing model and set default enabled parameters new_model = katpoint.PointingModel() num_params = len(new_model) default_enabled = np.nonzero(list(old_model.values()))[0] # If the old model is empty / null, select the most basic set of parameters for starters if len(default_enabled) == 0: default_enabled = np.array([1, 3, 4, 5, 6, 7]) - 1 enabled_params = np.tile(False, num_params) enabled_params[default_enabled] = True enabled_params = enabled_params.tolist() old = PointingResults(old_model) new = PointingResults(new_model) # Fit new pointing model and update results params, sigma_params = new_model.fit(az[keep], el[keep], measured_delta_az[keep], measured_delta_el[keep], std_delta_az[keep], std_delta_el[keep], enabled_params) new.update(new_model) # Axis limit to be applied to all residual plots resid_lim = 1.2 * old.abs_sky_error.max() # Save pointing model to file outfile = file(opts.outfilebase + '.csv', 'w') # The original pointing model description string was comma-separated outfile.write(new_model.description.replace(" ", ", ")) outfile.close() print("Saved %d-parameter pointing model to '%s'" % (len(new_model), opts.outfilebase + '.csv')) # Turn data recarray into list of dicts and add residuals to the mix extended_data = [] for n in range(len(data)): rec_dict = dict(list(zip(data.dtype.names, data[n]))) rec_dict['keep'] = int(keep[n]) rec_dict['old_residual_xel'] = rad2deg(old.residual_xel[n]) rec_dict['old_residual_el'] = rad2deg(old.residual_el[n]) rec_dict['new_residual_xel'] = rad2deg(new.residual_xel[n]) rec_dict['new_residual_el'] = rad2deg(new.residual_el[n]) extended_data.append(rec_dict) # Format the data similar to analyse_point_source_scans output CSV file, with four new columns at the end fields = '%(dataset)s, %(target)s, %(timestamp_ut)s, %(azimuth).7f, %(elevation).7f, ' \ '%(delta_azimuth).7f, %(delta_azimuth_std).7f, %(delta_elevation).7f, %(delta_elevation_std).7f, ' \ '%(data_unit)s, %(beam_height_I).7f, %(beam_height_I_std).7f, %(beam_width_I).7f, ' \ '%(beam_width_I_std).7f, %(baseline_height_I).7f, %(baseline_height_I_std).7f, %(refined_I).0f, ' \ '%(beam_height_HH).7f, %(beam_width_HH).7f, %(baseline_height_HH).7f, %(refined_HH).0f, ' \ '%(beam_height_VV).7f, %(beam_width_VV).7f, %(baseline_height_VV).7f, %(refined_VV).0f, ' \ '%(frequency).7f, %(flux).4f, %(temperature).2f, %(pressure).2f, %(humidity).2f, %(wind_speed).2f, ' \ '%(keep)d, %(old_residual_xel).7f, %(old_residual_el).7f, %(new_residual_xel).7f, %(new_residual_el).7f\n' field_names = [name.partition(')')[0] for name in fields[2:].split(', %(')] # Save residual data and flags to file outfile2 = file(opts.outfilebase + '_data.csv', 'w') outfile2.write('# antenna = %s\n' % antenna.description) outfile2.write(', '.join(field_names) + '\n') outfile2.writelines([fields % rec for rec in extended_data]) outfile2.close() # Pointing model report print('Generating report, this may take a few minutes...') nice_filename = os.path.splitext(os.path.basename(filename))[0]+ '_pointing_model' pp = PdfPages(nice_filename+'.pdf') pagetext = [] i = 0 tmpstr = "" linelength = 5 pagetext.append("List of targets used:") for tar in list(set(unique_targets)): if i % linelength == linelength-1 : pagetext.append(tmpstr) tmpstr = "" i = i + 1 tmpstr +='%s, '%(tar) pagetext.append(tmpstr) pagetext.append("Pointing metrics for fitted points. (N= %i Fitting Data Points) "%(np.sum(keep))) pagetext.append("New Pointing model:") pagetext.append(new_model.description.replace(" ", ", ")) pagetext.append("All sky RMS = %.3f' (robust %.3f') " % (new.sky_rms, new.robust_sky_rms)) fig = plt.figure(None,figsize = (10,16)) plt.figtext(0.1,0.1,'\n'.join(pagetext),fontsize=12) fig.savefig(pp,format='pdf') plt.close(fig) # List of colors used to represent different targets in scatter plots scatter_colors = ('b', 'r', 'g', 'k', 'c', 'm', 'y') target_colors = np.tile(scatter_colors, 1 + len(unique_targets) // len(scatter_colors))[:len(unique_targets)] # Quantity loosely related to the declination of the source north = (np.pi / 2. - el) / (np.pi / 2.) * np.cos(az) pseudo_dec = -np.ones(len(unique_targets)) for n, ind in enumerate(target_index): if north[n] > pseudo_dec[ind]: pseudo_dec[ind] = north[n] north_to_south = np.flipud(np.argsort(pseudo_dec)) target_colors = target_colors[north_to_south][target_index] for idx in np.unique(target_index): fig = plt.figure(1, figsize=(15, 10)) fig.clear() # Store highlighted target index on figure object fig.highlighted_target = idx # Axes to contain detail residual plots - initialise plots with old residuals ax = fig.add_axes([0.27, 0.74, 0.2, 0.2]) ax.axhline(0, color='k', zorder=0) plot_data_and_tooltip(ax, rad2deg(az), rad2deg(old.residual_xel) * 60.) ax.axis([-180., 180., -resid_lim, resid_lim]) ax.set_xticks([]) ax.yaxis.set_ticks_position('right') ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(arcmin_formatter)) ax.set_ylabel('Cross-EL offset') ax.set_title('RESIDUALS') ax = fig.add_axes([0.27, 0.54, 0.2, 0.2]) ax.axhline(0, color='k', zorder=0) plot_data_and_tooltip(ax, rad2deg(az), rad2deg(old.residual_el) * 60.) ax.axis([-180., 180., -resid_lim, resid_lim]) ax.set_xlabel('Azimuth (deg)') ax.yaxis.set_ticks_position('right') ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(arcmin_formatter)) ax.set_ylabel('EL offset') ax = fig.add_axes([0.27, 0.26, 0.2, 0.2]) ax.axhline(0, color='k', zorder=0) plot_data_and_tooltip(ax, rad2deg(el), rad2deg(old.residual_xel) * 60.) ax.axis([0., 90., -resid_lim, resid_lim]) ax.set_xticks([]) ax.yaxis.set_ticks_position('right') ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(arcmin_formatter)) ax.set_ylabel('Cross-EL offset') ax = fig.add_axes([0.27, 0.06, 0.2, 0.2]) ax.axhline(0, color='k', zorder=0) plot_data_and_tooltip(ax, rad2deg(el), rad2deg(old.residual_el) * 60.) ax.axis([0., 90., -resid_lim, resid_lim]) ax.set_xlabel('Elevation (deg)') ax.yaxis.set_ticks_position('right') ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(arcmin_formatter)) ax.set_ylabel('EL offset') # Axes to contain quiver plot - plot static measurement locations in ARC projection as a start ax = fig.add_axes([0.5, 0.43, 0.5, 0.5], projection='polar') plot_data_and_tooltip(ax, np.pi/2. - az, np.pi/2. - el) segms = quiver_segments(old.residual_az, old.residual_el, 0.) ax.quiv = mpl.collections.LineCollection(segms, color='0.3') ax.add_collection(ax.quiv) ax.set_xticks(deg2rad(np.arange(0., 360., 90.))) ax.set_xticklabels(['E', 'N', 'W', 'S']) ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(angle_formatter)) ax.set_ylim(0., np.pi / 2.) ax.set_yticks(deg2rad(np.arange(0., 90., 10.))) ax.set_yticklabels([]) # Axes to contain before/after residual plot ax = fig.add_axes([0.5, 0.135, 0.25, 0.25], projection='polar') ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(arcmin_formatter)) plot_data_and_tooltip(ax, np.arctan2(old.residual_el, old.residual_xel), old.abs_sky_error) ax.set_xticklabels([]) ax.set_title('OLD') fig.text(0.625, 0.09, "$\chi^2$ = %.1f" % (old.chi2,), ha='center', va='baseline') fig.text(0.625, 0.06, "all sky rms = %.3f' (robust %.3f')" % (old.sky_rms, old.robust_sky_rms), ha='center', va='baseline') old.metrics(target_index == fig.highlighted_target) fig.text(0.625, 0.03, "target sky rms = %.3f' (robust %.3f')" % (old.sky_rms, old.robust_sky_rms), ha='center', va='baseline', fontdict=dict(color=(0.25,0,0,1))) old.metrics(keep) ax = fig.add_axes([0.75, 0.135, 0.25, 0.25], projection='polar') ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(arcmin_formatter)) plot_data_and_tooltip(ax, np.arctan2(new.residual_el, new.residual_xel), new.abs_sky_error) ax.set_xticklabels([]) ax.set_title('NEW') fig.text(0.875, 0.09, "$\chi^2$ = %.1f" % (new.chi2,), ha='center', va='baseline') fig.text(0.875, 0.06, "all sky rms = %.3f' (robust %.3f')" % (new.sky_rms, new.robust_sky_rms), ha='center', va='baseline') new.metrics(target_index == fig.highlighted_target) fig.text(0.875, 0.03, "target sky rms = %.3f' (robust %.3f')" % (new.sky_rms, new.robust_sky_rms), ha='center', va='baseline', fontdict=dict(color=(0.25,0,0,1))) new.metrics(keep) param_button_color = ['0.65', '0.0'] param_button_weight = ['normal', 'bold'] # For display purposes, throw out unused parameters P2 and P10 display_params = list(range(num_params)) display_params.pop(9) display_params.pop(1) def setup_param_button(p): """Set up individual parameter toggle button.""" param = display_params[p] param_button = mpl.widgets.Button(fig.add_axes([0.09, 0.94 - (0.85 + p * 0.9) / len(display_params), 0.03, 0.85 / len(display_params)]), 'P%d' % (param + 1,)) fig.text(0.19, 0.94 - (0.5 * 0.85 + p * 0.9) / len(display_params), '', ha='right', va='center') fig.text(0.24, 0.94 - (0.5 * 0.85 + p * 0.9) / len(display_params), '', ha='right', va='center') state = enabled_params[param] param_button.label.set_color(param_button_color[state]) param_button.label.set_weight(param_button_weight[state]) def toggle_param_callback(event): state = not enabled_params[param] enabled_params[param] = state param_button.label.set_color(param_button_color[state]) param_button.label.set_weight(param_button_weight[state]) save_button.color = (0.85, 0, 0) save_button.hovercolor = (0.95, 0, 0) update(fig) param_button.on_clicked(toggle_param_callback) return param_button # This is to stop the gc from deleting the data param_buttons = [setup_param_button(p) for p in range(len(display_params))] # Add old pointing model and labels list_o_names = 'Ant:%s , Datasets:'%(antenna.name) + ' ,'.join(np.unique(data['dataset']).tolist() ) fig.text(0.905, 0.98,list_o_names, horizontalalignment='right',fontsize=10) fig.text(0.053, 0.95, 'OLD', ha='center', va='bottom', size='large') fig.text(0.105, 0.95, 'MODEL', ha='center', va='bottom', size='large') fig.text(0.16, 0.95, 'NEW', ha='center', va='bottom', size='large') fig.text(0.225, 0.95, 'STD', ha='center', va='bottom', size='large') for p, param in enumerate(display_params): param_str = param_to_str(old_model, param) if list(old_model.values())[param] else '' fig.text(0.085, 0.94 - (0.5 * 0.85 + p * 0.9) / len(display_params), param_str, ha='right', va='center') # Create target selector buttons and related text (title + target string) fig.text(0.565, 0.95, 'TARGET', ha='center', va='bottom', size='large') fig.text(0.565, 0.89, unique_targets[fig.highlighted_target], ha='center', va='top', fontdict=dict(color=(0.25,0,0,1))) quiver_scale = 0.1 * 10 * np.pi / 6 / deg2rad(old.robust_sky_rms / 60.) fig.axes[4].quiv.set_segments(quiver_segments(new.residual_az, new.residual_el, quiver_scale)) # Target state: 0 = flagged, 1 = unflagged, 2 = highlighted target_state = keep * ((target_index == fig.highlighted_target) + 1) # Specify colours of flagged, unflagged and highlighted dots, respectively, as RGBA tuples dot_colors = np.choose(target_state, np.atleast_3d(np.vstack([(1,1,1,1), (0,0,1,1), (1,0,0,1)]))).T for ax in fig.axes[:7]: ax.dots.set_facecolors(dot_colors) fig.texts[-1].set_text(unique_targets[fig.highlighted_target]) for p, param in enumerate(display_params): fig.texts[2*p + 6].set_text(param_to_str(new_model, param) if enabled_params[param] else '') # HACK to convert sigmas to arcminutes, but not for P9 and P12 (which are scale factors) # This functionality should really reside inside the PointingModel class std_param = rad2deg(sigma_params[param]) * 60. if param not in [8, 11] else sigma_params[param] std_param_str = ("%.2f'" % std_param) if param not in [8, 11] else ("%.0e" % std_param) fig.texts[2*p + 7].set_text(std_param_str if enabled_params[param] and opts.use_stats else '') # Turn parameter string bold if it changed significantly from old value if np.abs(params[param] - list(old_model.values())[param]) > 3.0 * sigma_params[param]: fig.texts[2*p + 6].set_weight('bold') fig.texts[2*p + 7].set_weight('bold') else: fig.texts[2*p + 6].set_weight('normal') fig.texts[2*p + 7].set_weight('normal') daz_az, del_az, daz_el, del_el, quiver, before, after = fig.axes[:7] # Update residual plots daz_az.dots.set_offsets(np.c_[rad2deg(az), rad2deg(new.residual_xel) * 60.]) del_az.dots.set_offsets(np.c_[rad2deg(az), rad2deg(new.residual_el) * 60.]) daz_el.dots.set_offsets(np.c_[rad2deg(el), rad2deg(new.residual_xel) * 60.]) del_el.dots.set_offsets(np.c_[rad2deg(el), rad2deg(new.residual_el) * 60.]) after.dots.set_offsets(np.c_[np.arctan2(new.residual_el, new.residual_xel), new.abs_sky_error]) resid_lim = 1.2 * max(new.abs_sky_error.max(), old.abs_sky_error.max()) daz_az.set_ylim(-resid_lim, resid_lim) del_az.set_ylim(-resid_lim, resid_lim) daz_el.set_ylim(-resid_lim, resid_lim) del_el.set_ylim(-resid_lim, resid_lim) before.set_ylim(0, resid_lim) after.set_ylim(0, resid_lim) fig.savefig(pp,format='pdf') # plt.close(fig) pp.close()
def angle_formatter(x, pos=None): return theta_formatter(wrap_angle(np.pi / 2.0 - x), pos)
# Interpret first non-comment line as header fields = data[0].tolist() # By default, all fields are assumed to contain floats formats = np.tile(np.float, len(fields)) # The string_fields are assumed to contain strings - use data's string type, as it is of sufficient length formats[[fields.index(name) for name in string_fields if name in fields]] = data.dtype # Convert to heterogeneous record array data = np.rec.fromarrays(data[1:].transpose(), dtype=zip(fields, formats)) # Load antenna description string from first line of file and construct antenna object from it antenna = katpoint.Antenna(file(filename).readline().strip().partition('=')[2]) # Use the pointing model contained in antenna object as the old model (if not overridden by file) # If the antenna has no model specified, a default null model will be used if old_model is None: old_model = antenna.pointing_model # Obtain desired fields and convert to radians az, el = wrap_angle(deg2rad(data['azimuth'])), deg2rad(data['elevation']) measured_delta_az, measured_delta_el = deg2rad(data['delta_azimuth']), deg2rad(data['delta_elevation']) # Uncertainties are optional min_std = deg2rad(opts.min_rms / 60. / np.sqrt(2)) std_delta_az = np.clip(deg2rad(data['delta_azimuth_std']), min_std, np.inf) \ if 'delta_azimuth_std' in data.dtype.fields and opts.use_stats else np.tile(min_std, len(az)) std_delta_el = np.clip(deg2rad(data['delta_elevation_std']), min_std, np.inf) \ if 'delta_elevation_std' in data.dtype.fields and opts.use_stats else np.tile(min_std, len(el)) targets = data['target'] keep = data['keep'].astype(np.bool) if 'keep' in data.dtype.fields else np.tile(True, len(targets)) # Initialise new pointing model and set default enabled parameters new_model = katpoint.PointingModel() num_params = len(new_model) default_enabled = np.nonzero(old_model.values())[0] # If the old model is empty / null, select the most basic set of parameters for starters
# By default, all fields are assumed to contain floats formats = np.tile(np.float, len(fields)) # The string_fields are assumed to contain strings - use data's string type, as it is of sufficient length formats[[fields.index(name) for name in string_fields if name in fields]] = data.dtype # Convert to heterogeneous record array data = np.rec.fromarrays(data[1:].transpose(), dtype=list(zip(fields, formats))) # Load antenna description string from first line of file and construct antenna object from it antenna = katpoint.Antenna(open(filename).readline().strip().partition('=')[2]) # Use the pointing model contained in antenna object as the old model (if not overridden by file) # If the antenna has no model specified, a default null model will be used if old_model is None: old_model = antenna.pointing_model # Obtain desired fields and convert to radians az, el = wrap_angle(deg2rad(data['azimuth'])), deg2rad(data['elevation']) measured_delta_az, measured_delta_el = deg2rad(data['delta_azimuth']), deg2rad( data['delta_elevation']) # Uncertainties are optional min_std = deg2rad(opts.min_rms / 60. / np.sqrt(2)) std_delta_az = np.clip(deg2rad(data['delta_azimuth_std']), min_std, np.inf) \ if 'delta_azimuth_std' in data.dtype.fields and opts.use_stats else np.tile(min_std, len(az)) std_delta_el = np.clip(deg2rad(data['delta_elevation_std']), min_std, np.inf) \ if 'delta_elevation_std' in data.dtype.fields and opts.use_stats else np.tile(min_std, len(el)) targets = data['target'] keep = data['keep'].astype( np.bool) if 'keep' in data.dtype.fields else np.tile(True, len(targets)) # Initialise new pointing model and set default enabled parameters new_model = katpoint.PointingModel() num_params = len(new_model)