def plot_time_table(t_ccd_table): fig = plt.figure(figsize=(6, 5)) day_secs = DateTime(t_ccd_table['day']).secs nom_t_ccd = t_ccd_table['nom_t_ccd'] best_t_ccd = t_ccd_table['best_t_ccd'] plot_cxctime(day_secs, nom_t_ccd, 'r') plot_cxctime(day_secs, best_t_ccd, 'b') plot_cxctime(day_secs, nom_t_ccd, 'r.', label='nom roll t ccd') plot_cxctime(day_secs, best_t_ccd, 'b.', label='best roll t ccd') plt.grid() plt.ylim(ymin=COLD_T_CCD, ymax=WARM_T_CCD + 3.0) plt.xlim(xmin=cxctime2plotdate([day_secs[0]]), xmax=cxctime2plotdate([day_secs[-1]])) plt.legend(loc='upper left', title="", numpoints=1, handlelength=.5) plt.ylabel('Max ACA CCD Temp (degC)') plt.tight_layout() return fig
def plot_cxctime(times, y, fmt='-b', fig=None, ax=None, yerr=None, xerr=None, tz=None, state_codes=None, interactive=True, **kwargs): """Make a date plot where the X-axis values are in CXC time. If no ``fig`` value is supplied then the current figure will be used (and created automatically if needed). If yerr or xerr is supplied, ``errorbar()`` will be called and any additional keyword arguments will be passed to it. Otherwise any additional keyword arguments (e.g. ``fmt='b-'``) are passed through to the ``plot()`` function. Also see ``errorbar()`` for an explanation of the possible forms of *yerr*/*xerr*. If the ``state_codes`` keyword argument is provided then the y-axis ticks and tick labels will be set accordingly. The ``state_codes`` value must be a list of (raw_count, state_code) tuples, and is normally set to ``msid.state_codes`` for an MSID object from fetch(). If the ``interactive`` keyword is True (default) then the plot will be redrawn at the end and a GUI callback will be created which allows for on-the-fly update of the date tick labels when panning and zooming interactively. Set this to False to improve the speed when making several plots. This will likely require issuing a plt.draw() or fig.canvas.draw() command at the end. :param times: CXC time values for x-axis (date) :param y: y values :param fmt: plot format (default = '-b') :param fig: pyplot figure object (optional) :param yerr: error on y values, may be [ scalar | N, Nx1, or 2xN array-like ] :param xerr: error on x values in units of DAYS (may be [ scalar | N, Nx1, or 2xN array-like ] ) :param tz: timezone string :param state_codes: list of (raw_count, state_code) tuples :param interactive: use plot interactively (default=True, faster if False) :param **kwargs: keyword args passed through to ``plot_date()`` or ``errorbar()`` :rtype: ticklocs, fig, ax = tick locations, figure, and axes object. """ if fig is None: fig = matplotlib.pyplot.gcf() if ax is None: ax = fig.gca() if yerr is not None or xerr is not None: ax.errorbar(cxctime2plotdate(times), y, yerr=yerr, xerr=xerr, fmt=fmt, **kwargs) ax.xaxis_date(tz) else: ax.plot_date(cxctime2plotdate(times), y, fmt=fmt, **kwargs) ticklocs = Ska.Matplotlib.set_time_ticks(ax) fig.autofmt_xdate() if state_codes is not None: counts, codes = zip(*state_codes) ax.yaxis.set_major_locator(Ska.Matplotlib.FixedLocator(counts)) ax.yaxis.set_major_formatter(Ska.Matplotlib.FixedFormatter(codes)) # If plotting interactively then show the figure and enable interactive resizing if interactive and hasattr(fig, 'show'): # fig.canvas.draw() ax.callbacks.connect('xlim_changed', Ska.Matplotlib.remake_ticks) return ticklocs, fig, ax
def paint_perigee(perigee_passages, states, plots, msid): """ This function draws vertical dahsed lines for EEF, Perigee and XEF events in the load.EEF and XEF lines are black; Perigee is red. You supply the list of perigee passage events which are: Radzone Start/Stop time Perigee Passage time The states you created in main The dictionary of plots you created The MSID (in this case FP_TEMP) used to access the dictionary """ # # Now plot any perigee passages that occur between xmin and xmax for eachpassage in perigee_passages: # The index [1] item is always the Perigee Passage time. Draw that line in red # If this line is between tstart and tstop then it needs to be drawn # on the plot. otherwise ignore if states['tstop'][-1] >= DateTime(eachpassage[0]).secs >= states['tstart'][0]: # Have to convert this time into the new x axis time scale necessitated by SKA xpos = cxctime2plotdate([DateTime(eachpassage[0]).secs]) ymin, ymax = plots[msid]['ax'].get_ylim() # now plot the line. plots[msid]['ax'].vlines(xpos, ymin, ymax, linestyle=':', color='red', linewidth=2.0) # Plot the perigee passage time so long as it was specified in the CTI_report file if eachpassage[1] != "Not-within-load": perigee_time = cxctime2plotdate([DateTime(eachpassage[1]).secs]) plots[msid]['ax'].vlines(perigee_time, ymin, ymax, linestyle=':', color='black', linewidth=2.0)
def plot_two( fig_id, x, y, x2, y2, linestyle='-', linestyle2='-', color='blue', color2='magenta', ylim=None, ylim2=None, xlabel='', ylabel='', ylabel2='', title='', figsize=(7, 3.5), markersize2=None, marker2=None, ): """Plot two quantities with a date x-axis""" xt = cxctime2plotdate(x) fig = plt.figure(fig_id, figsize=figsize) fig.clf() ax = fig.add_subplot(1, 1, 1) ax.plot_date(xt, y, fmt='-', linestyle=linestyle, color=color) ax.set_xlim(min(xt), max(xt)) if ylim: ax.set_ylim(*ylim) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_title(title) ax.grid() ax2 = ax.twinx() xt2 = cxctime2plotdate(x2) ax2.plot_date(xt2, y2, fmt='-', linestyle=linestyle2, color=color2, markersize=markersize2, marker=marker2) ax2.set_xlim(min(xt), max(xt)) if ylim2: ax2.set_ylim(*ylim2) ax2.set_ylabel(ylabel2, color=color2) ax2.xaxis.set_visible(False) set_time_ticks(ax) [label.set_rotation(30) for label in ax.xaxis.get_ticklabels()] [label.set_color(color2) for label in ax2.yaxis.get_ticklabels()] fig.subplots_adjust(bottom=0.22) return {'fig': fig, 'ax': ax, 'ax2': ax2}
def plot_two( fig_id, x, y, x2, y2, linestyle="-", linestyle2="-", color="blue", color2="magenta", ylim=None, ylim2=None, xlabel="", ylabel="", ylabel2="", title="", figsize=(7, 3.5), markersize2=None, marker2=None, ): """Plot two quantities with a date x-axis""" xt = cxctime2plotdate(x) fig = plt.figure(fig_id, figsize=figsize) fig.clf() ax = fig.add_subplot(1, 1, 1) ax.plot_date(xt, y, fmt="-", linestyle=linestyle, color=color) ax.set_xlim(min(xt), max(xt)) if ylim: ax.set_ylim(*ylim) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_title(title) ax.grid() ax2 = ax.twinx() xt2 = cxctime2plotdate(x2) ax2.plot_date(xt2, y2, fmt="-", linestyle=linestyle2, color=color2, markersize=markersize2, marker=marker2) ax2.set_xlim(min(xt), max(xt)) if ylim2: ax2.set_ylim(*ylim2) ax2.set_ylabel(ylabel2, color=color2) ax2.xaxis.set_visible(False) set_time_ticks(ax) [label.set_rotation(30) for label in ax.xaxis.get_ticklabels()] [label.set_color(color2) for label in ax2.yaxis.get_ticklabels()] fig.subplots_adjust(bottom=0.22) return {"fig": fig, "ax": ax, "ax2": ax2}
def paint_perigee(perigee_passages, states, plots): """ This function draws vertical dashed lines for EEF, Perigee and XEF events in the load.EEF and XEF lines are black; Perigee is red. You supply the list of perigee passage events which are: Radzone Start/Stop time Perigee Passage time The states you created in main The dictionary of plots you created The MSID (in this case FP_TEMP) used to access the dictionary """ # # Now plot any perigee passages that occur between xmin and xmax from cxotime import CxoTime for plot in plots.values(): for eachpassage in perigee_passages: # The index [1] item is always the Perigee Passage time. Draw that # line in red If this line is between tstart and tstop then it # needs to be drawn on the plot. otherwise ignore if states['tstop'][-1] >= CxoTime( eachpassage[0]).secs >= states['tstart'][0]: # Have to convert this time into the new x axis time scale # necessitated by SKA xpos = cxctime2plotdate([CxoTime(eachpassage[0]).secs]) ymin, ymax = plot['ax'].get_ylim() # now plot the line. plot['ax'].vlines(xpos, ymin, ymax, linestyle=':', color='red', linewidth=2.0) # Plot the perigee passage time so long as it was specified in # the CTI_report file if eachpassage[1] != "Not-within-load": perigee_time = cxctime2plotdate( [CxoTime(eachpassage[1]).secs]) plot['ax'].vlines(perigee_time, ymin, ymax, linestyle=':', color='black', linewidth=2.0)
def __init__(self, model): self.model = model self.n_mvals = 0 self.predict = False # Predict values for this model component self.pars = [] self.data = None self.data_times = None self.model_plotdate = cxctime2plotdate(self.model.times)
def update(self, *args): pd0, pd1, pd_center = cxctime2plotdate(times[np.array(get_index_lims())]) if not hasattr(self, 'patch'): self.patch = matplotlib.patches.Rectangle( (pd0, 0), width=(pd1-pd0), height=1.6e8, zorder=-100, facecolor='y', alpha=0.5) self.ax.add_patch(self.patch) else: self.patch.set_xy((pd0, 0)) self.patch.set_width(pd1-pd0)
def __init__(self, model): # This class overrides __setattr__ with a method that requires # the `pars` and `pars_dict` attrs to be visible. So do this # with the super (object) method right away. super(ModelComponent, self).__setattr__('pars', []) super(ModelComponent, self).__setattr__('pars_dict', {}) self.model = model self.n_mvals = 0 self.predict = False # Predict values for this model component self.data = None self.data_times = None self.model_plotdate = cxctime2plotdate(self.model.times)
def _plot_bands(self, tbegin, tend, plot, events, color, alpha=1.0): tc_start = list(self.events[events[0]]["times"]) tc_end = list(self.events[events[1]]["times"]) if tc_end[0] < tc_start[0]: tc_start.insert(0, self.first_time) if tc_start[-1] > tc_end[-1]: tc_end.append(self.last_time) assert len(tc_start) == len(tc_end) tc_start = date2secs(tc_start) tc_end = date2secs(tc_end) ybot, ytop = plot.ax.get_ylim() t = np.linspace(tbegin, tend, 500) tplot = cxctime2plotdate(t) for tcs, tce in zip(tc_start, tc_end): in_evt = (t >= tcs) & (t <= tce) plot.ax.fill_between(tplot, ybot, ytop, where=in_evt, color=color, alpha=alpha)
def test_check_many_sizes(): """Run through a multiplicative series of x-axis lengths and visually confirm that chosen axes are OK.""" plt.ion() dt = 6 while True: print dt t0 = np.random.uniform(3e7 * 10) times = np.linspace(t0, t0 + dt, 20) x = cxctime2plotdate(times) y = np.random.normal(size=len(times)) fig = plt.figure(1) fig.clf() plt1 = fig.add_subplot(1, 1, 1) plt1.plot_date(x, y, fmt="b-") set_time_ticks(plt1) fig.autofmt_xdate() plt.savefig("test-{:09d}.png".format(dt)) dt *= 2 if dt > 1e9: break
def make_prediction_plots(self, outdir, states, times, temps, load_start): """ Make plots of the thermal prediction as well as associated commanded states. Parameters ---------- outdir : string The path to the output directory. states : NumPy record array Commanded states times : NumPy array Times in seconds from the beginning of the mission for the temperature arrays temps : dict of NumPy arrays Dictionary of temperature arrays load_start : float The start time of the load in seconds from the beginning of the mission. """ plots = {} # Start time of loads being reviewed expressed in units for plotdate() load_start = cxctime2plotdate([load_start])[0] # Value for left side of plots plot_start = max(load_start-2.0, cxctime2plotdate([times[0]])[0]) w1 = None mylog.info('Making temperature prediction plots') plots[self.name] = plot_two(fig_id=1, x=times, y=temps[self.name], x2=pointpair(states['tstart'], states['tstop']), y2=pointpair(states['pitch']), title=self.msid.upper(), xmin=plot_start, xlabel='Date', ylabel='Temperature (C)', ylabel2='Pitch (deg)', ylim2=(40, 180), figsize=(8.0, 4.0), width=w1, load_start=load_start) # Add horizontal lines for the planning and caution limits ymin, ymax = plots[self.name]['ax'].get_ylim() ymax = max(self.yellow_hi+1, ymax) plots[self.name]['ax'].axhline(self.yellow_hi, linestyle='-', color='y', linewidth=2.0) plots[self.name]['ax'].axhline(self.plan_limit_hi, linestyle='--', color='y', linewidth=2.0) if self.flag_cold_viols: ymin = min(self.yellow_lo-1, ymin) plots[self.name]['ax'].axhline(self.yellow_lo, linestyle='-', color='y', linewidth=2.0) plots[self.name]['ax'].axhline(self.plan_limit_lo, linestyle='--', color='y', linewidth=2.0) plots[self.name]['ax'].set_ylim(ymin, ymax) filename = self.msid.lower() + '.png' outfile = os.path.join(outdir, filename) mylog.info('Writing plot file %s' % outfile) plots[self.name]['fig'].savefig(outfile) plots[self.name]['filename'] = filename # The next line is to ensure that the width of the axes # of all the weekly prediction plots are the same. w1, _ = plots[self.name]['fig'].get_size_inches() self._make_state_plots(plots, 1, w1, plot_start, outdir, states, load_start) plots['default'] = plots[self.name] return plots
def model_plotdate(self): if not hasattr(self, '_model_plotdate'): self._model_plotdate = cxctime2plotdate(self.model.times) return self._model_plotdate
def draw_obsids(extract_and_filter, obs_with_sensitivity, nopref_array, plots, msid, ypos, endcapstart, endcapstop, textypos, fontsize, plot_start): """ This functiion draws visual indicators across the top of the plot showing which observations are ACIS; whether they are ACIS-I (red) or ACIS-S (green) when they start and stop, and whether or not any observation is sensitive to the focal plane temperature. The list of observations sensitive to the focal plane is found by reading the fp_sensitive.dat file that is located in each LR directory and is created by the LR script. No CTI measurements are indicated - only science runs. The caller supplies: Options from the Command line supplied by the user at runtime The instance of the ObsidFindFilter() class created nopref rec array The plot dictionary The MSID used to index into the plot dictinary (superfluous but required) The position on the Y axis you'd like these indicators to appear The Y position of the bottom of the end caps The Y position of the top of the end caps The starting position of the OBSID number text The font size The left time of the plot in plot_date units """ # Now run through the observation list attribute of the ObsidFindFilter class for eachobservation in obs_with_sensitivity: # extract the obsid obsid = str(extract_and_filter.get_obsid(eachobservation)) # Color all ACIS-S observations green; all ACIS-I # observations red if eachobservation[extract_and_filter.in_focal_plane] == "ACIS-I": color = 'red' else: color = 'green' # Add the sensitivity text if the observation was found to be FP TEMP # sensitive # # If the observation is FP sensitive in the first place............ if eachobservation[extract_and_filter.is_fp_sensitive]: # extract the obsid for convenience this_obsid = extract_and_filter.get_obsid(eachobservation) # But if it's also in the nopref list AND the upcased CandS_status entry is "NO PREF" where_words = np.where(nopref_array['obsid'] == str(this_obsid)) if str(this_obsid) in nopref_array['obsid'] and \ nopref_array['CandS_status'][where_words[0][0]].upper()[:7] == 'NO_PREF': color = 'purple' obsid = obsid + ' * NO PREF *' else: obsid = obsid + ' * FP SENS *' # Convert the start and stop times into the Ska-required format obs_start = cxctime2plotdate([extract_and_filter.get_tstart(eachobservation)]) obs_stop = cxctime2plotdate([extract_and_filter.get_tstop(eachobservation)]) if eachobservation[extract_and_filter.in_focal_plane].startswith("ACIS-"): # For each ACIS Obsid, draw a horizontal line to show # its start and stop plots[msid]['ax'].hlines(ypos, obs_start, obs_stop, linestyle='-', color=color, linewidth=2.0) # Plot vertical end caps for each obsid to visually show start/stop plots[msid]['ax'].vlines(obs_start, endcapstart, endcapstop, color=color, linewidth=2.0) plots[msid]['ax'].vlines(obs_stop, endcapstart, endcapstop, color=color, linewidth=2.0) # Now print the obsid in the middle of the time span, # above the line, and rotate 90 degrees. obs_time = obs_start + (obs_stop - obs_start)/2 if obs_time > plot_start: # Now plot the obsid. plots[msid]['ax'].text(obs_time, textypos, obsid, color = color, va='bottom', ma='left', rotation = 90, fontsize = fontsize)
def make_prediction_plots(self, outdir, states, times, temps, tstart): """ Make output plots. :param outdir: the directory to which the products are written :param states: commanded states :param times: time stamps (sec) for temperature arrays :param temps: dict of temperatures :param tstart: load start time :rtype: dict of review information including plot file names This function assumes that ACIS Ops LR has been run and that the directory is populated with """ # Gather perigee passages self._gather_perigee(times[0], tstart) # Next we need to find all the ACIS-S observations within the start/stop # times so that we can paint those on the plots as well. We will get # those from the commanded states data structure called "states" # # Create an instance of the ObsidFindFilter class. This class provides # methods to extract obsid intervals from the commanded states based # upon ACIS definitions and considerations. It also provides # various methods to filter the interval set based upon pitch range, # number of ccd's, filter out CTI observations, and a range of exposure # times. extract_and_filter = ObsidFindFilter() # extract the OBSID's from the commanded states. NOTE: this contains all # observations including CTI runs and HRC observations observation_intervals = extract_and_filter.find_obsid_intervals(states, None) # Filter out any HRC science observations BUT keep ACIS CTI observations acis_and_cti_obs = extract_and_filter.hrc_science_obs_filter(observation_intervals) # Ok so now you have all the ACIS observations collected. Also, # they have been identified by ObsidFindFilter as to who is in the focal plane. # Some apps, like this one, care about FP_TEMP sensitivity. Some do not. # Since we do, then checking that and assigning a sensitivity must be done # # Open the sensitive observation list file, which is found in the LR # directory, # read each line, extract the OBSID and add that to a list. sensefile = open(os.path.join(self.bsdir, 'fp_sensitive.txt'), 'r') # The list_of_sensitive_obs is the list of all FP TEMP sensitive # observations extracted from the file in the load review directory list_of_sensitive_obs = [] # Get the list of FP_TEMP sensitive observations for eachline in sensefile.readlines()[1:]: # Extract the OBSID from each line; the obsid is in the second # column of this line. Append it to the list of FP_TEMP sensitive # observations # # NOTE: The obsid here is a STRING list_of_sensitive_obs.append(eachline.split()[1]) # Done with the file - close it sensefile.close() # Now that you have the latest list of temperature sensitive OBSID's, # run through each observation and append either "*FP SENS*" or # "NOT FP SENS" to the end of each observation. # Now run through the observation list attribute of the ObsidFindFilter class for eachobservation in acis_and_cti_obs: # Pull the obsid from the observation and turn it into a string obsid = str(extract_and_filter.get_obsid(eachobservation)) # See if it's in the sensitive list. If so, indicate whether or # not this observation is FP Senstive in the new list. This will be # used later in make_prediction_viols to catch violations. if obsid in list_of_sensitive_obs: eachobservation.append(True) else: eachobservation.append(False) self.obs_with_sensitivity.append(eachobservation) # create an empty dictionary called plots to contain the returned # figures, axes 1 and axes 2 of the plot_two call plots = {} # Start time of loads being reviewed expressed in units for plotdate() load_start = cxctime2plotdate([tstart])[0] # Value for left side of plots plot_start = max(load_start-2.0, cxctime2plotdate([times[0]])[0]) w1 = None # Make plots of FPTEMP and pitch vs time, looping over # three different temperature ranges ylim = [(-120, -90), (-120, -119), (-120.0, -111.5)] ypos = [-110.0, -119.35, -116] capwidth = [2.0, 0.1, 0.4] textypos = [-108.0, -119.3, -115.7] fontsize = [12, 9, 9] for i in range(3): name = "%s_%d" % (self.name, i+1) plots[name] = plot_two(fig_id=i+1, x=times, y=temps[self.name], x2=pointpair(states['tstart'], states['tstop']), y2=pointpair(states['pitch']), title=self.msid.upper() + " (ACIS-I obs. in red; ACIS-S in green)", xlabel='Date', ylabel='Temperature (C)', ylabel2='Pitch (deg)', xmin=plot_start, ylim=ylim[i], ylim2=(40, 180), figsize=(12, 6), width=w1, load_start=load_start) # Draw a horizontal line indicating the FP Sensitive Observation Cut off plots[name]['ax'].axhline(self.fp_sens_limit, linestyle='--', color='red', linewidth=2.0) # Draw a horizontal line showing the ACIS-I -114 deg. C cutoff plots[name]['ax'].axhline(self.acis_i_limit, linestyle='--', color='purple', linewidth=1.0) # Draw a horizontal line showing the ACIS-S -112 deg. C cutoff plots[name]['ax'].axhline(self.acis_s_limit, linestyle='--', color='blue', linewidth=1.0) # Get the width of this plot to make the widths of all the # prediction plots the same if i == 0: w1, _ = plots[name]['fig'].get_size_inches() # Now plot any perigee passages that occur between xmin and xmax # for eachpassage in perigee_passages: paint_perigee(self.perigee_passages, states, plots, name) # Now draw horizontal lines on the plot running from start to stop # and label them with the Obsid draw_obsids(extract_and_filter, self.obs_with_sensitivity, self.nopref_array, plots, name, ypos[i], ypos[i]-0.5*capwidth[i], ypos[i]+0.5*capwidth[i], textypos[i], fontsize[i], plot_start) # Build the file name and output the plot to a file filename = self.msid.lower() + 'M%dtoM%d.png' % (-ylim[i][0], -ylim[i][1]) outfile = os.path.join(outdir, filename) mylog.info('Writing plot file %s' % outfile) plots[name]['fig'].savefig(outfile) plots[name]['filename'] = filename self._make_state_plots(plots, 3, w1, plot_start, outdir, states, load_start, figsize=(12, 6)) return plots
import numpy as np import matplotlib.pyplot as plt from Ska.Matplotlib import cxctime2plotdate dac_vals = np.array([]) dac_times = np.array([]) from Chandra.Time import DateTime for year in range(2002, 2014): print year y_dac_vals = np.load('{}_vals.npy'.format(year)) y_dac_mask = np.load('{}_mask.npy'.format(year)) dac_vals = np.append(dac_vals, y_dac_vals[~y_dac_mask]) del y_dac_vals y_dac_times = cxctime2plotdate(np.load('{}_times.npy'.format(year))) dac_times = np.append(dac_times, y_dac_times[~y_dac_mask]) del y_dac_times del y_dac_mask plt.figure(figsize=(6,4)) plt.plot_date(dac_times, dac_vals, 'b.', markersize=1) plt.ylim(230, 530) plt.xlabel('Year') plt.ylabel('DAC level') plt.setp(plt.gca().xaxis.get_majorticklabels(), rotation=30) plt.tight_layout() plt.grid() plt.savefig('dac_level_over_time.png')
def draw_obsids(extract_and_filter, obs_with_sensitivity, plots, msid, ypos, endcapstart, endcapstop, textypos, fontsize, plot_start): """ This function draws visual indicators across the top of the plot showing which observations are ACIS; whether they are ACIS-I (red) or ACIS-S (green) when they start and stop, and whether or not any observation is sensitive to the focal plane temperature. The list of observations sensitive to the focal plane is found by reading the fp_sensitive.dat file that is located in each LR directory and is created by the LR script. No CTI measurements are indicated - only science runs. The caller supplies: Options from the Command line supplied by the user at runtime The instance of the ObsidFindFilter() class created The plot dictionary The MSID used to index into the plot dictionary (superfluous but required) The position on the Y axis you'd like these indicators to appear The Y position of the bottom of the end caps The Y position of the top of the end caps The starting position of the OBSID number text The font size The left time of the plot in plot_date units """ # Now run through the observation list attribute of the ObsidFindFilter class for eachobservation in obs_with_sensitivity: # extract the obsid obsid = str(extract_and_filter.get_obsid(eachobservation)) # Color all ACIS-S observations green; all ACIS-I # observations red if eachobservation[extract_and_filter.in_focal_plane] == "ACIS-I": color = 'red' else: color = 'green' # Add the sensitivity text if the observation was found to be FP TEMP # sensitive # # If the observation is FP sensitive in the first place............ if eachobservation[extract_and_filter.is_fp_sensitive]: obsid = obsid + ' * FP SENS *' # Convert the start and stop times into the Ska-required format obs_start = cxctime2plotdate( [extract_and_filter.get_tstart(eachobservation)]) obs_stop = cxctime2plotdate( [extract_and_filter.get_tstop(eachobservation)]) if eachobservation[extract_and_filter.in_focal_plane].startswith( "ACIS-"): # For each ACIS Obsid, draw a horizontal line to show # its start and stop plots[msid]['ax'].hlines(ypos, obs_start, obs_stop, linestyle='-', color=color, linewidth=2.0) # Plot vertical end caps for each obsid to visually show start/stop plots[msid]['ax'].vlines(obs_start, endcapstart, endcapstop, color=color, linewidth=2.0) plots[msid]['ax'].vlines(obs_stop, endcapstart, endcapstop, color=color, linewidth=2.0) # Now print the obsid in the middle of the time span, # above the line, and rotate 90 degrees. obs_time = obs_start + (obs_stop - obs_start) / 2 if obs_time > plot_start: # Now plot the obsid. plots[msid]['ax'].text(obs_time, textypos, obsid, color=color, va='bottom', ma='left', rotation=90, fontsize=fontsize)
def make_prediction_plots(self, outdir, states, temps, tstart): """ Make output plots. :param outdir: the directory to which the products are written :param states: commanded states :param times: time stamps (sec) for temperature arrays :param temps: dict of temperatures :param tstart: load start time :rtype: dict of review information including plot file names This function assumes that ACIS Ops LR has been run and that the directory is populated with """ times = self.predict_model.times # Gather perigee passages self._gather_perigee(times[0], tstart) # Next we need to find all the ACIS-S observations within the start/stop # times so that we can paint those on the plots as well. We will get # those from the commanded states data structure called "states" # # Create an instance of the ObsidFindFilter class. This class provides # methods to extract obsid intervals from the commanded states based # upon ACIS definitions and considerations. It also provides # various methods to filter the interval set based upon pitch range, # number of ccd's, filter out CTI observations, and a range of exposure # times. extract_and_filter = ObsidFindFilter() # extract the OBSID's from the commanded states. NOTE: this contains all # observations including CTI runs and HRC observations observation_intervals = extract_and_filter.find_obsid_intervals( states, None) # Filter out any HRC science observations BUT keep ACIS CTI observations acis_and_cti_obs = extract_and_filter.hrc_science_obs_filter( observation_intervals) # Ok so now you have all the ACIS observations collected. Also, # they have been identified by ObsidFindFilter as to who is in the focal plane. # Some apps, like this one, care about FP_TEMP sensitivity. Some do not. # Since we do, then checking that and assigning a sensitivity must be done # # Open the sensitive observation list file, which is found in the LR # directory, # read each line, extract the OBSID and add that to a list. sensefile = open(os.path.join(self.bsdir, 'fp_sensitive.txt'), 'r') # The list_of_sensitive_obs is the list of all FP TEMP sensitive # observations extracted from the file in the load review directory list_of_sensitive_obs = [] # Get the list of FP_TEMP sensitive observations for eachline in sensefile.readlines()[1:]: # Extract the OBSID from each line; the obsid is in the second # column of this line. Append it to the list of FP_TEMP sensitive # observations # # NOTE: The obsid here is a STRING list_of_sensitive_obs.append(eachline.split()[1]) # Done with the file - close it sensefile.close() # Now that you have the latest list of temperature sensitive OBSID's, # run through each observation and append either "*FP SENS*" or # "NOT FP SENS" to the end of each observation. # Now run through the observation list attribute of the ObsidFindFilter class for eachobservation in acis_and_cti_obs: # Pull the obsid from the observation and turn it into a string obsid = str(extract_and_filter.get_obsid(eachobservation)) # See if it's in the sensitive list. If so, indicate whether or # not this observation is FP Senstive in the new list. This will be # used later in make_prediction_viols to catch violations. if obsid in list_of_sensitive_obs: eachobservation.append(True) else: eachobservation.append(False) self.obs_with_sensitivity.append(eachobservation) # create an empty dictionary called plots to contain the returned # figures, axes 1 and axes 2 of the plot_two call plots = {} # Start time of loads being reviewed expressed in units for plotdate() load_start = cxctime2plotdate([tstart])[0] # Value for left side of plots plot_start = max(load_start - 2.0, cxctime2plotdate([times[0]])[0]) w1 = None # Make plots of FPTEMP and pitch vs time, looping over # three different temperature ranges ylim = [(-120, -90), (-120, -119), (-120.0, -109.5)] ypos = [-110.0, -119.35, -116] capwidth = [2.0, 0.1, 0.4] textypos = [-108.0, -119.3, -115.7] fontsize = [12, 9, 9] for i in range(3): name = "%s_%d" % (self.name, i + 1) plots[name] = plot_two(fig_id=i + 1, x=times, y=temps[self.name], x2=self.predict_model.times, y2=self.predict_model.comp["pitch"].mvals, title=self.msid.upper() + " (ACIS-I obs. in red; ACIS-S in green)", xlabel='Date', ylabel='Temperature (C)', ylabel2='Pitch (deg)', xmin=plot_start, ylim=ylim[i], ylim2=(40, 180), width=w1, load_start=load_start) # Draw a horizontal line indicating the FP Sensitive Observation Cut off plots[name]['ax'].axhline(self.fp_sens_limit, linestyle='--', color='red', linewidth=2.0) # Draw a horizontal line showing the ACIS-I -114 deg. C cutoff plots[name]['ax'].axhline(self.acis_i_limit, linestyle='--', color='purple', linewidth=1.0) # Draw a horizontal line showing the ACIS-S -112 deg. C cutoff plots[name]['ax'].axhline(self.acis_s_limit, linestyle='--', color='blue', linewidth=1.0) # Get the width of this plot to make the widths of all the # prediction plots the same if i == 0: w1, _ = plots[name]['fig'].get_size_inches() # Now plot any perigee passages that occur between xmin and xmax # for eachpassage in perigee_passages: paint_perigee(self.perigee_passages, states, plots, name) # Now draw horizontal lines on the plot running from start to stop # and label them with the Obsid draw_obsids(extract_and_filter, self.obs_with_sensitivity, plots, name, ypos[i], ypos[i] - 0.5 * capwidth[i], ypos[i] + 0.5 * capwidth[i], textypos[i], fontsize[i], plot_start) # Build the file name and output the plot to a file filename = self.msid.lower() + 'M%dtoM%d.png' % (-ylim[i][0], -ylim[i][1]) outfile = os.path.join(outdir, filename) mylog.info('Writing plot file %s' % outfile) plots[name]['fig'].savefig(outfile) plots[name]['filename'] = filename self._make_state_plots(plots, 3, w1, plot_start, outdir, states, load_start, figsize=(12, 6)) return plots
def make_validation_plots(self, tlm, model_spec, outdir, run_start): """ Make validation output plots by running the thermal model from a time in the past forward to the present and compare it to real telemetry Parameters ---------- tlm : NumPy record array NumPy record array of telemetry model_spec : string The path to the thermal model specification. outdir : string The directory to write outputs to. run_start : string The starting date/time of the run. """ start = tlm['date'][0] stop = tlm['date'][-1] states = self.state_builder.get_validation_states(start, stop) mylog.info('Calculating %s thermal model for validation' % self.name.upper()) # Run the thermal model from the beginning of obtained telemetry # to the end, so we can compare its outputs to the real values model = self.calc_model_wrapper(model_spec, states, start, stop) self.validate_model = model # Use an OrderedDict here because we want the plots on the validation # page to appear in this order pred = OrderedDict([(self.msid, model.comp[self.msid].mvals), ('pitch', model.comp['pitch'].mvals), ('tscpos', model.comp['sim_z'].mvals)]) if "roll" in model.comp: pred["roll"] = model.comp['roll'].mvals # Interpolate the model and data to a consistent set of times idxs = Ska.Numpy.interpolate(np.arange(len(tlm)), tlm['date'], model.times, method='nearest') tlm = tlm[idxs] # Set up labels for validation plots labels = {self.msid: 'Degrees (C)', 'pitch': 'Pitch (degrees)', 'tscpos': 'SIM-Z (steps/1000)', 'roll': 'Off-Nominal Roll (degrees)'} scales = {'tscpos': 1000.} fmts = {self.msid: '%.2f', 'pitch': '%.3f', 'tscpos': '%d', 'roll': '%.3f'} # Set up a mask of "good times" for which the validation is # "valid", e.g., not during situations where we expect in # advance that telemetry and model data will not match. This # is so we do not flag violations during these times good_mask = np.ones(len(tlm), dtype='bool') if hasattr(model, "bad_times"): for interval in model.bad_times: bad = ((tlm['date'] >= DateTime(interval[0]).secs) & (tlm['date'] < DateTime(interval[1]).secs)) good_mask[bad] = False # find perigee passages rzs = events.rad_zones.filter(start, stop) plots = [] mylog.info('Making %s model validation plots and quantile table' % self.name.upper()) quantiles = (1, 5, 16, 50, 84, 95, 99) # store lines of quantile table in a string and write out later quant_table = '' quant_head = ",".join(['MSID'] + ["quant%d" % x for x in quantiles]) quant_table += quant_head + "\n" xmin, xmax = cxctime2plotdate(model.times)[[0, -1]] for fig_id, msid in enumerate(pred.keys()): plot = dict(msid=msid.upper()) fig = plt.figure(10 + fig_id, figsize=(7, 3.5)) fig.clf() scale = scales.get(msid, 1.0) ticklocs, fig, ax = plot_cxctime(model.times, tlm[msid] / scale, fig=fig, fmt='-r') ticklocs, fig, ax = plot_cxctime(model.times, pred[msid] / scale, fig=fig, fmt='-b') if np.any(~good_mask): ticklocs, fig, ax = plot_cxctime(model.times[~good_mask], tlm[msid][~good_mask] / scale, fig=fig, fmt='.c') ax.set_title(msid.upper() + ' validation') ax.set_ylabel(labels[msid]) ax.grid() # add lines for perigee passages for rz in rzs: ptimes = cxctime2plotdate([rz.tstart, rz.tstop]) for ptime in ptimes: ax.axvline(ptime, ls='--', color='g') # Add horizontal lines for the planning and caution limits # or the limits for the focal plane model. Make sure we can # see all of the limits. if self.msid == msid: ymin, ymax = ax.get_ylim() if msid == "fptemp": fp_sens, acis_s, acis_i = get_acis_limits("fptemp") ax.axhline(acis_i, linestyle='-.', color='purple') ax.axhline(acis_s, linestyle='-.', color='blue') ymax = max(acis_i+1, ymax) else: ax.axhline(self.yellow_hi, linestyle='-', color='y') ax.axhline(self.plan_limit_hi, linestyle='--', color='y') ymax = max(self.yellow_hi+1, ymax) if self.flag_cold_viols: ax.axhline(self.yellow_lo, linestyle='-', color='y') ax.axhline(self.plan_limit_lo, linestyle='--', color='y') ymin = min(self.yellow_lo-1, ymin) ax.set_ylim(ymin, ymax) ax.set_xlim(xmin, xmax) filename = msid + '_valid.png' outfile = os.path.join(outdir, filename) mylog.info('Writing plot file %s' % outfile) fig.savefig(outfile) plot['lines'] = filename # Figure out histogram masks if msid == self.msid: masks = self.get_histogram_mask(tlm, self.hist_limit) ok = masks[0] & good_mask # Some models have a second histogram limit if len(self.hist_limit) == 2: ok2 = masks[1] & good_mask else: ok2 = np.zeros(tlm[msid].size, dtype=bool) else: ok = np.ones(tlm[msid].size, dtype=bool) ok2 = np.zeros(tlm[msid].size, dtype=bool) diff = np.sort(tlm[msid][ok] - pred[msid][ok]) if ok2.any(): diff2 = np.sort(tlm[msid][ok2] - pred[msid][ok2]) quant_line = "%s" % msid for quant in quantiles: quant_val = diff[(len(diff) * quant) // 100] plot['quant%02d' % quant] = fmts[msid] % quant_val quant_line += (',' + fmts[msid] % quant_val) quant_table += quant_line + "\n" # We make two histogram plots for each validation, # one with linear and another with log scaling. for histscale in ('log', 'lin'): fig = plt.figure(20 + fig_id, figsize=(4, 3)) fig.clf() ax = fig.gca() ax.hist(diff / scale, bins=50, log=(histscale == 'log'), histtype='step', color='b') if ok2.any(): ax.hist(diff2 / scale, bins=50, log=(histscale == 'log'), color='red', histtype='step') ax.set_title(msid.upper() + ' residuals: data - model') ax.set_xlabel(labels[msid]) fig.subplots_adjust(bottom=0.18) filename = '%s_valid_hist_%s.png' % (msid, histscale) outfile = os.path.join(outdir, filename) mylog.info('Writing plot file %s' % outfile) fig.savefig(outfile) plot['hist' + histscale] = filename plots.append(plot) # Write quantile tables to a CSV file filename = os.path.join(outdir, 'validation_quant.csv') mylog.info('Writing quantile table %s' % filename) f = open(filename, 'w') f.write(quant_table) f.close() # If run_start is specified this is likely for regression testing # or other debugging. In this case write out the full predicted and # telemetered dataset as a pickle. if run_start: filename = os.path.join(outdir, 'validation_data.pkl') mylog.info('Writing validation data %s' % filename) f = open(filename, 'wb') pickle.dump({'pred': pred, 'tlm': tlm}, f) f.close() return plots
def plot_one(fig_id, x, y, yy=None, linestyle='-', ll='--', color=thermal_blue, linewidth=2, xmin=None, xmax=None, ylim=None, xlabel='', ylabel='', title='', figsize=(12, 6), load_start=None, width=None): """ Plot one quantities with a date x-axis and a left y-axis. Parameters ---------- fig_id : integer The ID for this particular figure. x : NumPy array Times in seconds since the beginning of the mission for the left y-axis quantity. y : NumPy array Quantity to plot against the times on the left x-axis. yy : NumPy array, optional A second quantity to plot against the times on the left x-axis. Default: None linestyle : string, optional The style of the line for the left y-axis. ll : string, optional The style of the second line for the left y-axis. color : string, optional The color of the line for the left y-axis. linewidth : string, optional The width of the lines. Default: 2 xmin : float, optional The left-most value of the x-axis. xmax : float, optional The right-most value of the x-axis. ylim : 2-tuple, optional The limits for the left y-axis. xlabel : string, optional The label of the x-axis. ylabel : string, optional The label for the left y-axis. title : string, optional The title for the plot. figsize : 2-tuple of floats Size of plot in width and height in inches. """ # Convert times to dates xt = cxctime2plotdate(x) fig = plt.figure(fig_id, figsize=figsize) fig.clf() ax = fig.add_subplot(1, 1, 1) # Plot left y-axis ax.plot_date(xt, y, fmt='-', linestyle=linestyle, linewidth=linewidth, color=color) if yy is not None: ax.plot_date(xt, yy, fmt='-', linestyle=ll, linewidth=linewidth, color=color) if xmin is None: xmin = min(xt) if xmax is None: xmax = max(xt) ax.set_xlim(xmin, xmax) if ylim: ax.set_ylim(*ylim) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_title(title) ax.grid() if load_start is not None: # Add a vertical line to mark the start time of the load ax.axvline(load_start, linestyle='-', color='g', linewidth=2.0) Ska.Matplotlib.set_time_ticks(ax) [label.set_rotation(30) for label in ax.xaxis.get_ticklabels()] fig.subplots_adjust(bottom=0.22, right=0.87) # The next several lines ensure that the width of the axes # of all the weekly prediction plots are the same if width is not None: w2, _ = fig.get_size_inches() lm = fig.subplotpars.left * width / w2 rm = fig.subplotpars.right * width / w2 fig.subplots_adjust(left=lm, right=rm) return {'fig': fig, 'ax': ax}
def plot_one(fig_id, x, y, linestyle='-', color='blue', xmin=None, xmax=None, ylim=None, xlabel='', ylabel='', title='', figsize=(7, 3.5), load_start=None, width=None): """ Plot one quantities with a date x-axis and a left y-axis. Parameters ---------- fig_id : integer The ID for this particular figure. x : NumPy array Times in seconds since the beginning of the mission for the left y-axis quantity. y : NumPy array Quantity to plot against the times on the left x-axis. linestyle : string, optional The style of the line for the left y-axis. color : string, optional The color of the line for the left y-axis. xmin : float, optional The left-most value of the x-axis. xmax : float, optional The right-most value of the x-axis. ylim : 2-tuple, optional The limits for the left y-axis. xlabel : string, optional The label of the x-axis. ylabel : string, optional The label for the left y-axis. title : string, optional The title for the plot. figsize : 2-tuple of floats Size of plot in width and height in inches. """ # Convert times to dates xt = cxctime2plotdate(x) fig = plt.figure(fig_id, figsize=figsize) fig.clf() ax = fig.add_subplot(1, 1, 1) # Plot left y-axis ax.plot_date(xt, y, fmt='-', linestyle=linestyle, color=color) if xmin is None: xmin = min(xt) if xmax is None: xmax = max(xt) ax.set_xlim(xmin, xmax) if ylim: ax.set_ylim(*ylim) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_title(title) ax.grid() if load_start is not None: # Add a vertical line to mark the start time of the load ax.axvline(load_start, linestyle='-', color='g', linewidth=2.0) Ska.Matplotlib.set_time_ticks(ax) [label.set_rotation(30) for label in ax.xaxis.get_ticklabels()] fig.subplots_adjust(bottom=0.22, right=0.87) # The next several lines ensure that the width of the axes # of all the weekly prediction plots are the same if width is not None: w2, _ = fig.get_size_inches() lm = fig.subplotpars.left * width / w2 rm = fig.subplotpars.right * width / w2 fig.subplots_adjust(left=lm, right=rm) return {'fig': fig, 'ax': ax}
dat = dat[dat['r5_c2'] != 1759.75] dat = dat[dat['r6_c5'] != 791.0] dat = dat[dat['r2_c7'] != 584.0] dat = dat[dat['r7_c5'] != 470.0] dat = dat[dat['r2_c0'] != 410.0] dat = dat[dat['r0_c6'] != 235.0] dat = dat[dat['r0_c7'] != 217.0] dat = dat[dat['time'] > start.secs] dat['dt'] = dat['time'] - dat['time'][0] integ = dat['INTEG'] ccdfig = plt.figure("ccdplot") ccdax = plt.gca() if ccdax.lines: ccdline = ccdax.lines[0] ccdline.set_data(cxctime2plotdate(dat['time']), dat['TEMPCD']) ccdax.relim() ccdax.autoscale_view() else: plot_cxctime(dat['time'], dat['TEMPCD'], 'b.', ax=ccdax) #for colname in colnames: # dat[colname] = median_filter(dat[colname], 5) maxes = [np.max(median_filter(dat[colname], 5)) for colname in colnames] i_brightest = np.argsort(maxes)[-opt.n_brightest:] cols = [] for i, colname in enumerate(colnames): if i in i_brightest: cols.append(dat[colname])
def plot_two(fig_id, x, y, x2, y2, yy=None, linewidth=2, linestyle='-', linestyle2='-', ll='--', color=thermal_blue, color2='magenta', xmin=None, xmax=None, ylim=None, ylim2=None, xlabel='', ylabel='', ylabel2='', title='', figsize=(12, 6), load_start=None, width=None): """ Plot two quantities with a date x-axis, one on the left y-axis and the other on the right y-axis. Parameters ---------- fig_id : integer The ID for this particular figure. x : NumPy array Times in seconds since the beginning of the mission for the left y-axis quantity. y : NumPy array Quantity to plot against the times on the left x-axis. x2 : NumPy array Times in seconds since the beginning of the mission for the right y-axis quantity. y2 : NumPy array Quantity to plot against the times on the right y-axis. yy : NumPy array, optional A second quantity to plot against the times on the left x-axis. Default: None linewidth : string, optional The width of the lines. Default: 2 linestyle : string, optional The style of the line for the left y-axis. linestyle2 : string, optional The style of the line for the right y-axis. ll : string, optional The style of the second line for the left y-axis. color : string, optional The color of the line for the left y-axis. color2 : string, optional The color of the line for the right y-axis. xmin : float, optional The left-most value of the x-axis. xmax : float, optional The right-most value of the x-axis. ylim : 2-tuple, optional The limits for the left y-axis. ylim2 : 2-tuple, optional The limits for the right y-axis. xlabel : string, optional The label of the x-axis. ylabel : string, optional The label for the left y-axis. ylabel2 : string, optional The label for the right y-axis. title : string, optional The title for the plot. figsize : 2-tuple of floats Size of plot in width and height in inches. """ # Convert times to dates xt = cxctime2plotdate(x) fig = plt.figure(fig_id, figsize=figsize) fig.clf() ax = fig.add_subplot(1, 1, 1) # Plot left y-axis ax.plot_date(xt, y, fmt='-', linestyle=linestyle, linewidth=linewidth, color=color) if yy is not None: ax.plot_date(xt, yy, fmt='-', linestyle=ll, linewidth=linewidth, color=color) if xmin is None: xmin = min(xt) if xmax is None: xmax = max(xt) ax.set_xlim(xmin, xmax) if ylim: ax.set_ylim(*ylim) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_title(title) ax.grid() # Plot right y-axis ax2 = ax.twinx() xt2 = cxctime2plotdate(x2) ax2.plot_date(xt2, y2, fmt='-', linestyle=linestyle2, linewidth=linewidth, color=color2) ax2.set_xlim(xmin, xmax) if ylim2: ax2.set_ylim(*ylim2) ax2.set_ylabel(ylabel2, color=color2) ax2.xaxis.set_visible(False) if load_start is not None: # Add a vertical line to mark the start time of the load ax.axvline(load_start, linestyle='-', color='g', linewidth=2.0) Ska.Matplotlib.set_time_ticks(ax) for label in ax.xaxis.get_ticklabels(): label.set_rotation_mode("anchor") label.set_rotation(30) label.set_horizontalalignment('right') [label.set_color(color2) for label in ax2.yaxis.get_ticklabels()] ax.tick_params(which='major', axis='x', length=6) ax.tick_params(which='minor', axis='x', length=3) fig.subplots_adjust(bottom=0.22, right=0.87) # The next several lines ensure that the width of the axes # of all the weekly prediction plots are the same if width is not None: w2, _ = fig.get_size_inches() lm = fig.subplotpars.left * width / w2 rm = fig.subplotpars.right * width / w2 fig.subplots_adjust(left=lm, right=rm) ax.set_zorder(10) ax.patch.set_visible(False) return {'fig': fig, 'ax': ax, 'ax2': ax2}