def hinfo(args=None): """``hinfo input`` Prints out an hcm file along with its name and the number of CCDs. You will get information CCD-by-CCD first followed by the top level header. You will also get at least partial printouts of the data arrays. If you want still more detail, I recommend the FITS viewer tool 'fv', or 'ds9' for examining images (also :hplot:). Parameters: input : string name of the MCCD file """ command, args = utils.script_args(args) # get input section with Cline('HIPERCAM_ENV', '.hipercam', command, args) as cl: # register parameters cl.register('input', Cline.LOCAL, Cline.PROMPT) # get inputs frame = cl.get_value('input', 'frame to plot', cline.Fname('hcam', hcam.HCAM)) mccd = hcam.MCCD.read(frame) print('Name of file = {:s}'.format(frame)) print('Number of CCDs = {:d}\n'.format(len(mccd))) print(mccd)
def stats(args=None): """``stats input [format]`` Lists basic stats of a multi-CCD image, i.e. the minimum, maximum, mean, median and standard deviation of each window of each CCD. The output format can be altered to suit preference. Parameters: input : string name of the MCCD file format : string [hidden, defaults to 9.3f] C-style format code as used in Python format statements for output of the numerical values. e.g. '300.00' is '6.2f' (6 characters toal, 2 after the decimal point), '1.22e24' is '.2e' (as many characters as needed, 2 after the decimal point) """ command, args = utils.script_args(args) # get input section with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("input", Cline.LOCAL, Cline.PROMPT) cl.register("format", Cline.LOCAL, Cline.HIDE) # get inputs frame = cl.get_value("input", "frame to lists stats of", cline.Fname("hcam", hcam.HCAM)) mccd = hcam.MCCD.read(frame) cl.set_default("format", "9.3f") form = cl.get_value("format", "output format for numbers", "9.3f") for cnam, ccd in mccd.items(): for wnam, wind in ccd.items(): print( "CCD {0:s}, window {1:s}: min = {3:{2:s}}, max = {4:{2:s}}, mean = {5:{2:s}}, median = {6:{2:s}}, std = {7:{2:s}}" .format( cnam, wnam, form, wind.min(), wind.max(), wind.mean(), wind.median(), wind.std(), ))
def averun(args=None): """``averun [source] (run first last twait tmax | flist) trim ([ncol nrow]) bias dark flat [method sigma adjust clobber] output`` Averages images from a run using median combination, skipping the junk frames that result from NSKIP / NBLUE options in HiPERCAM and ULTRACAM data. `averun` is meant to be a simple tool to create median frames suitable prior to aperture selection with `setaper`. See `combine` if you want more fine-grained control over frame averaging. (`averun` uses a combination of `grab` [if needed] and `combine`). Parameters: source : string [hidden] Data source, five options: | 'hs' : HiPERCAM server | 'hl' : local HiPERCAM FITS file | 'us' : ULTRACAM server | 'ul' : local ULTRACAM .xml/.dat files | 'hf' : list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. run : string [if source ends 's' or 'l'] run number to access, e.g. 'run034' flist : string [if source ends 'f'] name of file list first : int [if source ends 's' or 'l'] exposure number to start from. 1 = first frame ('0' is not supported). last : int [if source ends 's' or 'l'] last exposure number must be >= first. twait : float [if source ends 's' or 'l'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's' or 'l'; hidden] maximum time to wait between attempts to find a new exposure, seconds. trim : bool True to trim columns and/or rows off the edges of windows nearest the readout. Useful for ULTRACAM particularly. ncol : int [if trim, hidden] Number of columns to remove (on left of left-hand window, and right of right-hand windows) nrow : int [if trim, hidden] Number of rows to remove (bottom of windows) bias : string Name of bias frame to subtract, 'none' to ignore. dark : string Name of dark frame to subtract, 'none' to ignore. flat : string Name of flat field to divide by, 'none' to ignore. method : string [hidden, defaults to 'm'] 'm' for median, 'c' for clipped mean. See below for pros and cons. sigma : float [hidden; if method == 'c'] With clipped mean combination, pixels that deviate by more than sigma RMS from the mean are kicked out. This is carried out in an iterative manner. sigma <= 0 implies no rejection, just a straight average. sigma=3 is typical. adjust : string [hidden; defaults to 'i'] adjustments to make: 'i' = ignore; 'n' = normalise the mean of all frames to match the first; 'b' = add offsets so that the mean of all frames is the same as the first. Option 'n' is useful for twilight flats; 'b' for combining biases. clobber : bool [hidden] clobber any pre-existing output files output : string output file """ command, args = utils.script_args(args) # get the inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("source", Cline.GLOBAL, Cline.HIDE) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("last", Cline.LOCAL, Cline.PROMPT) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("trim", Cline.GLOBAL, Cline.PROMPT) cl.register("ncol", Cline.GLOBAL, Cline.HIDE) cl.register("nrow", Cline.GLOBAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("bias", Cline.LOCAL, Cline.PROMPT) cl.register("dark", Cline.LOCAL, Cline.PROMPT) cl.register("flat", Cline.LOCAL, Cline.PROMPT) cl.register("method", Cline.LOCAL, Cline.HIDE) cl.register("sigma", Cline.LOCAL, Cline.HIDE) cl.register("adjust", Cline.LOCAL, Cline.HIDE) cl.register("clobber", Cline.LOCAL, Cline.HIDE) cl.register("output", Cline.LOCAL, Cline.PROMPT) # get inputs source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", "hl", lvals=("hs", "hl", "us", "ul", "hf"), ) # set a flag server_or_local = source.endswith("s") or source.endswith("l") if server_or_local: run = cl.get_value("run", "run name", "run005") first = cl.get_value("first", "first frame to average", 1, 1) last = cl.get_value("last", "last frame to average", first, first) twait = cl.get_value("twait", "time to wait for a new frame [secs]", 1.0, 0.0) tmax = cl.get_value("tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0) else: flist = cl.get_value("flist", "file list", cline.Fname("files.lis", hcam.LIST)) first = 1 trim = cl.get_value("trim", "do you want to trim edges of windows?", True) if trim: ncol = cl.get_value("ncol", "number of columns to trim from windows", 0) nrow = cl.get_value("nrow", "number of rows to trim from windows", 0) # bias frame (if any) bias = cl.get_value( "bias", "bias frame ['none' to ignore]", cline.Fname("bias", hcam.HCAM), ignore="none", ) # dark frame (if any) dark = cl.get_value( "dark", "dark frame ['none' to ignore]", cline.Fname("dark", hcam.HCAM), ignore="none", ) # flat field frame (if any) flat = cl.get_value( "flat", "flat field frame ['none' to ignore]", cline.Fname("flat", hcam.HCAM), ignore="none", ) cl.set_default("method", "m") method = cl.get_value("method", "c(lipped mean), m(edian)", "c", lvals=("c", "m")) if method == "c": sigma = cl.get_value("sigma", "number of RMS deviations to clip", 3.0) cl.set_default("adjust", "i") adjust = cl.get_value("adjust", "i(gnore), n(ormalise) b(ias offsets)", "i", lvals=("i", "n", "b")) clobber = cl.get_value("clobber", "clobber any pre-existing files on output", False) output = cl.get_value( "output", "output average", cline.Fname("hcam", hcam.HCAM, cline.Fname.NEW if clobber else cline.Fname.NOCLOBBER), ) # inputs done with. Now do the work with 'grab' and 'combine' if server_or_local: print("\nCalling 'grab' ...") if trim: args = [ None, "prompt", source, run, "yes", str(first), str(last), "yes", str(ncol), str(nrow), str(twait), str(tmax), "none", "f32", ] else: args = [ None, "prompt", source, run, "yes", str(first), str(last), "no", str(twait), str(tmax), "none", "f32", ] print("arg =", args) flist = hcam.scripts.grab(args) try: print("\nCalling 'combine' ...") if method == "m": args = [ None, "prompt", flist, "none" if bias is None else bias, "none" if dark is None else dark, "none" if flat is None else flat, method, adjust, "usemean=yes", "plot=no", "yes" if clobber else "no", output, ] else: args = [ None, "prompt", flist, "none" if bias is None else bias, "none" if dark is None else dark, "none" if flat is None else flat, method, str(sigma), adjust, "usemean=yes", "plot=no", "yes" if clobber else "no", output, ] hcam.scripts.combine(args) # remove temporary files with open(flist) as fin: for fname in fin: fname = fname.strip() os.remove(fname) os.remove(flist) print("\ntemporary files have been deleted") print("averun finished") except KeyboardInterrupt: # this to ensure we delete the temporary files with open(flist) as fin: for fname in fin: fname = fname.strip() os.remove(fname) os.remove(flist) print("\ntemporary files have been deleted") print("averun aborted")
def reduce(args=None): """``reduce [source] rfile (run first last twait tmax | flist) trim ([ncol nrow]) log lplot implot (ccd nx msub xlo xhi ylo yhi iset (ilo ihi | plo phi))`` Reduces a sequence of multi-CCD images, plotting lightcurves as images come in. It can extract with either simple aperture photometry or Tim Naylor's optimal photometry, on specific targets defined in an aperture file using |setaper|. reduce can source data from both the ULTRACAM and HiPERCAM servers, from local 'raw' ULTRACAM and HiPERCAM files (i.e. .xml + .dat for ULTRACAM, 3D FITS files for HiPERCAM) and from lists of HiPERCAM '.hcm' files. If you have data from a different instrument you should convert into the FITS-based hcm format. reduce is primarily configured from a file with extension ".red". This contains a series of directives, e.g. to say how to re-position and re-size the apertures. An initial reduce file is best generated with the script |genred| after you have created an aperture file. This contains lots of help on what to do. A reduce run can be terminated at any point with ctrl-C without doing any harm. You may often want to do this at the start in order to adjust parameters of the reduce file. Parameters: source : str [hidden] Data source, five options: | 'hs': HiPERCAM server | 'hl': local HiPERCAM FITS file | 'us': ULTRACAM server | 'ul': local ULTRACAM .xml/.dat files | 'hf': list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. run : str [if source ends 's' or 'l'] run number to access, e.g. 'run034' or a file list. If a run, then reduce and log below will be set to have the same name by default. first : int [if source ends 's' or 'l'] first frame to reduce. 1 = first frame; set = 0 to always try to get the most recent frame (if it has changed). last : int [if source ends 's' or 'l', hidden] last frame to reduce. 0 to just continue until the end. This is not prompted for by default and must be set explicitly. It defaults to 0 if not set. Its purpose is to allow accurate profiling tests. twait : float [if source ends 's'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's'; hidden] maximum time to wait between attempts to find a new exposure, seconds. flist : string [if source ends 'f'] name of file list trim : bool True to trim columns and/or rows off the edges of windows nearest the readout. Particularly useful with ULTRACAM windowed data where the first few rows and columns can contain bad data. ncol : int [if trim, hidden] Number of columns to remove (on left of left-hand window, and right of right-hand windows) nrow : int [if trim, hidden] Number of rows to remove (bottom of windows) rfile : str the "reduce" file, i.e. ASCII text file suitable for reading by ConfigParser. Best seen by example as it has many parts. If you are reducing a run, this will be set to have the same root name by default (but a different extension to avoid name clashes). log : str log file for the results. If you are reducing a run, this will be set to have the same root name by default (but a different extension to avoid name clashes) tkeep : float maximum number of minutes of data to store in internal buffers, 0 for the lot. When large numbers of frames are stored, performance can be slowed (although I am not entirely clear why) in which case it makes sense to lose the earlier points (without affecting the saving to disk). This parameter also gives operation similar to that of "max_xrange" parameter in the ULTRACAM pipeline whereby just the last few minutes are shown. lplot : bool flag to indicate you want to plot the light curve. Saves time not to especially in high-speed runs. implot : bool flag to indicate you want to plot images. ccd : string [if implot] CCD(s) to plot, '0' for all, '1 3' to plot '1' and '3' only, etc. nx : int [if implot] number of panels across to display. msub : bool [if implot] subtract the median from each window before scaling for the image display or not. This happens after any bias subtraction. xlo : float [if implot] left-hand X-limit for plot xhi : float [if implot] right-hand X-limit for plot (can actually be < xlo) ylo : float [if implot] lower Y-limit for plot yhi : float [if implot] upper Y-limit for plot (can be < ylo) iset : string [if implot] determines how the intensities are determined. There are three options: 'a' for automatic simply scales from the minimum to the maximum value found on a per CCD basis. 'd' for direct just takes two numbers from the user. 'p' for percentile dtermines levels based upon percentiles determined from the entire CCD on a per CCD basis. ilo : float [if implot and iset='d'] lower intensity level ihi : float [if implot and iset='d'] upper intensity level plo : float [if implot and iset='p'] lower percentile level phi : float [if implot and iset='p'] upper percentile level .. Warning:: The transmission plot generated with reduce is not reliable in the case of optimal photometry since it is highly correlated with the seeing. If you are worried about the transmission during observing, you should always use normal aperture photometry. """ command, args = utils.script_args(args) with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("source", Cline.GLOBAL, Cline.HIDE) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("last", Cline.LOCAL, Cline.HIDE) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("trim", Cline.GLOBAL, Cline.PROMPT) cl.register("ncol", Cline.GLOBAL, Cline.HIDE) cl.register("nrow", Cline.GLOBAL, Cline.HIDE) cl.register("rfile", Cline.GLOBAL, Cline.PROMPT) cl.register("log", Cline.GLOBAL, Cline.PROMPT) cl.register("tkeep", Cline.GLOBAL, Cline.PROMPT) cl.register("lplot", Cline.LOCAL, Cline.PROMPT) cl.register("implot", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("nx", Cline.LOCAL, Cline.PROMPT) cl.register("msub", Cline.GLOBAL, Cline.PROMPT) cl.register("iset", Cline.GLOBAL, Cline.PROMPT) cl.register("ilo", Cline.GLOBAL, Cline.PROMPT) cl.register("ihi", Cline.GLOBAL, Cline.PROMPT) cl.register("plo", Cline.GLOBAL, Cline.PROMPT) cl.register("phi", Cline.LOCAL, Cline.PROMPT) cl.register("xlo", Cline.GLOBAL, Cline.PROMPT) cl.register("xhi", Cline.GLOBAL, Cline.PROMPT) cl.register("ylo", Cline.GLOBAL, Cline.PROMPT) cl.register("yhi", Cline.GLOBAL, Cline.PROMPT) # get inputs source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", "hl", lvals=("hs", "hl", "us", "ul", "hf"), ) # set some flags server_or_local = source.endswith("s") or source.endswith("l") if server_or_local: resource = cl.get_value("run", "run name", "run005") first = cl.get_value("first", "first frame to reduce", 1, 0) cl.set_default("last", 0) last = cl.get_value("last", "last frame to reduce", 0, 0) if last and last < first: print("Cannot set last < first unless last == 0") print("*** reduce aborted") exit(1) twait = cl.get_value("twait", "time to wait for a new frame [secs]", 1.0, 0.0) tmx = cl.get_value("tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0) # keep the reduce name and log in sync to save errors root = os.path.basename(resource) cl.set_default('rfile', cline.Fname(root, hcam.RED)) cl.set_default('log', cline.Fname(root, hcam.LOG, cline.Fname.NEW)) else: resource = cl.get_value("flist", "file list", cline.Fname("files.lis", hcam.LIST)) first = 1 last = 0 trim = cl.get_value("trim", "do you want to trim edges of windows?", True) if trim: ncol = cl.get_value("ncol", "number of columns to trim from windows", 0) nrow = cl.get_value("nrow", "number of rows to trim from windows", 0) # the reduce file rfilen = cl.get_value("rfile", "reduce file", cline.Fname("reduce.red", hcam.RED)) try: rfile = Rfile.read(rfilen) except hcam.HipercamError as err: # abort on failure to read as there are many ways to get reduce # files wrong print(err, file=sys.stderr) print("*** reduce aborted") exit(1) log = cl.get_value( "log", "name of log file to store results", cline.Fname("reduce.log", hcam.LOG, cline.Fname.NEW), ) tkeep = cl.get_value( "tkeep", "number of minute of data to" " keep in internal buffers (0 for all)", 0.0, 0.0, ) lplot = cl.get_value("lplot", "do you want to plot light curves?", True) implot = cl.get_value("implot", "do you want to plot images?", True) if implot: # define the panel grid. first get the labels and maximum # dimensions ccdinf = spooler.get_ccd_pars(source, resource) try: nxdef = cl.get_default("nx") except KeyError: nxdef = 3 if len(ccdinf) > 1: ccd = cl.get_value("ccd", "CCD(s) to plot [0 for all]", "0") if ccd == "0": ccds = list(ccdinf.keys()) else: ccds = ccd.split() if len(ccds) > 1: nxdef = min(len(ccds), nxdef) cl.set_default("nx", nxdef) nx = cl.get_value("nx", "number of panels in X", 3, 1) else: nx = 1 else: nx = 1 ccds = list(ccdinf.keys()) # define the display intensities msub = cl.get_value("msub", "subtract median from each window?", True) iset = cl.get_value( "iset", "set intensity a(utomatically)," " d(irectly) or with p(ercentiles)?", "a", lvals=["a", "d", "p"], ) plo, phi = 5, 95 ilo, ihi = 0, 1000 if iset == "d": ilo = cl.get_value("ilo", "lower intensity limit", 0.0) ihi = cl.get_value("ihi", "upper intensity limit", 1000.0) elif iset == "p": plo = cl.get_value("plo", "lower intensity limit percentile", 5.0, 0.0, 100.0) phi = cl.get_value("phi", "upper intensity limit percentile", 95.0, 0.0, 100.0) # region to plot for i, cnam in enumerate(ccds): nxtot, nytot, nxpad, nypad = ccdinf[cnam] if i == 0: xmin, xmax = float(-nxpad), float(nxtot + nxpad + 1) ymin, ymax = float(-nypad), float(nytot + nypad + 1) else: xmin = min(xmin, float(-nxpad)) xmax = max(xmax, float(nxtot + nxpad + 1)) ymin = min(ymin, float(-nypad)) ymax = max(ymax, float(nytot + nypad + 1)) xlo = cl.get_value("xlo", "left-hand X value", xmin, xmin, xmax) xhi = cl.get_value("xhi", "right-hand X value", xmax, xmin, xmax) ylo = cl.get_value("ylo", "lower Y value", ymin, ymin, ymax) yhi = cl.get_value("yhi", "upper Y value", ymax, ymin, ymax) else: ccds, nx, msub, iset = None, None, None, None ilo, ihi, plo, phi = None, None, None, None xlo, xhi, ylo, yhi = None, None, None, None # save list of parameter values for writing to the reduction file plist = cl.list() ################################################################ # # all the inputs have now been obtained. Get on with doing stuff if implot: plot_lims = (xlo, xhi, ylo, yhi) else: plot_lims = None imdev, lcdev, spanel, tpanel, xpanel, ypanel, lpanel = setup_plots( rfile, ccds, nx, plot_lims, implot, lplot) # a couple of initialisations total_time = 0 # time waiting for new frame if lplot: lbuffer, xbuffer, ybuffer, tbuffer, sbuffer = setup_plot_buffers(rfile) else: lbuffer, xbuffer, ybuffer, tbuffer, sbuffer = None, None, None, None, None ############################################ # # open the log file and write headers # with LogWriter(log, rfile, hipercam_version, plist) as logfile: ncpu = rfile["general"]["ncpu"] if ncpu > 1: pool = multiprocessing.Pool(processes=ncpu) else: pool = None # whether some parameters have been initialised initialised = False # containers for the processed and raw MCCD groups # and their frame numbers pccds, mccds, nframes = [], [], [] ############################################## # # Finally, start winding through the frames # with spooler.data_source(source, resource, first, full=False) as spool: # 'spool' is an iterable source of MCCDs for nf, mccd in enumerate(spool): if server_or_local: # Handle the waiting game ... give_up, try_again, total_time = spooler.hang_about( mccd, twait, tmx, total_time) if give_up: # Giving up, but need to handle any partially filled # frame group if len(mccds): # finish processing remaining frames. This step # will only occur if we have at least once passed # to later stages during which read and gain will # be set up results = processor(pccds, mccds, nframes) # write out results to the log file alerts = logfile.write_results(results) # print out any accumulated alert messages if len(alerts): print("\n".join(alerts)) update_plots( results, rfile, implot, lplot, imdev, lcdev, pccd, ccds, msub, nx, iset, plo, phi, ilo, ihi, xlo, xhi, ylo, yhi, lpanel, xpanel, ypanel, tpanel, spanel, tkeep, lbuffer, xbuffer, ybuffer, tbuffer, sbuffer, ) mccds = [] print("reduce finished") break elif try_again: continue # Trim the frames: ULTRACAM windowed data has bad # columns and rows on the sides of windows closest to # the readout which can badly affect reduction. This # option strips them. if trim: hcam.ccd.trim_ultracam(mccd, ncol, nrow) # indicate progress if "NFRAME" in mccd.head: nframe = mccd.head["NFRAME"] else: nframe = nf + 1 if source != "hf" and last and nframe > last: # finite last frame number if len(mccds): # finish processing remaining frames results = processor(pccds, mccds, nframes) # write out results to the log file alerts = logfile.write_results(results) # print out any accumulated alert messages if len(alerts): print("\n".join(alerts)) update_plots( results, rfile, implot, lplot, imdev, lcdev, pccd, ccds, msub, nx, iset, plo, phi, ilo, ihi, xlo, xhi, ylo, yhi, lpanel, xpanel, ypanel, tpanel, spanel, tkeep, lbuffer, xbuffer, ybuffer, tbuffer, sbuffer, ) mccds = [] print("\nHave reduced up to the last frame set.") print("reduce finished") break print( "Frame {:d}: {:s} ({:s})".format( nframe, mccd.head["TIMSTAMP"], "ok" if mccd.head.get("GOODTIME", True) else "nok", ), end="" if implot else "\n", ) if not initialised: # This is the first frame which allows us to make # some checks and initialisations. read, gain, ok = initial_checks(mccd, rfile) # Define the CCD processor function object processor = ProcessCCDs(rfile, read, gain, ccdproc, pool) # set flag to show we are set if not ok: break initialised = True # De-bias the data. Retain a copy of the raw data as 'mccd' # in order to judge saturation. Processed data called 'pccd' if rfile.bias is not None: # subtract bias pccd = mccd - rfile.bias bexpose = rfile.bias.head.get("EXPTIME", 0.0) else: # no bias subtraction pccd = mccd.copy() bexpose = 0.0 if rfile.dark is not None: # subtract dark, CCD by CCD dexpose = rfile.dark.head["EXPTIME"] for cnam in pccd: ccd = pccd[cnam] cexpose = ccd.head["EXPTIME"] scale = (cexpose - bexpose) / dexpose ccd -= scale * rfile.dark[cnam] if rfile.flat is not None: # apply flat field to processed frame pccd /= rfile.flat if rfile["focal_mask"]["demask"]: # attempt to correct for poorly placed frame # transfer mask causing a step illumination in the # y-direction. Loop through all windows of all # CCDs. Also include a stage where we average in # the Y direction to try to eliminate high pixels. dthresh = rfile["focal_mask"]["dthresh"] for cnam, ccd in pccd.items(): for wnam, wind in ccd.items(): # form mean in Y direction, then try to # mask out high pixels ymean = np.mean(wind.data, 0) xmask = ymean == ymean while 1: # rejection cycle, rejecting # overly positive pixels ave = ymean[xmask].mean() rms = ymean[xmask].std() diff = ymean - ave diff[~xmask] = 0 imax = np.argmax(diff) if diff[imax] > dthresh * rms: xmask[imax] = False else: break # form median in X direction xmedian = np.median(wind.data[:, xmask], 1) # subtract it's median to avoid removing # general background xmedian -= np.median(xmedian) # now subtract from 2D image using # broadcasting rules wind.data -= xmedian.reshape((len(xmedian), 1)) # Acummulate frames into processing groups for faster # parallelisation pccds.append(pccd) mccds.append(mccd) nframes.append(nframe) if len(pccds) == rfile["general"]["ngroup"]: # parallel processing. This should usually be the first # points at which it takes place results = processor(pccds, mccds, nframes) # write out results to the log file alerts = logfile.write_results(results) # print out any accumulated alert messages if len(alerts): print("\n".join(alerts)) update_plots( results, rfile, implot, lplot, imdev, lcdev, pccds[-1], ccds, msub, nx, iset, plo, phi, ilo, ihi, xlo, xhi, ylo, yhi, lpanel, xpanel, ypanel, tpanel, spanel, tkeep, lbuffer, xbuffer, ybuffer, tbuffer, sbuffer, ) # Reset the frame buffers pccds, mccds, nframes = [], [], [] if len(mccds): # out of loop now. Finish processing any remaining # frames. results = processor(pccds, mccds, nframes) # write out results to the log file alerts = logfile.write_results(results) # print out any accumulated alert messages if len(alerts): print("\n".join(alerts)) update_plots( results, rfile, implot, lplot, imdev, lcdev, pccd, ccds, msub, nx, iset, plo, phi, ilo, ihi, xlo, xhi, ylo, yhi, lpanel, xpanel, ypanel, tpanel, spanel, tkeep, lbuffer, xbuffer, ybuffer, tbuffer, sbuffer, ) print("reduce finished")
def setdefect(args=None): """``setdefect mccd defect ccd [linput width height] rtarg rsky1 rsky2 nx msub iset (ilo ihi | plo phi) [profit method beta fwmin fwhm fwfix shbox smooth splot fhbox read gain thresh]`` Interactive definition of CCD defects. This is a matplotlib-based routine allowing you to define defects using the cursor. Parameters: mccd : string name of an MCCD file, as produced by e.g. 'grab' defect : string the name of a defect file. If it exists it will be read so that defects can be added to it. If it does not exist, it will be created on exiting the routine. The defect files are in a fairly readable / editiable text format ccd : string CCD(s) to plot, '0' for all. If not '0' then '1', '2' or even '3 4' are possible inputs (without the quotes). '3 4' will plot CCD '3' and CCD '4'. If you want to plot more than one CCD, then you will be prompted for the number of panels in the X direction. This parameter will not be prompted if there is only one CCD in the file. width : float [hidden] plot width (inches). Set = 0 to let the program choose. height : float [hidden] plot height (inches). Set = 0 to let the program choose. BOTH width AND height must be non-zero to have any effect nx : int number of panels across to display, prompted if more than one CCD is to be plotted. msub : bool True/False to subtract median from each window before scaling invert : bool [if msub] If msub is True, then you can invert the image values (-ve to +ve) with this parameter. Can make it easier to spot bad values. ffield : bool If True, all defects will be assumed to be flat-field or poor charge transfer defects as opposed to hot pixels. The latter are best set from dark frames, and have a different impact than the first two types in that they are worst for faint targets. Hot pixels and flat-field defects are shown with the same colours for moderate and severe, but different symbols (filled circles for flat-field defects, stars for hot pixels). If you say no to add hot pixels, the line defect option is not available. hsbox : int half-width in binned pixels of stats box as offset from central pixel hsbox = 1 gives a 3x3 box; hsbox = 2 gives 5x5 etc. This is used by the "show" option when setting defects. iset : string [single character] determines how the intensities are determined. There are three options: 'a' for automatic simply scales from the minimum to the maximum value found on a per CCD basis. 'd' for direct just takes two numbers from the user. 'p' for percentile dtermines levels based upon percentiles determined from the entire CCD on a per CCD bais. ilo : float [if iset=='d'] lower intensity level ihi : float [if iset=='d'] upper intensity level plo : float [if iset=='p'] lower percentile level phi : float [if iset=='p'] upper percentile level There are a few conveniences to make setdefect easier: 1. The plot is initialised in pan mode whereby you can move around and scale using the left and right mouse buttons. 2. All input is accomplished with the keyboard; the mouse buttons are only for navigating the image. 3. The label input can be switched between sequential numerical, single- and multi-character input ('linput'). Various standard keyboard shortcuts (e.g. 's' to save) are disabled as they just confuse things and are of limited use in setdefect in any case. Some aspects of the usage of matplotlib in setdefect are tricky. It is possible that particular 'backends' will cause problems. I have tested this with Qt4Agg, Qt5agg and GTK3Agg. One aspect is the cursor icon in pan mode is a rather indistinct hand where one can't tell what is being pointed at. I have therefore suppressed this, but only for the tested backends. Others would need require further investigation. NB At the end of this routine, it re-orders the defects so that the severe ones follows the moderates. This helps emphasize the severe ones over the moderates when running rtplot. """ command, args = utils.script_args(args) # get input section with Cline('HIPERCAM_ENV', '.hipercam', command, args) as cl: # register parameters cl.register('mccd', Cline.LOCAL, Cline.PROMPT) cl.register('defect', Cline.LOCAL, Cline.PROMPT) cl.register('ccd', Cline.LOCAL, Cline.PROMPT) cl.register('width', Cline.LOCAL, Cline.HIDE) cl.register('height', Cline.LOCAL, Cline.HIDE) cl.register('nx', Cline.LOCAL, Cline.PROMPT) cl.register('msub', Cline.GLOBAL, Cline.PROMPT) cl.register('invert', Cline.GLOBAL, Cline.PROMPT) cl.register('ffield', Cline.GLOBAL, Cline.PROMPT) cl.register('hsbox', Cline.GLOBAL, Cline.HIDE) cl.register('iset', Cline.GLOBAL, Cline.PROMPT) cl.register('ilo', Cline.GLOBAL, Cline.PROMPT) cl.register('ihi', Cline.GLOBAL, Cline.PROMPT) cl.register('plo', Cline.GLOBAL, Cline.PROMPT) cl.register('phi', Cline.GLOBAL, Cline.PROMPT) # get inputs mccd = cl.get_value('mccd', 'frame to plot', cline.Fname('hcam', hcam.HCAM)) mccd = hcam.MCCD.read(mccd) dfct = cl.get_value('defect', 'name of defect file', cline.Fname('defect', hcam.DFCT, exist=False)) if os.path.exists(dfct): # read in old defects mccd_dfct = defect.MccdDefect.read(dfct) print('Loaded existing file = {:s}'.format(dfct)) else: # create empty container mccd_dfct = defect.MccdDefect() print('No file called {:s} exists; ' 'will create from scratch'.format(dfct)) # define the panel grid try: nxdef = cl.get_default('nx') except: nxdef = 3 max_ccd = len(mccd) if max_ccd > 1: ccd = cl.get_value('ccd', 'CCD(s) to plot [0 for all]', '0') if ccd == '0': ccds = list(mccd.keys()) else: ccds = ccd.split() else: ccds = list(mccd.keys()) width = cl.get_value('width', 'plot width (inches)', 0.) height = cl.get_value('height', 'plot height (inches)', 0.) # number of panels in X if len(ccds) > 1: nxdef = min(len(ccds), nxdef) cl.set_default('nx', nxdef) nx = cl.get_value('nx', 'number of panels in X', 3, 1) else: nx = 1 # define the display intensities msub = cl.get_value('msub', 'subtract median from each window?', True) if msub: invert = cl.get_value('invert', 'invert image intensities?', True) ffield = cl.get_value('ffield', 'flat field defects? [else hot pixels]', True) hsbox = cl.get_value('hsbox', 'half-width of stats box (binned pixels)', 2, 1) iset = cl.get_value('iset', 'set intensity a(utomatically),' ' d(irectly) or with p(ercentiles)?', 'a', lvals=['a', 'A', 'd', 'D', 'p', 'P']) iset = iset.lower() plo, phi = 5, 95 ilo, ihi = 0, 1000 if iset == 'd': ilo = cl.get_value('ilo', 'lower intensity limit', 0.) ihi = cl.get_value('ihi', 'upper intensity limit', 1000.) elif iset == 'p': plo = cl.get_value('plo', 'lower intensity limit percentile', 5., 0., 100.) phi = cl.get_value('phi', 'upper intensity limit percentile', 95., 0., 100.) nxmax, nymax = 0, 0 for cnam in ccds: nxmax = max(nxmax, mccd[cnam].nxtot) nymax = max(nymax, mccd[cnam].nytot) # might be worth trying to improve this at some point xlo, xhi, ylo, yhi = 0, nxmax + 1, 0, nymax + 1 # Inputs obtained. # re-configure keyboard shortcuts to avoid otherwise confusing behaviour # quit_all does not seem to be universal, hence the try/except try: mpl.rcParams['keymap.back'] = '' mpl.rcParams['keymap.forward'] = '' mpl.rcParams['keymap.fullscreen'] = '' mpl.rcParams['keymap.grid'] = '' mpl.rcParams['keymap.home'] = '' mpl.rcParams['keymap.pan'] = '' mpl.rcParams['keymap.quit'] = '' mpl.rcParams['keymap.save'] = '' mpl.rcParams['keymap.pan'] = '' mpl.rcParams['keymap.save'] = '' mpl.rcParams['keymap.xscale'] = '' mpl.rcParams['keymap.yscale'] = '' mpl.rcParams['keymap.zoom'] = '' except KeyError: pass # start plot if width > 0 and height > 0: fig = plt.figure(figsize=(width, height)) else: fig = plt.figure() # get the navigation toolbar. Go straight into pan mode # where we want to stay. toolbar = fig.canvas.manager.toolbar toolbar.pan() nccd = len(ccds) ny = nccd // nx if nccd % nx == 0 else nccd // nx + 1 # we need to store some stuff ax = None cnams = {} anams = {} # this is a container for all the objects used to plot Defects to allow # deletion. This is Group of Group objects supporting tuple storage. The # idea is that pobjs[cnam][anam] returns the objects used to plot Defect # anam of CCD cnam. It is initially empty, pobjs = hcam.Group(hcam.Group) for n, cnam in enumerate(ccds): if ax is None: axes = ax = fig.add_subplot(ny, nx, n + 1) axes.set_aspect('equal', adjustable='box') axes.set_xlim(xlo, xhi) axes.set_ylim(ylo, yhi) else: axes = fig.add_subplot(ny, nx, n + 1, sharex=ax, sharey=ax) axes.set_aspect('equal', adjustable='datalim') if msub: # subtract median from each window for wind in mccd[cnam].values(): wind -= wind.median() if invert: mccd *= -1 hcam.mpl.pCcd(axes, mccd[cnam], iset, plo, phi, ilo, ihi, 'CCD {:s}'.format(cnam)) # keep track of the CCDs associated with each axes cnams[axes] = cnam # and axes associated with each CCD anams[cnam] = axes if cnam in mccd_dfct: # plot any pre-existing Defects, keeping track of # the plot objects pobjs[cnam] = hcam.mpl.pCcdDefect(axes, mccd_dfct[cnam]) else: # add in an empty CcdDefect for any CCD not already present mccd_dfct[cnam] = defect.CcdDefect() # and an empty container for any new plot objects pobjs[cnam] = hcam.Group(tuple) # create the Defect picker (see below for class def) picker = PickDefect(mccd, cnams, anams, toolbar, fig, mccd_dfct, dfct, ffield, hsbox, pobjs) plt.tight_layout() picker.action_prompt(False) # squeeze space a bit plt.subplots_adjust(wspace=0.1, hspace=0.1) # finally show stuff .... plt.show()
def averun(args=None): """``averun [source] (run first last twait tmax | flist) trim ([ncol nrow]) bias dark flat fmap (fpair [nhalf rmin rmax]) [method sigma adjust clobber] output`` Averages images from a run using median combination, skipping the junk frames that result from NSKIP / NBLUE options in HiPERCAM and ULTRACAM data. `averun` is meant to be a simple tool to create median frames suitable prior to aperture selection with `setaper`. See `combine` if you want more fine-grained control over frame averaging. (`averun` uses a combination of `grab` [if needed] and `combine`). Parameters: source : str [hidden] Data source, five options: | 'hs' : HiPERCAM server | 'hl' : local HiPERCAM FITS file | 'us' : ULTRACAM server | 'ul' : local ULTRACAM .xml/.dat files | 'hf' : list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. The standard start-off default for ``source`` can be set using the environment variable HIPERCAM_DEFAULT_SOURCE. e.g. in bash :code:`export HIPERCAM_DEFAULT_SOURCE="us"` would ensure it always started with the ULTRACAM server by default. If unspecified, it defaults to 'hl'. run : str [if source ends 's' or 'l'] run number to access, e.g. 'run034' flist : str [if source ends 'f'] name of file list first : int [if source ends 's' or 'l'] exposure number to start from. 1 = first frame ('0' is not supported). last : int [if source ends 's' or 'l'] last exposure number must be >= first, or 0 for the lot twait : float [if source ends 's' or 'l'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's' or 'l'; hidden] maximum time to wait between attempts to find a new exposure, seconds. trim : bool True to trim columns and/or rows off the edges of windows nearest the readout. Useful for ULTRACAM particularly. ncol : int [if trim, hidden] Number of columns to remove (on left of left-hand window, and right of right-hand windows) nrow : int [if trim, hidden] Number of rows to remove (bottom of windows) bias : str Name of bias frame to subtract, 'none' to ignore. dark : str Name of dark frame to subtract, 'none' to ignore. flat : str Name of flat field to divide by, 'none' to ignore. fmap : str Name of fringe map (see e.g. `makefringe`), 'none' to ignore. fpair : str [if fmap is not 'none'] Name of fringe pair file (see e.g. `setfringe`). Required if a fringe map has been specified. nhalf : int [if fmap is not 'none', hidden] When calculating the differences for fringe measurement, a region extending +/-nhalf binned pixels will be used when measuring the amplitudes. Basically helps the stats. rmin : float [if fmap is not 'none', hidden] Minimum individual ratio to accept prior to calculating the overall median in order to reduce the effect of outliers. Although all ratios should be positive, you might want to set this a little below zero to allow for some statistical fluctuation. rmax : float [if fmap is not 'none', hidden] Maximum individual ratio to accept prior to calculating the overall median in order to reduce the effect of outliers. Probably typically < 1 if fringe map was created from longer exposure data. method : str [hidden, defaults to 'm'] 'm' for median, 'c' for clipped mean. See below for pros and cons. sigma : float [hidden; if method == 'c'] With clipped mean combination, pixels that deviate by more than sigma RMS from the mean are kicked out. This is carried out in an iterative manner. sigma <= 0 implies no rejection, just a straight average. sigma=3 is typical. adjust : str [hidden; defaults to 'i'] adjustments to make: 'i' = ignore; 'n' = normalise the mean of all frames to match the first; 'b' = add offsets so that the mean of all frames is the same as the first. Option 'n' is useful for twilight flats; 'b' for combining biases. clobber : bool [hidden] clobber any pre-existing output files output : str output file. Set by default to match the last part of "run" (but it will have a different extension so they won't clash) """ command, args = utils.script_args(args) # get the inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("source", Cline.GLOBAL, Cline.HIDE) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("last", Cline.LOCAL, Cline.PROMPT) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("trim", Cline.GLOBAL, Cline.PROMPT) cl.register("ncol", Cline.GLOBAL, Cline.HIDE) cl.register("nrow", Cline.GLOBAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("bias", Cline.LOCAL, Cline.PROMPT) cl.register("dark", Cline.GLOBAL, Cline.PROMPT) cl.register("flat", Cline.GLOBAL, Cline.PROMPT) cl.register("fmap", Cline.GLOBAL, Cline.PROMPT) cl.register("fpair", Cline.GLOBAL, Cline.PROMPT) cl.register("nhalf", Cline.GLOBAL, Cline.HIDE) cl.register("rmin", Cline.GLOBAL, Cline.HIDE) cl.register("rmax", Cline.GLOBAL, Cline.HIDE) cl.register("method", Cline.LOCAL, Cline.HIDE) cl.register("sigma", Cline.LOCAL, Cline.HIDE) cl.register("adjust", Cline.LOCAL, Cline.HIDE) cl.register("clobber", Cline.LOCAL, Cline.HIDE) cl.register("output", Cline.LOCAL, Cline.PROMPT) # get inputs default_source = os.environ.get('HIPERCAM_DEFAULT_SOURCE','hl') source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", default_source, lvals=("hs", "hl", "us", "ul", "hf"), ) # set a flag server_or_local = source.endswith("s") or source.endswith("l") if server_or_local: resource = cl.get_value("run", "run name", "run005") root = os.path.basename(resource) cl.set_default('output', cline.Fname(root, hcam.HCAM)) first = cl.get_value("first", "first frame to average", 1, 1) last = cl.get_value("last", "last frame to average", first, 0) twait = cl.get_value( "twait", "time to wait for a new frame [secs]", 1.0, 0.0 ) tmax = cl.get_value( "tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0 ) else: resource = cl.get_value( "flist", "file list", cline.Fname("files.lis", hcam.LIST) ) first = 1 trim = cl.get_value("trim", "do you want to trim edges of windows?", True) if trim: ncol = cl.get_value("ncol", "number of columns to trim from windows", 0) nrow = cl.get_value("nrow", "number of rows to trim from windows", 0) # bias frame (if any) bias = cl.get_value( "bias", "bias frame ['none' to ignore]", cline.Fname("bias", hcam.HCAM), ignore="none", ) # dark frame (if any) dark = cl.get_value( "dark", "dark frame ['none' to ignore]", cline.Fname("dark", hcam.HCAM), ignore="none", ) # flat field frame (if any) flat = cl.get_value( "flat", "flat field frame ['none' to ignore]", cline.Fname("flat", hcam.HCAM), ignore="none", ) # fringe file (if any) fmap = cl.get_value( "fmap", "fringe map ['none' to ignore]", cline.Fname("fmap", hcam.HCAM), ignore="none", ) if fmap is not None: fpair = cl.get_value( "fpair", "fringe pair file", cline.Fname("fringe", hcam.FRNG) ) nhalf = cl.get_value( "nhalf", "half-size of fringe measurement regions", 2, 0 ) rmin = cl.get_value( "rmin", "minimum fringe pair ratio", -0.2 ) rmax = cl.get_value( "rmax", "maximum fringe pair ratio", 1.0 ) cl.set_default("method", "m") method = cl.get_value( "method", "c(lipped mean), m(edian)", "c", lvals=("c", "m") ) if method == "c": sigma = cl.get_value("sigma", "number of RMS deviations to clip", 3.0) cl.set_default("adjust", "i") adjust = cl.get_value( "adjust", "i(gnore), n(ormalise) b(ias offsets)", "i", lvals=("i", "n", "b") ) clobber = cl.get_value( "clobber", "clobber any pre-existing files on output", False ) output = cl.get_value( "output", "output average", cline.Fname( "hcam", hcam.HCAM, cline.Fname.NEW if clobber else cline.Fname.NOCLOBBER ), ) # inputs done with. Now do the work with 'grab' and 'combine' if server_or_local or bias is not None or dark is not None or \ flat is not None or fmap is not None: print("\nCalling 'grab' ...") # Build argument list args = [None,"prompt",source,"yes",resource] if server_or_local: args += [str(first), str(last), str(twait), str(tmax)] if trim: args += ["yes",str(ncol),str(nrow)] else: args += ["no"] args += [ "none" if bias is None else bias, "none" if dark is None else dark, "none" if flat is None else flat, ] if fmap is None: args += ["none", "f32"] else: args += [fmap,fpair,str(nhalf),str(rmin),str(rmax),"false","f32"] resource = hcam.scripts.grab(args) # at this point 'resource' is a list of files, no matter the input # method. with CleanUp( resource, server_or_local or bias is not None or dark is not None or flat is not None or fmap is not None ) as cleanup: print("\nCalling 'combine' ...") args = [ None, "prompt", resource, "none", "none", "none", method ] if method != "m": args += [str(sigma)] args += [ adjust, "usemean=yes", "plot=no", "yes" if clobber else "no", output, ] hcam.scripts.combine(args) print("averun finished")
def rtplot(args=None): """``rtplot [source device width height] (run first (trim [ncol nrow]) twait tmax | flist) [pause plotall] ccd (nx) bias flat defect setup [hurl] msub iset (ilo ihi | plo phi) xlo xhi ylo yhi (profit [fdevice fwidth fheight method beta fwhm fwhm_min shbox smooth splot fhbox hmin read gain thresh])`` Plots a sequence of images as a movie in near 'real time', hence 'rt'. Designed to be used to look at images coming in while at the telescope, 'rtplot' comes with many options, a large number of which are hidden by default, and many of which are only prompted if other arguments are set correctly. If you want to see them all, invoke as 'rtplot prompt'. This is worth doing once to know rtplot's capabilities. rtplot can source data from both the ULTRACAM and HiPERCAM servers, from local 'raw' ULTRACAM and HiPERCAM files (i.e. .xml + .dat for ULTRACAM, 3D FITS files for HiPERCAM) and from lists of HiPERCAM '.hcm' files. rtplot optionally allows the selection of targets to be fitted with gaussian or moffat profiles, and, if successful, will plot circles of 2x the measured FWHM in green over the selected targets. This option only works if a single CCD is being plotted. Parameters: source : string [hidden] Data source, five options: | 'hs' : HiPERCAM server | 'hl' : local HiPERCAM FITS file | 'us' : ULTRACAM server | 'ul' : local ULTRACAM .xml/.dat files | 'hf' : list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. device : string [hidden] Plot device. PGPLOT is used so this should be a PGPLOT-style name, e.g. '/xs', '1/xs' etc. At the moment only ones ending /xs are supported. width : float [hidden] plot width (inches). Set = 0 to let the program choose. height : float [hidden] plot height (inches). Set = 0 to let the program choose. BOTH width AND height must be non-zero to have any effect run : string [if source ends 's' or 'l'] run number to access, e.g. 'run034' flist : string [if source ends 'f'] name of file list first : int [if source ends 's' or 'l'] exposure number to start from. 1 = first frame; set = 0 to always try to get the most recent frame (if it has changed). For data from the |hiper| server, a negative number tries to get a frame not quite at the end. i.e. -10 will try to get 10 from the last frame. This is mainly to sidestep a difficult bug with the acquisition system. trim : bool [if source starts with 'u'] True to trim columns and/or rows off the edges of windows nearest the readout. This is particularly for ULTRACAM windowed data where the first few rows and columns can contain bad data. ncol : int [if trim, hidden] Number of columns to remove (on left of left-hand window, and right of right-hand windows) nrow : int [if trim, hidden] Number of rows to remove (bottom of windows) twait : float [if source ends 's' or 'l'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's' or 'l'; hidden] maximum time to wait between attempts to find a new exposure, seconds. pause : float [hidden] seconds to pause between frames (defaults to 0) plotall : bool [hidden] plot all frames regardless of status (i.e. including blank frames when nskips are enabled (defaults to False). The profile fitting will still be disabled for bad frames. ccd : string CCD(s) to plot, '0' for all, '1 3' to plot '1' and '3' only, etc. nx : int [if more than 1 CCD] number of panels across to display. bias : string Name of bias frame to subtract, 'none' to ignore. flat : string Name of flat field to divide by, 'none' to ignore. Should normally only be used in conjunction with a bias, although it does allow you to specify a flat even if you haven't specified a bias. defect : string Name of defect file, 'none' to ignore. setup : bool True/yes to access the current windows from hdriver. Useful during observing when seeting up windows, but not normally otherwise. Next argument (hidden) is the URL to get to hdriver. Once setup, you should probably turn this off to avoid overloading hdriver, especially if in drift mode as it makes a request for the windows for every frame. hurl : string [if setup; hidden] URL needed to access window setting from hdriver. The internal server in hdriver must be switched on using rtplot_server_on in the hdriver config file. msub : bool subtract the median from each window before scaling for the image display or not. This happens after any bias subtraction. iset : string [single character] determines how the intensities are determined. There are three options: 'a' for automatic simply scales from the minimum to the maximum value found on a per CCD basis. 'd' for direct just takes two numbers from the user. 'p' for percentile dtermines levels based upon percentiles determined from the entire CCD on a per CCD basis. ilo : float [if iset='d'] lower intensity level ihi : float [if iset='d'] upper intensity level plo : float [if iset='p'] lower percentile level phi : float [if iset='p'] upper percentile level xlo : float left-hand X-limit for plot xhi : float right-hand X-limit for plot (can actually be < xlo) ylo : float lower Y-limit for plot yhi : float upper Y-limit for plot (can be < ylo) profit : bool [if plotting a single CCD only] carry out profile fits or not. If you say yes, then on the first plot, you will have the option to pick objects with a cursor. The program will then attempt to track these from frame to frame, and fit their profile. You may need to adjust 'first' to see anything. The parameters used for profile fits are hidden and you may want to invoke the command with 'prompt' the first time you try profile fitting. fdevice : string [if profit; hidden] plot device for profile fits, PGPLOT-style name. e.g. '/xs', '2/xs' etc. fwidth : float [if profit; hidden] fit plot width (inches). Set = 0 to let the program choose. fheight : float [if profit; hidden] fit plot height (inches). Set = 0 to let the program choose. BOTH fwidth AND fheight must be non-zero to have any effect method : string [if profit; hidden] this defines the profile fitting method, either a gaussian or a moffat profile. The latter is usually best. beta : float [if profit and method == 'm'; hidden] default Moffat exponent fwhm : float [if profit; hidden] default FWHM, unbinned pixels. fwhm_min : float [if profit; hidden] minimum FWHM to allow, unbinned pixels. shbox : float [if profit; hidden] half width of box for searching for a star, unbinned pixels. The brightest target in a region +/- shbox around an intial position will be found. 'shbox' should be large enough to allow for likely changes in position from frame to frame, but try to keep it as small as you can to avoid jumping to different targets and to reduce the chances of interference by cosmic rays. smooth : float [if profit; hidden] FWHM for gaussian smoothing, binned pixels. The initial position for fitting is determined by finding the maximum flux in a smoothed version of the image in a box of width +/- shbox around the starter position. Typically should be comparable to the stellar width. Its main purpose is to combat cosmi rays which tend only to occupy a single pixel. splot : bool [if profit; hidden] Controls whether an outline of the search box and a target number is plotted (in red) or not. fhbox : float [if profit; hidden] half width of box for profile fit, unbinned pixels. The fit box is centred on the position located by the initial search. It should normally be > ~2x the expected FWHM. hmin : float [if profit; hidden] height threshold to accept a fit. If the height is below this value, the position will not be updated. This is to help in cloudy conditions. read : float [if profit; hidden] readout noise, RMS ADU, for assigning uncertainties gain : float [if profit; hidden] gain, ADU/count, for assigning uncertainties thresh : float [if profit; hidden] sigma rejection threshold for fits """ command, args = utils.script_args(args) # get the inputs with Cline('HIPERCAM_ENV', '.hipercam', command, args) as cl: # register parameters cl.register('source', Cline.GLOBAL, Cline.HIDE) cl.register('device', Cline.LOCAL, Cline.HIDE) cl.register('width', Cline.LOCAL, Cline.HIDE) cl.register('height', Cline.LOCAL, Cline.HIDE) cl.register('run', Cline.GLOBAL, Cline.PROMPT) cl.register('first', Cline.LOCAL, Cline.PROMPT) cl.register('trim', Cline.GLOBAL, Cline.PROMPT) cl.register('ncol', Cline.GLOBAL, Cline.HIDE) cl.register('nrow', Cline.GLOBAL, Cline.HIDE) cl.register('twait', Cline.LOCAL, Cline.HIDE) cl.register('tmax', Cline.LOCAL, Cline.HIDE) cl.register('flist', Cline.LOCAL, Cline.PROMPT) cl.register('ccd', Cline.LOCAL, Cline.PROMPT) cl.register('pause', Cline.LOCAL, Cline.HIDE) cl.register('plotall', Cline.LOCAL, Cline.HIDE) cl.register('nx', Cline.LOCAL, Cline.PROMPT) cl.register('bias', Cline.GLOBAL, Cline.PROMPT) cl.register('flat', Cline.GLOBAL, Cline.PROMPT) cl.register('defect', Cline.GLOBAL, Cline.PROMPT) cl.register('setup', Cline.GLOBAL, Cline.PROMPT) cl.register('hurl', Cline.GLOBAL, Cline.HIDE) cl.register('msub', Cline.GLOBAL, Cline.PROMPT) cl.register('iset', Cline.GLOBAL, Cline.PROMPT) cl.register('ilo', Cline.GLOBAL, Cline.PROMPT) cl.register('ihi', Cline.GLOBAL, Cline.PROMPT) cl.register('plo', Cline.GLOBAL, Cline.PROMPT) cl.register('phi', Cline.LOCAL, Cline.PROMPT) cl.register('xlo', Cline.GLOBAL, Cline.PROMPT) cl.register('xhi', Cline.GLOBAL, Cline.PROMPT) cl.register('ylo', Cline.GLOBAL, Cline.PROMPT) cl.register('yhi', Cline.GLOBAL, Cline.PROMPT) cl.register('profit', Cline.LOCAL, Cline.PROMPT) cl.register('fdevice', Cline.LOCAL, Cline.HIDE) cl.register('fwidth', Cline.LOCAL, Cline.HIDE) cl.register('fheight', Cline.LOCAL, Cline.HIDE) cl.register('method', Cline.LOCAL, Cline.HIDE) cl.register('beta', Cline.LOCAL, Cline.HIDE) cl.register('fwhm', Cline.LOCAL, Cline.HIDE) cl.register('fwhm_min', Cline.LOCAL, Cline.HIDE) cl.register('shbox', Cline.LOCAL, Cline.HIDE) cl.register('smooth', Cline.LOCAL, Cline.HIDE) cl.register('splot', Cline.LOCAL, Cline.HIDE) cl.register('fhbox', Cline.LOCAL, Cline.HIDE) cl.register('hmin', Cline.LOCAL, Cline.HIDE) cl.register('read', Cline.LOCAL, Cline.HIDE) cl.register('gain', Cline.LOCAL, Cline.HIDE) cl.register('thresh', Cline.LOCAL, Cline.HIDE) # get inputs source = cl.get_value('source', 'data source [hs, hl, us, ul, hf]', 'hl', lvals=('hs','hl','us','ul','hf')) # set some flags server_or_local = source.endswith('s') or source.endswith('l') # plot device stuff device = cl.get_value('device', 'plot device', '1/xs') width = cl.get_value('width', 'plot width (inches)', 0.) height = cl.get_value('height', 'plot height (inches)', 0.) if server_or_local: resource = cl.get_value('run', 'run name', 'run005') if source == 'hs': first = cl.get_value('first', 'first frame to plot', 1) else: first = cl.get_value('first', 'first frame to plot', 1, 0) if source.startswith('u'): trim = cl.get_value( 'trim', 'do you want to trim edges of windows? (ULTRACAM only)', True ) if trim: ncol = cl.get_value( 'ncol', 'number of columns to trim from windows', 0) nrow = cl.get_value( 'nrow', 'number of rows to trim from windows', 0) else: trim = False twait = cl.get_value( 'twait', 'time to wait for a new frame [secs]', 1., 0.) tmax = cl.get_value( 'tmax', 'maximum time to wait for a new frame [secs]', 10., 0.) else: resource = cl.get_value( 'flist', 'file list', cline.Fname('files.lis',hcam.LIST) ) first = 1 trim = False # define the panel grid. first get the labels and maximum dimensions ccdinf = spooler.get_ccd_pars(source, resource) try: nxdef = cl.get_default('nx') except: nxdef = 3 if len(ccdinf) > 1: ccd = cl.get_value('ccd', 'CCD(s) to plot [0 for all]', '0') if ccd == '0': ccds = list(ccdinf.keys()) else: ccds = ccd.split() check = set(ccdinf.keys()) if not set(ccds) <= check: raise hcam.HipercamError( 'At least one invalid CCD label supplied' ) if len(ccds) > 1: nxdef = min(len(ccds), nxdef) cl.set_default('nx', nxdef) nx = cl.get_value('nx', 'number of panels in X', 3, 1) else: nx = 1 else: nx = 1 ccds = list(ccdinf.keys()) cl.set_default('pause', 0.) pause = cl.get_value( 'pause', 'time delay to add between' ' frame plots [secs]', 0., 0. ) cl.set_default('plotall', False) plotall = cl.get_value('plotall', 'plot all frames,' ' regardless of status?', False) # bias frame (if any) bias = cl.get_value( 'bias', "bias frame ['none' to ignore]", cline.Fname('bias', hcam.HCAM), ignore='none' ) if bias is not None: # read the bias frame bias = hcam.MCCD.read(bias) fprompt = "flat frame ['none' to ignore]" else: fprompt = "flat frame ['none' is normal choice with no bias]" # flat (if any) flat = cl.get_value( 'flat', fprompt, cline.Fname('flat', hcam.HCAM), ignore='none' ) if flat is not None: # read the flat frame flat = hcam.MCCD.read(flat) # defect file (if any) dfct = cl.get_value( 'defect', "defect file ['none' to ignore]", cline.Fname('defect', hcam.DFCT), ignore='none' ) if dfct is not None: # read the defect frame dfct = defect.MccdDefect.read(dfct) # Get windows from hdriver setup = cl.get_value( 'setup', 'display current hdriver window settings', False ) if setup: hurl = cl.get_value( 'hurl', 'URL for hdriver windows', 'http://192.168.1.2:5100' ) # define the display intensities msub = cl.get_value('msub', 'subtract median from each window?', True) iset = cl.get_value( 'iset', 'set intensity a(utomatically),' ' d(irectly) or with p(ercentiles)?', 'a', lvals=['a','d','p']) iset = iset.lower() plo, phi = 5, 95 ilo, ihi = 0, 1000 if iset == 'd': ilo = cl.get_value('ilo', 'lower intensity limit', 0.) ihi = cl.get_value('ihi', 'upper intensity limit', 1000.) elif iset == 'p': plo = cl.get_value('plo', 'lower intensity limit percentile', 5., 0., 100.) phi = cl.get_value('phi', 'upper intensity limit percentile', 95., 0., 100.) # region to plot for i, cnam in enumerate(ccds): nxtot, nytot, nxpad, nypad = ccdinf[cnam] if i == 0: xmin, xmax = float(-nxpad), float(nxtot + nxpad + 1) ymin, ymax = float(-nypad), float(nytot + nypad + 1) else: xmin = min(xmin, float(-nxpad)) xmax = max(xmax, float(nxtot + nxpad + 1)) ymin = min(ymin, float(-nypad)) ymax = max(ymax, float(nytot + nypad + 1)) xlo = cl.get_value('xlo', 'left-hand X value', xmin, xmin, xmax) xhi = cl.get_value('xhi', 'right-hand X value', xmax, xmin, xmax) ylo = cl.get_value('ylo', 'lower Y value', ymin, ymin, ymax) yhi = cl.get_value('yhi', 'upper Y value', ymax, ymin, ymax) # profile fitting if just one CCD chosen if len(ccds) == 1: # many parameters for profile fits, although most are not plotted # by default profit = cl.get_value('profit', 'do you want profile fits?', False) if profit: fdevice = cl.get_value('fdevice', 'plot device for fits', '2/xs') fwidth = cl.get_value('fwidth', 'fit plot width (inches)', 0.) fheight = cl.get_value( 'fheight', 'fit plot height (inches)', 0.) method = cl.get_value( 'method', 'fit method g(aussian) or m(offat)', 'm', lvals=['g','m']) if method == 'm': beta = cl.get_value( 'beta', 'initial exponent for Moffat fits', 5., 0.5) else: beta = 0. fwhm_min = cl.get_value( 'fwhm_min', 'minimum FWHM to allow [unbinned pixels]', 1.5, 0.01 ) fwhm = cl.get_value( 'fwhm', 'initial FWHM [unbinned pixels] for profile fits', 6., fwhm_min ) shbox = cl.get_value( 'shbox', 'half width of box for initial location' ' of target [unbinned pixels]', 11., 2.) smooth = cl.get_value( 'smooth', 'FWHM for smoothing for initial object' ' detection [binned pixels]', 6.) splot = cl.get_value( 'splot', 'plot outline of search box?', True) fhbox = cl.get_value( 'fhbox', 'half width of box for profile fit' ' [unbinned pixels]', 21., 3.) hmin = cl.get_value( 'hmin', 'minimum peak height to accept the fit', 50.) read = cl.get_value('read', 'readout noise, RMS ADU', 3.) gain = cl.get_value('gain', 'gain, ADU/e-', 1.) thresh = cl.get_value('thresh', 'number of RMS to reject at', 4.) else: profit = False ################################################################ # # all the inputs have now been obtained. Get on with doing stuff # open image plot device imdev = hcam.pgp.Device(device) if width > 0 and height > 0: pgpap(width,height/width) # set up panels and axes nccd = len(ccds) ny = nccd // nx if nccd % nx == 0 else nccd // nx + 1 # slice up viewport pgsubp(nx,ny) # plot axes, labels, titles. Happens once only for cnam in ccds: pgsci(hcam.pgp.Params['axis.ci']) pgsch(hcam.pgp.Params['axis.number.ch']) pgenv(xlo, xhi, ylo, yhi, 1, 0) pglab('X','Y','CCD {:s}'.format(cnam)) # initialisations. 'last_ok' is used to store the last OK frames of each # CCD for retrieval when coping with skipped data. total_time = 0 # time waiting for new frame fpos = [] # list of target positions to fit fframe = True # waiting for first valid frame with profit # plot images with spooler.data_source(source, resource, first, full=False) as spool: # 'spool' is an iterable source of MCCDs n = 0 for mccd in spool: if server_or_local: # Handle the waiting game ... give_up, try_again, total_time = spooler.hang_about( mccd, twait, tmax, total_time ) if give_up: print('rtplot stopped') break elif try_again: continue # Trim the frames: ULTRACAM windowed data has bad columns # and rows on the sides of windows closest to the readout # which can badly affect reduction. This option strips # them. if trim: hcam.ccd.trim_ultracam(mccd, ncol, nrow) # indicate progress tstamp = Time(mccd.head['TIMSTAMP'], format='isot', precision=3) print( '{:d}, utc = {:s}, '.format( mccd.head['NFRAME'], tstamp.iso), end='' ) # accumulate errors emessages = [] if n == 0: if bias is not None: # crop the bias on the first frame only bias = bias.crop(mccd) if flat is not None: # crop the flat on the first frame only flat = flat.crop(mccd) if setup: # Get windows from hdriver. Fair bit of error checking # needed. 'got_windows' indicates if anything useful # found, 'hwindows' is a list of (llx,lly,nx,ny) tuples # if somthing is found. try: r = requests.get( 'http://192.168.1.2:5100',timeout=0.2 ) if r.text.strip() == 'No valid data available': emessages.append( '** bad return from hdriver = {:s}'.format(r.text.strip()) ) got_windows = False elif r.text.strip() == 'fullframe': # to help Stu out a bit, effectively just ignore this one got_windows = False else: # OK, got something lines = r.text.split('\r\n') xbinh,ybinh,nwinh = lines[0].split() xbinh,ybinh,nwinh = int(xbinh),int(ybinh),int(nwinh) hwindows = [] for line in lines[1:nwinh+1]: llxh,llyh,nxh,nyh = line.split() hwindows.append( (int(llxh),int(llyh),int(nxh),int(nyh)) ) if nwinh != len(hwindows): emessages.append( ('** expected {:d} windows from' ' hdriver but got {:d}').format(nwinh,len(hwindows)) ) got_windows = False got_windows = True except (requests.exceptions.ConnectionError, socket.timeout, requests.exceptions.Timeout) as err: emessages.append(' ** hdriver error: {!r}'.format(err)) got_windows = False else: got_windows = False # display the CCDs chosen message = '' pgbbuf() for nc, cnam in enumerate(ccds): ccd = mccd[cnam] if plotall or ccd.is_data(): # this should be data as opposed to a blank frame # between data frames that occur with nskip > 0 # subtract the bias if bias is not None: ccd -= bias[cnam] # divide out the flat if flat is not None: ccd /= flat[cnam] if msub: # subtract median from each window for wind in ccd.values(): wind -= wind.median() # set to the correct panel and then plot CCD ix = (nc % nx) + 1 iy = nc // nx + 1 pgpanl(ix,iy) vmin, vmax = hcam.pgp.pCcd( ccd,iset,plo,phi,ilo,ihi, xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi ) if got_windows: # plot the current hdriver windows pgsci(hcam.CNAMS['yellow']) pgsls(2) for llxh,llyh,nxh,nyh in hwindows: pgrect(llxh-0.5,llxh+nxh-0.5,llyh-0.5,llyh+nyh-0.5) if dfct is not None and cnam in dfct: # plot defects hcam.pgp.pCcdDefect(dfct[cnam]) # accumulate string of image scalings if nc: message += ', ccd {:s}: {:.1f}, {:.1f}, exp: {:.4f}'.format( cnam,vmin,vmax,mccd.head['EXPTIME'] ) else: message += 'ccd {:s}: {:.1f}, {:.1f}, exp: {:.4f}'.format( cnam,vmin,vmax,mccd.head['EXPTIME'] ) pgebuf() # end of CCD display loop print(message) for emessage in emessages: print(emessage) if ccd.is_data() and profit and fframe: fframe = False # cursor selection of targets after first plot, if profit # accumulate list of starter positions print('Please select targets for profile fitting. You can select as many as you like.') x, y, reply = (xlo+xhi)/2, (ylo+yhi)/2, '' ntarg = 0 pgsci(2) pgslw(2) while reply != 'Q': print("Place cursor on fit target. Any key to register, 'q' to quit") x, y, reply = pgcurs(x, y) if reply == 'q': break else: # check that the position is inside a window wnam = ccd.inside(x, y, 2) if wnam is not None: # store the position, Window label, target number, # box size fwhm, beta ntarg += 1 fpos.append(Fpar(x,y,wnam,ntarg,shbox,fwhm,beta)) # report information, overplot search box print( ('Target {:d} selected at {:.1f},' '{:.1f} in window {:s}').format(ntarg,x,y,wnam) ) if splot: fpos[-1].plot() if len(fpos): print(len(fpos),'targets selected') # if some targets were selected, open the fit plot device fdev = hcam.pgp.Device(fdevice) if fwidth > 0 and fheight > 0: pgpap(fwidth,fheight/fwidth) if ccd.is_data(): # carry out fits. Nothing happens if fpos is empty for fpar in fpos: # switch to the image plot imdev.select() # plot search box if splot: fpar.plot() try: # extract search box from the CCD. 'fpar' is updated later # if the fit is successful to reflect the new position swind = fpar.swind(ccd) # carry out initial search x,y,peak = swind.search(smooth, fpar.x, fpar.y, hmin, False) # now for a more refined fit. First extract fit Window fwind = ccd[fpar.wnam].window( x-fhbox, x+fhbox, y-fhbox, y+fhbox ) # crude estimate of sky background sky = np.percentile(fwind.data, 50) # refine the Aperture position by fitting the profile (sky, height, x, y, fwhm, beta), epars, \ (wfit, X, Y, sigma, chisq, nok, nrej, npar, message) = hcam.fitting.combFit( fwind, method, sky, peak-sky, x, y, fpar.fwhm, fwhm_min, False, fpar.beta, read, gain, thresh ) print('Targ {:d}: {:s}'.format(fpar.ntarg,message)) if peak > hmin and ccd[fpar.wnam].distance(x,y) > 1: # update some initial parameters for next time if method == 'g': fpar.x, fpar.y, fpar.fwhm = x, y, fwhm elif method == 'm': fpar.x, fpar.y, fpar.fwhm, \ fpar.beta = x, y, fwhm, beta # plot values versus radial distance ok = sigma > 0 R = np.sqrt((X-x)**2+(Y-y)**2) fdev.select() vmin = min(sky, sky+height, fwind.min()) vmax = max(sky, sky+height, fwind.max()) extent = vmax-vmin pgeras() pgvstd() pgswin(0,R.max(),vmin-0.05*extent,vmax+0.05*extent) pgsci(4) pgbox('bcnst',0,0,'bcnst',0,0) pgsci(2) pglab('Radial distance [unbinned pixels]','Counts','') pgsci(1) pgpt(R[ok].flat, fwind.data[ok].flat, 1) if nrej: pgsci(2) pgpt(R[~ok].flat, fwind.data[~ok].flat, 5) # line fit pgsci(3) r = np.linspace(0,R.max(),400) if method == 'g': alpha = 4*np.log(2.)/fwhm**2 f = sky+height*np.exp(-alpha*r**2) elif method == 'm': alpha = 4*(2**(1/beta)-1)/fwhm**2 f = sky+height/(1+alpha*r**2)**beta pgline(r,f) # back to the image to plot a circle of radius FWHM imdev.select() pgsci(3) pgcirc(x,y,fwhm) else: print(' *** below detection threshold; position & FWHM will not updated') pgsci(2) # plot location on image as a cross pgpt1(x, y, 5) except hcam.HipercamError as err: print(' >> Targ {:d}: fit failed ***: {!s}'.format( fpar.ntarg, err) ) pgsci(2) if pause > 0.: # pause between frames time.sleep(pause) # update the frame number n += 1
def psf_reduce(args=None): """``psf_reduce [source] rfile (run first twait tmax | flist) log lplot implot (ccd nx msub xlo xhi ylo yhi iset (ilo ihi | plo phi))`` Performs PSF photometry on a sequence of multi-CCD images, plotting lightcurves as images come in. It performs PSF photometry on specific targets, defined in an aperture file using |psf_setaper| or |setaper|. Aperture repositioning is identical to |reduce|, and the PSF used in photometry is set by fitting well separated reference stars. For the best results, a suitable strategy is to only reduce data from a small window near the target. Set well separated bright stars as reference apertures and link fainter, or blended objects to the references. |psf_reduce| can source data from both the ULTRACAM and HiPERCAM servers, from local 'raw' ULTRACAM and HiPERCAM files (i.e. .xml + .dat for ULTRACAM, 3D FITS files for HiPERCAM) and from lists of HiPERCAM '.hcm' files. If you have data from a different instrument you should convert into the FITS-based hcm format. |psf_reduce| is primarily configured from a file with extension ".red". This contains a series of directives, e.g. to say how to re-position the apertures. An initial reduce file is best generated with the script |genred| after you have created an aperture file. This contains lots of help on what to do. A |psf_reduce| run can be terminated at any point with ctrl-C without doing any harm. You may often want to do this at the start in order to adjust parameters of the reduce file. Parameters: source : string [hidden] Data source, five options: | 'hs': HiPERCAM server | 'hl': local HiPERCAM FITS file | 'us': ULTRACAM server | 'ul': local ULTRACAM .xml/.dat files | 'hf': list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. rfile : string the "reduce" file, i.e. ASCII text file suitable for reading by ConfigParser. Best seen by example as it has many parts. Generate a starter file with |genred|. run : string [if source ends 's' or 'l'] run number to access, e.g. 'run034' first : int [if source ends 's' or 'l'] exposure number to start from. 1 = first frame; set = 0 to always try to get the most recent frame (if it has changed) last : int [if source ends 's' or 'l', hidden] last frame to reduce. 0 to just continue until the end. This is not prompted for by default and must be set explicitly. It defaults to 0 if not set. Its purpose is to allow accurate profiling tests. twait : float [if source ends 's'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's'; hidden] maximum time to wait between attempts to find a new exposure, seconds. flist : string [if source ends 'f'] name of file list log : string log file for the results tkeep : float maximum number of minutes of data to store in internal buffers, 0 for the lot. When large numbers of frames are stored, performance can be slowed (although I am not entirely clear why) in which case it makes sense to lose the earlier points (without affecting the saving to disk). This parameter also gives operation similar to that of "max_xrange" parameter in the ULTRACAM pipeline whereby just the last few minutes are shown. lplot : bool flag to indicate you want to plot the light curve. Saves time not to especially in high-speed runs. implot : bool flag to indicate you want to plot images. ccd : string [if implot] CCD(s) to plot, '0' for all, '1 3' to plot '1' and '3' only, etc. nx : int [if implot] number of panels across to display. msub : bool [if implot] subtract the median from each window before scaling for the image display or not. This happens after any bias subtraction. xlo : float [if implot] left-hand X-limit for plot xhi : float [if implot] right-hand X-limit for plot (can actually be < xlo) ylo : float [if implot] lower Y-limit for plot yhi : float [if implot] upper Y-limit for plot (can be < ylo) iset : string [if implot] determines how the intensities are determined. There are three options: 'a' for automatic simply scales from the minimum to the maximum value found on a per CCD basis. 'd' for direct just takes two numbers from the user. 'p' for percentile dtermines levels based upon percentiles determined from the entire CCD on a per CCD basis. ilo : float [if implot and iset='d'] lower intensity level ihi : float [if implot and iset='d'] upper intensity level plo : float [if implot and iset='p'] lower percentile level phi : float [if implot and iset='p'] upper percentile level """ command, args = utils.script_args(args) with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("source", Cline.GLOBAL, Cline.HIDE) cl.register("rfile", Cline.GLOBAL, Cline.PROMPT) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("last", Cline.LOCAL, Cline.HIDE) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("log", Cline.GLOBAL, Cline.PROMPT) cl.register("tkeep", Cline.GLOBAL, Cline.PROMPT) cl.register("lplot", Cline.LOCAL, Cline.PROMPT) cl.register("implot", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("nx", Cline.LOCAL, Cline.PROMPT) cl.register("msub", Cline.GLOBAL, Cline.PROMPT) cl.register("iset", Cline.GLOBAL, Cline.PROMPT) cl.register("ilo", Cline.GLOBAL, Cline.PROMPT) cl.register("ihi", Cline.GLOBAL, Cline.PROMPT) cl.register("plo", Cline.GLOBAL, Cline.PROMPT) cl.register("phi", Cline.LOCAL, Cline.PROMPT) cl.register("xlo", Cline.GLOBAL, Cline.PROMPT) cl.register("xhi", Cline.GLOBAL, Cline.PROMPT) cl.register("ylo", Cline.GLOBAL, Cline.PROMPT) cl.register("yhi", Cline.GLOBAL, Cline.PROMPT) # get inputs source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", "hl", lvals=("hs", "hl", "us", "ul", "hf"), ) # set some flags server_or_local = source.endswith("s") or source.endswith("l") # the reduce file rfilen = cl.get_value("rfile", "reduce file", cline.Fname("reduce.red", hcam.RED)) try: rfile = Rfile.read(rfilen) except hcam.HipercamError as err: # abort on failure to read as there are many ways to get reduce # files wrong print(err, file=sys.stderr) exit(1) if server_or_local: resource = cl.get_value("run", "run name", "run005") first = cl.get_value("first", "first frame to reduce", 1, 0) cl.set_default("last", 0) last = cl.get_value("last", "last frame to reduce", 0, 0) if last and last < first: print("Cannot set last < first unless last == 0") print("*** psf_reduce aborted") exit(1) twait = cl.get_value("twait", "time to wait for a new frame [secs]", 1.0, 0.0) tmx = cl.get_value("tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0) else: resource = cl.get_value("flist", "file list", cline.Fname("files.lis", hcam.LIST)) first = 1 last = 0 log = cl.get_value( "log", "name of log file to store results", cline.Fname("reduce.log", hcam.LOG, cline.Fname.NEW), ) tkeep = cl.get_value( "tkeep", "number of minute of data to" " keep in internal buffers (0 for all)", 0.0, 0.0, ) lplot = cl.get_value("lplot", "do you want to plot light curves?", True) implot = cl.get_value("implot", "do you want to plot images?", True) if implot: # define the panel grid. first get the labels and maximum # dimensions ccdinf = spooler.get_ccd_pars(source, resource) nxdef = cl.get_default("nx", 3) if len(ccdinf) > 1: ccd = cl.get_value("ccd", "CCD(s) to plot [0 for all]", "0") if ccd == "0": ccds = list(ccdinf.keys()) else: ccds = ccd.split() if len(ccds) > 1: nxdef = min(len(ccds), nxdef) cl.set_default("nx", nxdef) nx = cl.get_value("nx", "number of panels in X", 3, 1) else: nx = 1 else: nx = 1 ccds = list(ccdinf.keys()) # define the display intensities msub = cl.get_value("msub", "subtract median from each window?", True) iset = cl.get_value( "iset", "set intensity a(utomatically)," " d(irectly) or with p(ercentiles)?", "a", lvals=["a", "d", "p"], ) plo, phi = 5, 95 ilo, ihi = 0, 1000 if iset == "d": ilo = cl.get_value("ilo", "lower intensity limit", 0.0) ihi = cl.get_value("ihi", "upper intensity limit", 1000.0) elif iset == "p": plo = cl.get_value("plo", "lower intensity limit percentile", 5.0, 0.0, 100.0) phi = cl.get_value("phi", "upper intensity limit percentile", 95.0, 0.0, 100.0) # region to plot for i, cnam in enumerate(ccds): nxtot, nytot, nxpad, nypad = ccdinf[cnam] if i == 0: xmin, xmax = float(-nxpad), float(nxtot + nxpad + 1) ymin, ymax = float(-nypad), float(nytot + nypad + 1) else: xmin = min(xmin, float(-nxpad)) xmax = max(xmax, float(nxtot + nxpad + 1)) ymin = min(ymin, float(-nypad)) ymax = max(ymax, float(nytot + nypad + 1)) xlo = cl.get_value("xlo", "left-hand X value", xmin, xmin, xmax, enforce=False) xhi = cl.get_value("xhi", "right-hand X value", xmax, xmin, xmax, enforce=False) ylo = cl.get_value("ylo", "lower Y value", ymin, ymin, ymax, enforce=False) yhi = cl.get_value("yhi", "upper Y value", ymax, ymin, ymax, enforce=False) else: xlo, xhi, ylo, yhi = None, None, None, None # save list of parameter values for writing to the reduction file plist = cl.list() ################################################################ # # all the inputs have now been obtained. Get on with doing stuff if implot: plot_lims = (xlo, xhi, ylo, yhi) else: plot_lims = None imdev, lcdev, spanel, tpanel, xpanel, ypanel, lpanel = setup_plots( rfile, ccds, nx, plot_lims, implot, lplot) # a couple of initialisations total_time = 0 # time waiting for new frame # dictionary of dictionaries for looking up the window associated with a # given aperture, i.e. mccdwins[cnam][apnam] give the name of the Window. mccdwins = {} if lplot: lbuffer, xbuffer, ybuffer, tbuffer, sbuffer = setup_plot_buffers(rfile) else: lbuffer, xbuffer, ybuffer, tbuffer, sbuffer = None, None, None, None, None ############################################ # # open the log file and write headers # with LogWriter(log, rfile, hipercam_version, plist) as logfile: ncpu = rfile["general"]["ncpu"] if ncpu > 1: pool = multiprocessing.Pool(processes=ncpu) else: pool = None # whether settings have been initialised initialised = False # containers for the processed and raw MCCD groups # and their frame numbers pccds, mccds, nframes = [], [], [] ############################################## # # Finally, start winding through the frames # with spooler.data_source(source, resource, first, full=False) as spool: # 'spool' is an iterable source of MCCDs for nf, mccd in enumerate(spool): if server_or_local: # Handle the waiting game ... give_up, try_again, total_time = spooler.hang_about( mccd, twait, tmx, total_time) if give_up: # Giving up, but need to handle any partially filled # frame group if len(mccds): # finish processing remaining frames. This step # will only occur if we have at least once passed # to later stages during which read and gain will # be set up results = processor(pccds, mccds, nframes) # write out results to the log file alerts = logfile.write_results(results) # print out any accumulated alert messages if len(alerts): print("\n".join(alerts)) update_plots( results, rfile, implot, lplot, imdev, lcdev, pccd, ccds, msub, nx, iset, plo, phi, ilo, ihi, xlo, xhi, ylo, yhi, lpanel, xpanel, ypanel, tpanel, spanel, tkeep, lbuffer, xbuffer, ybuffer, tbuffer, sbuffer, ) print("psf_reduce stopped") break elif try_again: continue # indicate progress if "NFRAME" in mccd.head: nframe = mccd.head["NFRAME"] else: nframe = nf + 1 if last and nframe > last: if len(mccds): # finish processing remaining frames results = processor(pccds, mccds, nframes) # write out results to the log file alerts = logfile.write_results(results) # print out any accumulated alert messages if len(alerts): print("\n".join(alerts)) update_plots( results, rfile, implot, lplot, imdev, lcdev, pccd, ccds, msub, nx, iset, plo, phi, ilo, ihi, xlo, xhi, ylo, yhi, lpanel, xpanel, ypanel, tpanel, spanel, tkeep, lbuffer, xbuffer, ybuffer, tbuffer, sbuffer, ) print("\nHave reduced up to the last frame set.") print("psf_reduce finished") break print( "Frame {:d}: {:s} [{:s}]".format( nframe, mccd.head["TIMSTAMP"], "OK" if mccd.head.get("GOODTIME", True) else "NOK", ), end="" if implot else "\n", ) if not initialised: # This is the first frame which allows us to make # some checks and initialisations. read, gain, ok = initial_checks(mccd, rfile) # Define the CCD processor function object processor = ProcessCCDs(rfile, read, gain, ccdproc, pool) # set flag to show we are set if not ok: break initialised = True # De-bias the data. Retain a copy of the raw data as 'mccd' # in order to judge saturation. Processed data called 'pccd' if rfile.bias is not None: # subtract bias pccd = mccd - rfile.bias else: # no bias subtraction pccd = mccd.copy() if rfile.flat is not None: # apply flat field to processed frame pccd /= rfile.flat # Acummulate frames into processing groups for faster # parallelisation pccds.append(pccd) mccds.append(mccd) nframes.append(nframe) if len(pccds) == rfile["general"]["ngroup"]: # parallel processing. This should usually be the first # points at which it takes place results = processor(pccds, mccds, nframes) # write out results to the log file alerts = logfile.write_results(results) # print out any accumulated alert messages if len(alerts): print("\n".join(alerts)) update_plots( results, rfile, implot, lplot, imdev, lcdev, pccds[-1], ccds, msub, nx, iset, plo, phi, ilo, ihi, xlo, xhi, ylo, yhi, lpanel, xpanel, ypanel, tpanel, spanel, tkeep, lbuffer, xbuffer, ybuffer, tbuffer, sbuffer, ) # Reset the frame buffers pccds, mccds, nframes = [], [], []
def genred(args=None): """``genred apfile rfile bias dark flat fmap fpair seeing (sworst binfac) template (inst (nccd (ccd) nonlin sat scale readout gain))`` Generates a reduce file as needed by |reduce| or |psf_reduce|. You give it the name of an aperture file, calibration frames and a few other parameters and it will write out a reduce file which you can then refine by hand. The aim is to try to get a reduce file that is self-consistent and works to get observing started as soon as possible. The parameters are not necessarily the best. It is assumed that the target is called '1', the main comparison '2'. To avoid endless prompts, genred does not try to prompt for all parameters even via hidden ones (a change from its previous behaviour). However, if you find yourself always making the samed edits to the file produced by genred, you may start wanting a way to alter some of the otherwise fixed parameters. You can do so by supplying a template file, which can just be from a previous run of genred where such parameters can be altered by editing with a standard text editor before running genred. genred recognises some standard instrument telescope combinations which it uses to set parameters such as the pixel scale and readout noise, but if you choose 'other', you will be prompted for these details. The main parameters it does prompt for are calibration files, and it allows you to define a "seeing" which then controls the setting of various profile fit parameters. This can be ignored by setting = 0, in which case any template parameters will be passed unchanged. Parameters: apfile : str the input aperture file created using |setaper| (default extension .ape). This will be read for the targets. The main target will be assumed to have been called '1', the main comparison '2'. If there is a '3' it will be plotted relative to '2'; all others will be ignored for plotting purposes. Target '2' will be used to define the position and transmission plots for one CCD only [user definable]. Target '1' will be used for the seeing plot unless it is linked when target '2' will be used instead. rfile : str the output reduce file created by this script. The main target will be assumed to have been called '1', the main comparison '2'. If there is a '3' it will be plotted relative to '2'; all others will be ignored for plotting purposes. bias : str Name of bias frame; 'none' to ignore. dark : str Name of dark frame; 'none' to ignore. flat : str Name of flat field frame; 'none' to ignore. fmap : str Name of fringe frame; 'none' to ignore. fpair : str [if fmap not 'none'] File defining pairs to measure fringe amplitudes. seeing : float estimate of seeing which will be used to define several of the profile fitting parameters. Enter 0 to ignore and use defaults from the template (or genred if no template) instead. sworst : float [if seeing > 0] worst seeing expected during run. Expands the search and fitting boxes which can cause difficulties if they are too small. It also sets the maximum target aperture radius which will set to ~ 1.8*sworst, converted to pixels, and the sky annulus radii. It has to be at least 2*seeing. Always remember that you can override the automated settings using a pre-edited template and seeing = 0. binfac : int [if seeing > 0] binning factor. e.g. 4 if using 4x4. Needed to optimise some profile parameters. template : str Reduce file to use as a template for any parameters not set by genred. This allows one to tweak settings by running genred without a template, then modifying the resultant file and using it as a template. 'none' to ignore, and then these values will be set by genred by default. Some will be modified anyway (e.g. the calibration file names), and if seeing > 0, then several of the profile fitting paramaters will be adapted to the value given along with the instrument parameters. genred will try to keep as many of the 'extraction', 'position', 'transmission', 'light' etc lines associated with particular CCDs in this case without modification, and will pass readout, saturation levels etc unchanged from the template. inst : str [if template == 'none'] the instrument. 'hipercam-gtc', 'ultracam-ntt', 'ultraspec-tnt', 'ultracam-wht', 'ultracam-vlt', or 'other'. Sets pixel scale and standard readnoise params. nccd : int [if inst == 'other'] number of CCDs. ccd : int [if nccd > 1] the CCD to use for position plots nonlin : list(float) [if inst == 'other'] values for warning of non linearity sat : list(float) [if inst == 'other'] values for warning of saturation scale : float [if inst == 'other'] image scale in arcsec/pixel readout : float | string [if inst == 'other'] readout noise, RMS ADU. Can either be a single value or an hcm file. gain : float | string [if inst == 'other'] gain, electrons per ADU. Can either be a single value or an hcm file. """ command, args = utils.script_args(args) with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("apfile", Cline.LOCAL, Cline.PROMPT) cl.register("rfile", Cline.GLOBAL, Cline.PROMPT) cl.register("comment", Cline.LOCAL, Cline.PROMPT) cl.register("bias", Cline.LOCAL, Cline.PROMPT) cl.register("dark", Cline.LOCAL, Cline.PROMPT) cl.register("flat", Cline.LOCAL, Cline.PROMPT) cl.register("fmap", Cline.LOCAL, Cline.PROMPT) cl.register("fpair", Cline.LOCAL, Cline.PROMPT) cl.register("seeing", Cline.LOCAL, Cline.PROMPT) cl.register("sworst", Cline.LOCAL, Cline.PROMPT) cl.register("binfac", Cline.LOCAL, Cline.PROMPT) cl.register("inst", Cline.LOCAL, Cline.PROMPT) cl.register("nccd", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("nonlin", Cline.LOCAL, Cline.PROMPT) cl.register("sat", Cline.LOCAL, Cline.PROMPT) cl.register("scale", Cline.LOCAL, Cline.PROMPT) cl.register("readout", Cline.LOCAL, Cline.PROMPT) cl.register("gain", Cline.LOCAL, Cline.PROMPT) cl.register("template", Cline.LOCAL, Cline.PROMPT) # get inputs # the aperture file apfile = cl.get_value( "apfile", "aperture input file", cline.Fname("aper.ape", hcam.APER) ) # Read the aperture file aper = hcam.MccdAper.read(apfile) # the reduce file root = os.path.splitext(os.path.basename(apfile))[0] cl.set_default("rfile", cline.Fname(root, hcam.RED, cline.Fname.NEW)) rfile = cl.get_value( "rfile", "reduce output file", cline.Fname("reduce.red", hcam.RED, cline.Fname.NEW), ) # calibrations # bias frame bias = cl.get_value( "bias", "bias frame ['none' to ignore]", cline.Fname("bias", hcam.HCAM), ignore="none", ) # dark frame dark = cl.get_value( "dark", "dark field frame ['none' to ignore]", cline.Fname("dark", hcam.HCAM), ignore="none", ) # flat field frame flat = cl.get_value( "flat", "flat field frame ['none' to ignore]", cline.Fname("flat", hcam.HCAM), ignore="none", ) # fringe frame fmap = cl.get_value( "fmap", "fringe frame ['none' to ignore]", cline.Fname("fringe", hcam.HCAM), ignore="none", ) if fmap is not None: fpair = cl.get_value( "fpair", "fringe pair file", cline.Fname("fpair", hcam.FRNG), ) else: fpair = None seeing = cl.get_value( "seeing", "approximate seeing [arcsec, 0 to ignore]", 1.0, 0. ) if seeing > 0.: sworst = cl.get_value( "sworst", "worst expected seeing [arcsec]", max(3., 2.5*seeing), 2.*seeing ) binfac = cl.get_value( "binfac", "binning factor", 1, 1 ) # template template = cl.get_value( "template", "template reduce file ['none' to ignore]", cline.Fname("template", hcam.RED), ignore="none", ) if template is None: instrument = cl.get_value( "inst", "the instrument-telescope", "hipercam-gtc", lvals=[ "hipercam-gtc", "ultracam-ntt", "ultracam-wht", "ultracam-vlt", "ultraspec-tnt", "other" ], ) if instrument.startswith("hipercam"): warn_levels = """ warn = 1 50000 64000 warn = 2 50000 64000 warn = 3 50000 64000 warn = 4 50000 64000 warn = 5 50000 64000 """ maxcpu = 5 skipbadt = False scale = 0.081 readout = '4.2' gain = '1.1' nccd = 5 ccd = '2' elif instrument.startswith("ultracam"): warn_levels = """ warn = 1 28000 64000 warn = 2 28000 64000 warn = 3 50000 64000 """ maxcpu = 3 skipbadt = True readout = '2.8' gain = '1.1' nccd = 3 ccd = '2' if instrument.endswith("ntt"): scale = 0.35 elif instrument.endswith("wht"): scale = 0.30 elif instrument.endswith("vlt"): scale = 0.15 elif instrument.startswith("ultraspec"): warn_levels = """ warn = 1 60000 64000 """ maxcpu = 1 skipbadt = True scale = 0.45 readout = '5.0' gain = '0.8' nccd = 1 ccd = '1' elif instrument == "other": # unrecognised instrument need extra stuff nccd = cl.get_value("nccd", "how many CCDs?", 3, 1) if nccd > 1: ccd = cl.get_value("ccd", "which CCD for the position plot?", 1, 1, nccd) ccd = str(ccd) else: ccd = '1' # non-linearity warning levels nonlin = cl.get_default("nonlin") if nonlin is not None and len(nonlin) != nccd: cl.set_default("nonlin", nccd * (50000.,)) nonlins = cl.get_value( "nonlin", "levels to indicate non-linear behaviour, 1 per CCD", nccd*(50000.,) ) # saturation warning levels sat = cl.get_default("sat") if sat is not None and len(sat) != nccd: cl.set_default("sat", nccd * (62000.,)) sats = cl.get_value( "sat", "levels to indicate saturation, 1 per CCD", nccd*(62000.,) ) warn_levels = "" for n, (nonlin,sat) in enumerate(zip(nonlins,sats)): warn_levels += \ f"warn = {n+1} {nonlin} {sat}\n" maxcpu = nccd scale = cl.get_value( "scale", "image scale [arcsec/unbinned pixel]", 0.3, 0.001 ) readout = cl.get_value( "readout", "readout noise, RMS ADU (float or file name)", "4.5" ) gain = cl.get_value( "gain", "gain, electrons/ADU, (float or file name)", "1.1" ) else: raise hcam.HipercamError( "Programming error: instrument unrecognised" ) if ccd not in aper: # make sure 'ccd' is in aper, even if our # favoured one isn't for cnam in aper: ccd = cnam break ################################################################ # # all the inputs have now been obtained. Get on with doing stuff if template is not None: # read the template to define many unprompted values # shortcut names define. tvals = reduction.Rfile.read(template, False) gsec = tvals['general'] instrument = gsec.get('instrument') scale = gsec.get('scale') warn_levels = "" for value in gsec['warn']: warn_levels += f'warn = {value}\n' asec = tvals['apertures'] psfsec = tvals['psf_photom'] sksec = tvals['sky'] csec = tvals['calibration'] lcsec = tvals['lcplot'] lsec = tvals['light'] psec = tvals.get('position',None) tsec = tvals.get('transmission',None) ssec = tvals.get('seeing',None) fsec = tvals.get('focal_mask',None) else: # define default values for unprompted params. if seeing == 0.: seeing = 1.5 sworst = 4.0 binfac = 1 # same name as read into template tvals = {} # general section gsec = tvals['general'] = {} gsec['instrument'] = instrument gsec['scale'] = scale gsec['ldevice'] = '1/xs' gsec['lwidth'] = 0. gsec['lheight'] = 0. gsec['idevice'] = '2/xs' gsec['iwidth'] = 0. gsec['iheight'] = 0. gsec['toffset'] = 0 gsec['skipbadt'] = 'yes' gsec['ncpu'] = 1 gsec['ngroup'] = 1 # aperture section asec = tvals['apertures'] = {} asec['location'] = 'variable' asec['search_smooth_fft'] = 'no' asec['fit_method'] = 'moffat' asec['fit_beta'] = 5.0 asec['fit_beta_max'] = 20.0 asec['fit_ndiv'] = 0 asec['fit_fwhm_fixed'] = 'no' asec['fit_thresh'] = 8. asec['fit_height_min_ref'] = 40. asec['fit_height_min_nrf'] = 10. asec['fit_alpha'] = 0.1 # sky sksec = tvals['sky'] = {} sksec['error'] = 'variance' sksec['method'] = 'clipped' sksec['thresh'] = 3.0 # calibration csec = tvals['calibration'] = {} csec['crop'] = 'yes' csec['nhalf'] = 3 csec['rmin'] = -1. csec['rmax'] = 2. csec['readout'] = readout csec['gain'] = gain # PSF photom psfsec = tvals['psf_photom'] = {} psfsec['gfac'] = 3.0 psfsec['fit_half_width'] = 15.0 psfsec['positions'] = 'fixed' # light curve lcsec = tvals['lcplot'] = {} lcsec['xrange'] = 0 lcsec['extend_x'] = 10.0 # light lsec = tvals['light'] = {} lsec['linear'] = 'no' lsec['y_fixed'] = 'no' lsec['y1'] = 0 lsec['y2'] = 0 lsec['extend_y'] = 0.1 # position psec = tvals['position'] = {} psec['height'] = 0.5 psec['x_fixed'] = 'no' psec['x_min'] = -5.0 psec['x_max'] = +5.0 psec['y_fixed'] = 'no' psec['y_min'] = -5.0 psec['y_max'] = +5.0 psec['extend_y'] = 0.2 # transmission tsec = tvals['transmission'] = {} tsec['height'] = 0.5 tsec['ymax'] = 110. # seeing ssec = tvals['seeing'] = {} ssec['height'] = 0.5 ssec['ymax'] = 2.999 ssec['y_fixed'] = 'no' ssec['extend_y'] = 0.2 fsec = tvals['focal_mask'] = {} fsec['demask'] = 'no' fsec['dthresh'] = 3.0 # apply seeing/instrument-related fixes... # *always* gets run in the no template case if seeing > 0.: asec['search_half_width'] = max(5., sworst/scale) asec['search_smooth_fwhm'] = max(2., seeing/scale/binfac) asec['fwhm_min'] = 1.5*binfac asec['fwhm'] = max(1.5*binfac, seeing/scale) asec['fwhm_max'] = 3.*sworst/scale asec['fit_max_shift'] = max(1., sworst/scale/4.) asec['fit_half_width'] = max(5., 2*sworst/scale) asec['fit_diff'] = max(1., sworst/scale/6.) rfac = 1.8 ramin = 3*binfac ramax = max(ramin,rfac*sworst/scale) sinner = ramax souter = 1.5*sinner # standard colours for CCDs if instrument.startswith("hipercam"): CCD_COLS = { "1": "blue", "2": "green", "3": "orange", "4": "red", "5": "darkred", } elif instrument.startswith("ultracam"): CCD_COLS = { "1": "red", "2": "green", "3": "blue", } elif instrument.startswith("ultraspec"): CCD_COLS = { "1": "green", } elif instrument == "other": # 'other' CCD_COLS = { "1": "green", "2": "blue", "3": "red", "4": "orange", "5": "purple", } else: raise hcam.HipercamError( "Programming error: instrument unrecognised" ) # Extraction lines extraction = "" if template is None: # generate one for each CCD for cnam in aper: extraction += ( f"{cnam} = variable normal" f" {rfac:.2f} {ramin:.1f} {ramax:.1f}" f" 2.5 {sinner:.1f} {sinner:.1f}" f" 3.0 {souter:.1f} {souter:.1f}\n" ) else: # pass through unchanged where possible if # a template being used but check for consistency esec = tvals['extraction'] for cnam, lst in esec.items(): if cnam in aper: # unpack items ltype,etype,rfac,ramin,ramax,sifac,simin,simax,sofac,somin,somax = lst extraction += ( f"{cnam} = variable normal" f" {rfac:.2f} {ramin:.1f} {ramax:.1f}" f" {sifac:.2f} {simin:.1f} {simax:.1f}" f" {sofac:.2f} {somin:.1f} {somax:.1f}\n" ) else: warnings.warn( f'CCD {cnam} has an extraction line in {template} but no apertures in {apfile} and will be skipped' ) # warn if there are apertures for a CCD but no extraction line for cnam in aper: if cnam not in esec: warnings.warn( f'CCD {cnam} has apertures defined in {apfile} but no extraction line in {template}' ) # Generate the light curve plot lines light_plot = "" no_light = True if template is None: # Here we assume target in aperture 1, main comparison in aperture 2 for cnam in aper: ccdaper = aper[cnam] if "1" in ccdaper and "2" in ccdaper: # favour 2 as the comparison light_plot += ( f'plot = {cnam} 1 2 0 1 {CCD_COLS.get(cnam,"black")} ! ' " # ccd, targ, comp, off, fac, dcol, ecol\n" ) no_light = False elif "1" in ccdaper and "3" in ccdaper: # but try 3 if need be light_plot += ( f'plot = {cnam} 1 3 0 1 {CCD_COLS.get(cnam,"black")} ! ' " # ccd, targ, comp, off, fac, dcol, ecol\n" ) no_light = False elif "1": # just plot 1 as last resort light_plot += ( f'plot = {cnam} 1 ! 0 1 {CCD_COLS.get(cnam,"black")} ! ' " # ccd, targ, comp, off, fac, dcol, ecol\n" ) no_light = False else: # template case: pass as many of the lines as we can but # run checks of apertures plots = tvals['light']['plot'] for pl in plots: cnam, targ, comp = pl['ccd'], pl['targ'], pl['comp'] off, fac, dcol, ecol = pl['off'], pl['fac'], ct(pl['dcol']), ct(pl['ecol']) if cnam in aper and targ in aper[cnam] and (comp == '!' or comp in aper[cnam]): light_plot += ( f"plot = {cnam} {targ} {comp} {off} {fac} {dcol} {ecol}" " # ccd, targ, comp, off, fac, dcol, ecol\n" ) no_light = False else: warnings.warn( "Light curve plot line:\n" f"plot = {cnam} {targ} {comp} {off} {fac} {dcol} {ecol}\n" f"from {template} is incompatible with {apfile} and has been skipped" ) if no_light: raise hcam.HipercamError( "Found no targets for light curve plots in any CCD; cannot make light " "curve plot. Needs at least aperture '1' defined and preferably '2' as well," "for at least one CCD" ) # Generate the position plot lines. Favour the comparisons first, then the target. position_plot = "" no_position = True if template is None: ccdaper = aper[ccd] for targ in ('2','3','1'): if targ in ccdaper: position_plot += ( f'plot = {ccd} {targ} ' f'{CCD_COLS.get(cnam,"black")} ! ' '# ccd, targ, dcol, ecol\n' ) no_position = False break if no_position: warnings.warn( f"Targets 2, 1, or 3 not found in CCD = {ccd} in {apfile}" ) elif psec is not None: plots = psec['plot'] for pl in plots: cnam, targ, dcol, ecol = pl['ccd'], pl['targ'], ct(pl['dcol']), ct(pl['ecol']) if cnam in aper and targ in aper[cnam]: position_plot += ( f'plot = {cnam} {targ} {dcol} {ecol} # ccd, targ, dcol, ecol\n' ) no_position = False else: warnings.warn( f"Position target {targ} / CCD {cnam} not found in {apfile}; will skip" ) if no_position: warnings.warn(f"No position plot will be made") # Generate the transmission plot lines, again comparisons first, # target only if desperate. transmission_plot = "" no_transmission = True if template is None: for cnam in aper: ccdaper = aper[cnam] for targ in ('2','3','1'): if targ in ccdaper: transmission_plot += ( f'plot = {cnam} {targ} ' f'{CCD_COLS.get(cnam,"black")} ! ' f"# ccd, targ, dcol, ecol\n" ) no_transmission = False break if no_transmission: warnings.warn( f"Targets 2, 3, or 1 not found in any CCD within {apfile}; no transmission plot" ) elif tsec is not None: plots = tsec['plot'] for pl in plots: cnam, targ, dcol, ecol = pl['ccd'], pl['targ'], ct(pl['dcol']), ct(pl['ecol']) if cnam in aper and targ in aper[cnam]: transmission_plot += ( f'plot = {cnam} {targ} {dcol} {ecol} # ccd, targ, dcol, ecol\n' ) no_transmission = False else: warnings.warn( f"Transmission target {targ} / CCD {cnam} not found in {apfile}; will skip" ) if no_transmission: warnings.warn(f"No transmission plot will be made") # Generate the seeing plot lines. Prioritise the target in this case. seeing_plot = "" no_seeing = True if template is None: for cnam in aper: ccdaper = aper[cnam] for targ in ('1','2','3'): if targ in ccdaper and not ccdaper[targ].linked: seeing_plot += ( f"plot = {cnam} {targ}" f' {CCD_COLS.get(cnam,"black")} ! ' "# ccd, targ, dcol, ecol\n" ) no_seeing = False break if no_seeing: raise hcam.HipercamError( "Targets 1, 2 and 3 not found in any CCD" " (or they are linked); cannot make seeing plot" ) elif ssec is not None: plots = tsec['plot'] for pl in plots: cnam, targ, dcol, ecol = pl['ccd'], pl['targ'], ct(pl['dcol']), ct(pl['ecol']) if cnam in aper and targ in aper[cnam] and not aper[cnam][targ].linked: seeing_plot += ( f'plot = {cnam} {targ} {dcol} {ecol} # ccd, targ, dcol, ecol\n' ) no_seeing = False else: warnings.warn( f"Seeing target {targ} / CCD {cnam} not found or linked in {apfile}; will skip" ) if no_seeing: warnings.warn(f"No seeing plot will be made") # monitor targets (all of them by default) if template is None: targs = set() for cnam in aper: ccdaper = aper[cnam] for targ in ccdaper: targs.add(targ) monitor = "" for targ in sorted(targs): monitor += ( f"{targ} = NO_EXTRACTION TARGET_SATURATED TARGET_AT_EDGE" " TARGET_NONLINEAR NO_SKY NO_FWHM NO_DATA SKY_AT_EDGE\n" ) else: monitor = "" msec = tvals['monitor'] rlook = dict([(k,v) for v,k in hcam.FLAGS]) for targ, masks in msec.items(): smasks = ' '.join([rlook[mask] for mask in masks]) monitor += f'{targ} = {smasks}\n' # time stamp tstamp = strftime("%d %b %Y %H:%M:%S (UTC)", gmtime()) # Finally, write out the reduce file ... with open(rfile, "w") as fout: # write out file fout.write( f"""# # This is a HiPERCAM "reduce file" which defines the operation of the # reduce script. It was written by the HiPERCAM pipeline command # 'genred'. It consists of a series of sections each of which # contains a number of parameters. The file is self-documenting on the # meaning of these parameters. The idea is that these are to large # extent unchanging and it would be annoying to be prompted every time # for them, but it also acts as a record of how reduction was carried # out and is fed into the log file produce by 'reduce'. # File written on {tstamp} # # HiPERCAM pipeline version: {hcam.REDUCE_FILE_VERSION} # # Start with some general items that tend not to change # much. 'version' is the version of the reduce file format which # changes more slowly than the software does. It must match the same # parameter in 'reduce' for reduction to proceed. This is # automatically the case at the time of creation, but old versions of # the reduce file may become incompatible with later versions of # reduce. Either they will require updating to be used, or the # software version can be rolled back to give a compatible version of # reduce using 'git'. The script 'rupdate', which attempts automatic # update, may be worth trying if you need to update. It attempts to # make the minimum changes needed to an old reduce file to run with # later version dates. [general] version = {hcam.REDUCE_FILE_VERSION} # must be compatible with the version in reduce instrument = {gsec['instrument']} # instrument scale = {gsec['scale']} # pixel scale, arcsec/pixel ldevice = {gsec['ldevice']} # PGPLOT plot device for light curve plots lwidth = {gsec['lwidth']} # light curve plot width, inches, 0 to let program choose lheight = {gsec['lheight']} # light curve plot height, inches idevice = {gsec['idevice']} # PGPLOT plot device for image plots [if implot True] iwidth = {gsec['iwidth']} # image curve plot width, inches, 0 to let program choose iheight = {gsec['iheight']} # image curve plot height, inches toffset = {gsec['toffset']} # integer offset to subtract from the MJD # skip points with bad times in plots. HiPERCAM has a problem in not # correctly indicating bad times so one does not usually want to # skip "bad time" points, whereas one should for ULTRACAM and ULTRASPEC. skipbadt = {gsec['skipbadt']} # series of count levels at which warnings will be triggered for (a) # non linearity and (b) saturation. Each line starts 'warn =', and is # then followed by the CCD label, the non-linearity level and the # saturation level {warn_levels} # The aperture reposition and extraction stages can be run in separate # CPUs in parallel for each CCD potentially offering speed # advantages. 'ncpu' is the number of CPUs to use for this. The # maximum useful and best number to use is the number of CCDs in the # instrument, e.g. 5 for HiPERCAM. You probably also want to leave at # least one CPU to do other stuff, but if you have more than 2 CPUs, # this parameter may help speed things. If you use this option (ncpu > # 1), then there is also an advantage in terms of reducing # parallelisation overheads in reading frames a few at a time before # processing. This is controlled using 'ngroup'. i.e. with ngroup=10, # 10 full frames are read before being processed. This parameter is # ignored if ncpu==1 # I have had mixed results with this, which could depend a lot on the # installation. On my home desktop is works better with ncpu=1 because # it already seems to parallelise and it only gets worse if I split things # up. But no harm trying. ncpu = {gsec['ncpu']} ngroup = {gsec['ngroup']} # The next section '[apertures]' defines how the apertures are # re-positioned from frame to frame. Apertures are re-positioned # through a combination of a search near a start location followed by # a 2D profile fit. Several parameters below are associated with this # process and setting these right can be the key to a successful # reduction. If there are reference apertures, they are located first # to give a mean shift. This is used to avoid the initial search for # any non-reference apertures which has the advantage of reducing the # chance of problems. The search is carried out by first extracting a # sub-window centred on the last good position of a target. This is # then smoothed by a gaussian (width 'search_smooth_fwhm'), and the # closest peak to the last valid position higher than # 'fit_height_min_ref' above background (median of the square box) is # taken as the initial position for later profile fits. The smoothing # serves to make the process more robust against cosmic rays. The # width of the search box ('search_half_width') depends on how good # the telescope guiding is. It should be large enough to cope with the # largest likely shift in position between any two consecutive # frames. Well-chosen reference targets, which should be isolated and # bright, can help this process a great deal. The threshold is applied # to the *smoothed* image. This means that it can be significantly # lower than simply the raw peak height. e.g. a target might have a # typical peak height around 100, in seeing of 4 pixels FWHM. If you # smooth by 10 pixels, the peak height will drop to # 100*4**2/(4**2+10**2) = 14 counts. It will be much more stable as a # result, but you should then probably choose a threshold of 7 when # you might have thought 50 was appropriate. The smoothing itself can # be carried out by direct convolution or by an FFT-based method. The # end-result is the same either way but for large values of # 'search_smooth_fwhm', i.e. >> 1, FFTs may offer an advantage # speed-wise. But the only way to tell is by explicity running with # 'search_smooth_fft' switched from 'no' to 'yes'. # The boxes for the fits ('fit_half_width') need to be large enough to # include the target and a bit of sky to ensure that the FWHM is # accurately measured, remembering that seeing can flare of course. If # your target was defocussed, a gaussian or Moffat function will be a # poor fit and you may be better keeping the FWHM fixed at a large # value comparable to the widths of your defoccused images (and use # the gaussian option in such cases). If the apertures are chosen to # be fixed, there will be no search or fit carried out in which case # you must choose 'fixed' as well when it comes the extraction since # otherwise it needs a FWHM. 'fixed' is a last resort and you will # very likely need to use large aperture radii in the extraction # section. # An obscure parameter is 'fit_ndiv'. If this is made > 0, the fit # routine attempts to allow for pixellation by evaluating the profile # at multiple points within each pixel of the fit. First it will # evaluate the profile for every unbinned pixel within a binned pixel # if the pixels are binned; second, it will evaluate the profile over # an ndiv by ndiv square grid within each unbinned pixel. Thus ndiv=1 # means one evaluation at the centre of each unbinned pixel, # etc. Obviously this will slow things, but it could help if your # images are under-sampled. I would always start with fit_ndiv=0, and # only raise it if the measured FWHM seem to be close to or below two # binned pixels. # If you use reference targets (you should if possible), the initial # positions for the non-reference targets should be good. You can then # guard further against problems using the parameter 'fit_max_shift' # to reject positions for the non-reference targets that shift too far # from the initial guess. 'fit_alpha' is another parameter that # applies only in this case. If reference apertures are being used, # the expected locations of non-reference apertures can be predicted # with some confidence. In this case when the non-reference aperture's # position is measured, its position will be adjusted by 'fit_alpha' # times the measured change in its position. Its value is bounded by 0 # < fit_alpha <= 1. "1" just means use the full measured change from # the current frame to update the position. Anything < 1 builds in a # bit of past history. The hope is that this could make the aperture # positioning, especially for faint targets, more robust to cosmic # rays and other issues. Of course it will correlate the positions # from frame to frame. fit_alpha = 0.1 for instance will lead to a # correlation length ~ 10 frames. # If you use > 1 reference targets, then the parameter 'fit_diff' # comes into play. Multiple reference targets should move together # and give very consistent shifts. If they don't, then a problem may # have occurred, e.g. one or more have been affected by a meteor trail # for instance. The maximum acceptable differential shift is defined # by 'fit_diff'. If exceeded, then the entire extraction will be # aborted and positions held fixed. # To get and idea of the right values of some of these parameters, in # particular the 'search_half_width', the height thresholds, # 'fit_max_shift' and 'fit_diff', the easiest approach is probably to # run a reduction with loose values and see how it goes. [apertures] aperfile = {apfile} # file of software apertures for each CCD location = {asec['location']} # aperture locations: 'fixed' or 'variable' search_half_width = {asec['search_half_width']:.1f} # for initial search for objects around previous position, unbinned pixels search_smooth_fwhm = {asec['search_smooth_fwhm']:.1f} # smoothing FWHM, binned pixels search_smooth_fft = {asec['search_smooth_fft']} # use FFTs for smoothing, 'yes' or 'no'. fit_method = {asec['fit_method']} # gaussian or moffat fit_beta = {asec['fit_beta']:.1f} # Moffat exponent fit_beta_max = {asec['fit_beta_max']:.1f} # max Moffat expt fit_fwhm = {asec['fwhm']:.1f} # FWHM, unbinned pixels fit_fwhm_min = {asec['fwhm_min']:.1f} # Minimum FWHM, unbinned pixels [MUST be < fit_fwhm!] fit_fwhm_max = {asec['fwhm_max']:.1f} # Maximum FWHM, unbinned pixels fit_ndiv = {asec['fit_ndiv']} # sub-pixellation factor fit_fwhm_fixed = {asec['fit_fwhm_fixed']} # Might want to set = 'yes' for defocussed images fit_half_width = {asec['fit_half_width']:.1f} # for fit, unbinned pixels fit_thresh = {asec['fit_thresh']:.2f} # RMS rejection threshold for fits fit_height_min_ref = {asec['fit_height_min_ref']:.1f} # minimum height to accept a fit, reference aperture fit_height_min_nrf = {asec['fit_height_min_nrf']:.1f} # minimum height to accept a fit, non-reference aperture fit_max_shift = {asec['fit_max_shift']:.1f} # max. non-ref. shift, unbinned pixels. fit_alpha = {asec['fit_alpha']:.2f} # Fraction of non-reference aperture shift to apply fit_diff = {asec['fit_diff']:.2f} # Maximum differential shift of multiple reference apertures, unbinned # The next lines define how the apertures will be re-sized and how the # flux will be extracted from the aperture. There is one line per CCD # with format: # <CCD label> = <resize> <extract method> [scale min max] [scale min max] # [scale min max] # where: <CCD label> is the CCD label; <resize> is either 'variable' # or 'fixed' and sets how the aperture size will be determined -- if # variable it will be scaled relative to the FWHM, so profile fitting # will be attempted; <extract method> is either 'normal' or 'optimal' # to say how the flux will be extracted -- 'normal' means a straight # sum of sky subtracted flux over the aperture, 'optimal' use Tim # Naylor's profile weighting, and requires profile fits to # work. Finally there follow a series of numbers in three triplets, # each of which is a scale factor relative to the FWHM for the # aperture radius if the 'variable' option was chosen, then a minimum # and a maximum aperture radius in unbinned pixels. The three triples # correspond to the target aperture radius, the inner sky radius and # finally the outer sky radius. The mininum and maximum also apply if # you choose 'fixed' apertures and can be used to override whatever # value comes from the aperture file. A common approach is set them # equal to each other to give a fixed value, especially for the sky # where one does not necessarily want the radii to vary. For PSF # photometry, all these settings have no effect, but this section can # still be used to determine which CCDs have fluxes extracted. [extraction] {extraction} # The next lines are specific to the PSF photometry option. 'gfac' is # used to label the sources according to groups, such that stars # closer than 'gfac' times the FWHM are labelled in the same # group. Each group has a PSF model fit independently. The reason # behind the construction of groups is to reduce the dimensionality of # the fitting procedure. Usually you want closely seperated stars to # be fit simultaneously, but too large a value will mean fitting a # model with many free parameters, which can fail to converge. The # size of the box over which data is collected for fitting is set by # 'fit_half_width'. Finally, 'positions' determines whether the star's # positions should be considered variable in the PSF fitting. If this # is set to fixed, the positions are held at the locations found in # the aperture repositioning step, otherwise the positions are refined # during PSF fitting. This step can fail for PSF photometry of faint # sources. [psf_photom] gfac = {psfsec['gfac']} # multiple of the FWHM to use in grouping objects fit_half_width = {psfsec['fit_half_width']} # size of window used to collect the data to do the fitting positions = {psfsec['positions']} # 'fixed' or 'variable' # Next lines determine how the sky background level is # calculated. Note you can only set error = variance if method = # 'clipped'. 'median' should usually be avoided as it can cause # noticable steps in light curves. It's here as a comparator. [sky] method = {sksec['method']} # 'clipped' | 'median' error = {sksec['error']} # 'variance' | 'photon': first uses actual variance of sky thresh = {sksec['thresh']:.1f} # threshold in terms of RMS for 'clipped' # Calibration frames and constants # If you specify "!" for the readout, an attempt to estimate it from # +/- 1 sigma percentiles will be made. This could help if you have no # bias (and hence variance calculation from the count level will be # wrong) # The fringe parameters are the most complex. To de-fringe you need # first a fringe map, e.g. as returned by makefringe. Then you need a # file of FringePairs as returned by 'setfringe'. These are pairs of # points placed at peaks and troughs of fringes. They will be used to # measure a series of differences in each frame of interest and the # fringe map from which a series of ratios is formed. The differences # are measured from a group of pixels extending +/-nhlaf around the # central pixel of each point of a FringePair. Thus nhalf=2 would give # a 5x5 array (unbinned pixels). Finally to reduce the effect of bad # values the individual ratios can be pruned if they lie outside a # range rmin to rmax prior to their overall median being calculated. # rmin should be small, but probably slightly less than zero to allow # for a bit of statistical fluctuation, depending on your setup. One # would normally expect rmax < 1 if the fringe map came from frames with # a long exposure compared to the data. [calibration] crop = {csec['crop']} # Crop calibrations to match the data bias = {'' if bias is None else bias} # Bias frame, blank to ignore flat = {'' if flat is None else flat} # Flat field frame, blank to ignore dark = {'' if dark is None else dark} # Dark frame, blank to ignore fmap = {'' if fmap is None else fmap} # Fringe map frame, blank to ignore fpair = {'' if fpair is None else fpair} # File of fringe pairs, ignored if fmap blank nhalf = {csec['nhalf']} # Half-size of region used for fringe ratios, binned pix, ignored if fmap blank="" rmin = {csec['rmin']} # Mininum acceptable individual fmap ratio, ignored if fmap blank rmax = {csec['rmax']} # Maximum acceptable individual fmap ratio, ignored if fmap blank readout = {csec['readout']} # RMS ADU. Float or string name of a file or "!" to estimate on the fly gain = {csec['gain']} # Gain, electrons/ADU. Float or string name of a file # The light curve plot which consists of light curves, X & Y # poistions, the transmission and seeing. All but the light curves can # be switched off by commenting them out (in full). First a couple of # general parameters. [lcplot] xrange = {lcsec['xrange']} # maximum range in X to plot (minutes), <= 0 for everything extend_x = {lcsec['extend_x']} # amount by which to extend xrange, minutes. # The light curve panel (must be present). Mostly obvious, then a # series of lines, each starting 'plot' which specify one light curve # to be plotted giving CCD, target, comparison ('!' if you don't want # a comparison), an additive offset, a multiplicative scaling factor # and then a colour for the data and a colour for the error bar There # will always be a light curve plot, whereas later elements are # optional, therefore the light curve panel is defined to have unit # height and all others are scaled relative to this. [light] linear = {lsec['linear']} # linear vertical scale (else magnitudes): 'yes' or 'no' y_fixed = {lsec['y_fixed']} # keep a fixed vertical range or not: 'yes' or 'no' y1 = {lsec['y1']} # initial lower y value y2 = {lsec['y2']} # initial upper y value. y1=y2 for auto scaling extend_y = {lsec['extend_y']} # fraction of plot height to extend when rescaling # line or lines defining the targets to plot {light_plot} """ ) # position plot (optional) if no_position: fout.write( f"""# The X,Y position panel. Uncomment to show #[position] #height = 0.5 # height relative to light curve plot #x_fixed = no # keep X-position vertical range fixed #x_min = -5 # lower limit for X-position #x_max = +5 # upper limit for X-position #y_fixed = no # keep Y-position vertical range fixed #y_min = -5 # lower limit for Y-position #y_max = +5 # upper limit for Y-position #extend_y = 0.2 # Vertical extension fraction if limits exceeded ## line or lines defining the targets to plot # plot = 1 1 darkred ! # ccd, targ, dcol, ecol """ ) else: fout.write( f"""# The X,Y position panel. Comment *every* line # if you don't want it. [position] height = {psec['height']} # height relative to light curve plot x_fixed = {psec['x_fixed']} # keep X-position vertical range fixed x_min = {psec['x_min']} # lower limit for X-position x_max = {psec['x_max']} # upper limit for X-position y_fixed = {psec['y_fixed']} # keep Y-position vertical range fixed y_min = {psec['y_min']} # lower limit for Y-position y_max = {psec['y_max']} # upper limit for Y-position extend_y = {psec['extend_y']} # Vertical extension fraction if limits exceeded # line or lines defining the targets to plot {position_plot} """ ) # Transmission plot, optional if no_transmission: fout.write( f"""# The transmission panel. Uncomment if you want it # Simply plots the flux in whatever apertures are chosen, # scaling them by their maximum (hence one can sometimes # find that what you thought was 100% transmission was # actually only 50% revealed as the cloud clears). #[transmission] #height = 0.5 # height relative to the light curve plot #ymax = 110 # Maximum transmission to plot (>= 100 to slow replotting) ## line or lines defining the targets to plot # plot = 1 2 green ! # ccd, targ, dcol, ecol """ ) else: fout.write( f"""# The transmission panel. If you comment it out, # comment *every* line. # Simply plots the flux in whatever apertures are chosen, # scaling them by their maximum (hence one can sometimes # find that what you thought was 100% transmission was # actually only 50% revealed as the cloud clears). [transmission] height = {tsec['height']} # height relative to the light curve plot ymax = {tsec['ymax']} # Maximum transmission to plot (>= 100 to slow replotting) # line or lines defining the targets to plot {transmission_plot} """ ) if no_seeing: fout.write( f"""# The seeing panel. Uncomment if you want it. # You can have multiple plot lines. Don't choose linked # targets as their FWHMs are not measured. #[seeing] #height = 0.5 # height relative to the light curve plot #ymax = 2.999 # Initial maximum seeing #y_fixed = no # fix the seeing scale (or not) #extend_y = 0.2 # Y extension fraction if out of range and not fixed ## line or lines defining the targets to plot # plot = 1 2 green ! # ccd, targ, dcol, ecol """ ) else: fout.write( f"""# The seeing panel. Can be commented out if you don't want one but make # sure to comment it out completely, section name and all # parameters. You can have multiple plot lines. Don't choose linked # targets as their FWHMs are not measured. [seeing] height = {ssec['height']} # height relative to the light curve plot ymax = {ssec['ymax']} # Initial maximum seeing y_fixed = {ssec['y_fixed']} # fix the seeing scale (or not) extend_y = {ssec['extend_y']} # Y extension fraction if out of range and not fixed # line or lines defining the targets to plot {seeing_plot} """ ) # Finish up fout.write( f""" # This option attempts to correct for a badly-positioned focal plane mask # in drift mode which combined with a high background can lead to steps in # illumination in the Y direction. This tries to subtract the median in the # X-direction of each window. 'dthresh' is a threshold used to reject X # pixels prior to taking the median. The purpose is to prevent the stars # from distorting the median. Take care with this option which is experimental. [focal_mask] demask = {fsec['demask']} dthresh = {fsec['dthresh']} # Monitor section. This section allows you to monitor particular # targets for problems. If they occur, then messages will be printed # to the terminal during reduce. The messages are determined by the # bitmask flag set during the extraction of each # target. Possibilities: # NO_FWHM : no FWHM measured # NO_SKY : no sky pixels at all # SKY_AT_EDGE : sky aperture off edge of window # TARGET_AT_EDGE : target aperture off edge of window # TARGET_SATURATED : at least one pixel in target above saturation level # TARGET_NONLINEAR : at least one pixel in target above nonlinear level # NO_EXTRACTION : no extraction possible # NO_DATA : no valid pixels in aperture # For a target you want to monitor, type its label, '=', then the # bitmask patterns you want to be flagged up if they are set. This is # designed mainly for observing, as there is less you can do once the # data have been taken, but it still may prove useful. [monitor] {monitor} """ ) print("Reduce file written to {:s}".format(rfile))
def makemccd(args=None): """Script to generate multi-CCD test data given a set of parameters defined in a config file (parsed using configparser). This allows things such as a bias frame, flat field variations offsets, scale factor and rotations between CCDs, and temporal variations. Arguments:: config : string file defining the parameters. parallel : bool True / yes etc to run in parallel. Be warned: it does not always make things faster, which I assume is the result of overheads when parallelising. It will simply use whatever CPUs are available. Depending upon the setting in the config file, this could generate a large number of different files and so the first time you run it, you may want to do so in a clean directory. Config file format: see the documentation of configparser for the general format of the config files expected by this routine. Essentially there are a series of sections, e.g.: [ccd 1] nxtot = 2048 nytot = 1048 . . . which define all the parameters needed. There are many others to simulate a bias offset, a flat field; see the example file ??? for a fully-documented version. """ import configparser global _gframe, _gfield command, args = utils.script_args(args) # get inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # Register parameters cl.register("config", Cline.LOCAL, Cline.PROMPT) cl.register("parallel", Cline.LOCAL, Cline.PROMPT) # Prompt for them config = cl.get_value("config", "configuration file", cline.Fname("config")) parallel = cl.get_value("parallel", "add targets in parallel?", False) # Read the config file conf = configparser.ConfigParser() conf.read(config) # Determine whether files get overwritten or not overwrite = (conf.getboolean("general", "overwrite") if "overwrite" in conf["general"] else False) dtype = conf["general"]["dtype"] if "dtype" in conf["general"] else None # Top-level header thead = fits.Header() thead.add_history("Created by makedata") # Store the CCD labels and parameters and their dimensions. Determine # maximum dimensions for later use when adding targets ccd_pars = Odict() maxnx = 0 maxny = 0 for key in conf: if key.startswith("ccd"): # translate parameters nxtot = int(conf[key]["nxtot"]) nytot = int(conf[key]["nytot"]) xcen = float(conf[key]["xcen"]) ycen = float(conf[key]["ycen"]) angle = float(conf[key]["angle"]) scale = float(conf[key]["scale"]) xoff = float(conf[key]["xoff"]) yoff = float(conf[key]["yoff"]) fscale = float(conf[key]["fscale"]) toff = float(conf[key]["toff"]) field = (hcam.Field.rjson(conf[key]["field"]) if "field" in conf[key] else None) ndiv = int(conf[key]["ndiv"]) if field is not None else None back = float(conf[key]["back"]) # determine maximum total dimension maxnx = max(maxnx, nxtot) maxny = max(maxny, nytot) # store parameters ccd_pars[key[3:].strip()] = { "nxtot": nxtot, "nytot": nytot, "xcen": xcen, "ycen": ycen, "angle": angle, "scale": scale, "xoff": xoff, "yoff": yoff, "fscale": fscale, "toff": toff, "field": field, "ndiv": ndiv, "back": back, } if not len(ccd_pars): raise ValueError("hipercam.makedata: no CCDs found in " + config) # get the timing data utc_start = Time(conf["timing"]["utc_start"]) exposure = float(conf["timing"]["exposure"]) deadtime = float(conf["timing"]["deadtime"]) # Generate the CCDs, store the read / gain values ccds = hcam.Group(hcam.CCD) rgs = {} for cnam, pars in ccd_pars.items(): # Generate header with timing data head = fits.Header() td = TimeDelta(pars["toff"], format="sec") utc = utc_start + td head["UTC"] = (utc.isot, "UTC at mid exposure") head["MJD"] = (utc.mjd, "MJD at mid exposure") head["EXPOSE"] = (exposure, "Exposure time, seconds") head["TIMEOK"] = (True, "Time status flag") # Generate the Windows winds = hcam.Group(hcam.Window) rgs[cnam] = {} for key in conf: if key.startswith("window"): iccd, wnam = key[6:].split() if iccd == cnam: llx = int(conf[key]["llx"]) lly = int(conf[key]["lly"]) nx = int(conf[key]["nx"]) ny = int(conf[key]["ny"]) xbin = int(conf[key]["xbin"]) ybin = int(conf[key]["ybin"]) if len(winds): wind = hcam.Window( hcam.Winhead(llx, lly, nx, ny, xbin, ybin)) else: # store the header in the first Window wind = hcam.Window( hcam.Winhead(llx, lly, nx, ny, xbin, ybin, head)) # Store the Window winds[wnam] = wind # Store read / gain value rgs[cnam][wnam] = ( float(conf[key]["read"]), float(conf[key]["gain"]), ) # Accumulate CCDs ccds[cnam] = hcam.CCD(winds, pars["nxtot"], pars["nytot"]) # Make the template MCCD mccd = hcam.MCCD(ccds, thead) # Make a flat field flat = mccd.copy() if "flat" in conf: rms = float(conf["flat"]["rms"]) for ccd in flat.values(): # Generate dust nspeck = int(conf["flat"]["nspeck"]) if nspeck: radius = float(conf["flat"]["radius"]) depth = float(conf["flat"]["depth"]) specks = [] for n in range(nspeck): x = np.random.uniform(0.5, ccd.nxtot + 0.5) y = np.random.uniform(0.5, ccd.nytot + 0.5) specks.append(Dust(x, y, radius, depth)) # Set the flat field values for wind in ccd.values(): wind.data = np.random.normal(1.0, rms, (wind.ny, wind.nx)) if nspeck: wind.add_fxy(specks) flat.head["DATATYPE"] = ("Flat field", "Artificially generated") fname = utils.add_extension(conf["flat"]["flat"], hcam.HCAM) flat.write(fname, overwrite) print("Saved flat field to ", fname) else: # Set the flat to unity flat.set_const(1.0) print("No flat field generated") # Make a bias frame bias = mccd.copy() if "bias" in conf: mean = float(conf["bias"]["mean"]) rms = float(conf["bias"]["rms"]) for ccd in bias.values(): for wind in ccd.values(): wind.data = np.random.normal(mean, rms, (wind.ny, wind.nx)) bias.head["DATATYPE"] = ("Bias frame", "Artificially generated") fname = utils.add_extension(conf["bias"]["bias"], hcam.HCAM) bias.write(fname, overwrite) print("Saved bias frame to ", fname) else: # Set the bias to zero bias.set_const(0.0) print("No bias frame generated") # Everything is set to go, so now generate data files nfiles = int(conf["files"]["nfiles"]) if nfiles == 0: out = mccd * flat + bias fname = utils.add_extension(conf["files"]["root"], hcam.HCAM) out.write(fname, overwrite) print("Written data to", fname) else: # file naming info root = conf["files"]["root"] ndigit = int(conf["files"]["ndigit"]) # movement xdrift = float(conf["movement"]["xdrift"]) ydrift = float(conf["movement"]["ydrift"]) nreset = int(conf["movement"]["nreset"]) jitter = float(conf["movement"]["jitter"]) print("Now generating data") tdelta = TimeDelta(exposure + deadtime, format="sec") for nfile in range(nfiles): # copy over template (into a global variable for multiprocessing speed) _gframe = mccd.copy() # get x,y offset xoff = np.random.normal(xdrift * (nfile % nreset), jitter) yoff = np.random.normal(ydrift * (nfile % nreset), jitter) # create target fields for each CCD, add background _gfield = {} for cnam in _gframe.keys(): p = ccd_pars[cnam] _gframe[cnam] += p["back"] if p["field"] is not None: # get field modification settings transform = Transform( p["nxtot"], p["nytot"], p["xcen"], p["ycen"], p["angle"], p["scale"], p["xoff"] + xoff, p["yoff"] + yoff, ) fscale = p["fscale"] _gfield[cnam] = p["field"].modify(transform, fscale) # add the targets in (slow step) if parallel: # run in parallel on whatever cores are available args = [(cnam, ccd_pars[cnam]["ndiv"]) for cnam in _gfield] with Pool() as pool: ccds = pool.map(worker, args) for cnam in _gfield: _gframe[cnam] = ccds.pop(0) else: # single core for cnam in _gfield: ccd = _gframe[cnam] ndiv = ccd_pars[cnam]["ndiv"] field = _gfield[cnam] for wind in ccd.values(): field.add(wind, ndiv) # Apply flat _gframe *= flat # Add noise for cnam, ccd in _gframe.items(): for wnam, wind in ccd.items(): readout, gain = rgs[cnam][wnam] wind.add_noise(readout, gain) # Apply bias _gframe += bias # data type on output if dtype == "float32": _gframe.float32() elif dtype == "uint16": _gframe.uint16() # Save fname = "{0:s}{1:0{2:d}d}{3:s}".format(root, nfile + 1, ndigit, hcam.HCAM) _gframe.write(fname, overwrite) print("Written file {0:d} to {1:s}".format(nfile + 1, fname)) # update times in template for ccd in mccd.values(): head = ccd.head utc = Time(head["UTC"]) + tdelta head["UTC"] = (utc.isot, "UTC at mid exposure") head["MJD"] = (utc.mjd, "MJD at mid exposure")
def makefringe(args=None): """``makefringe [source] (run first last [twait tmax] | flist) bias dark flat fpair ([nhalf]) ccd fwhm [clobber] output`` Averages a set of images to make a frame for defringing (referred to elsewhere as a "fringe map"). At long wavelengths, CCDs suffer what is known as "fringing", which in terms of structure looks something like the coloured patterns you see when there is a thin layer of oil on water. Both patterns are caused by interference. In the case of CCDs this is caused by sky emission lines and is variable in strength, and additive in nature, but seems mostly fairly fixed in position. De-fringing in |hiper| is implemented according to the method suggested by Snodgrass & Carry (2013Msngr.152...14S). The idea is to create a set of pairs of points marking the peak and troughs of fringes. The differences in intensity between these point pairs in the data to be corrected is compared with the differences for the same pairs in a reference "fringe map" by taking their ratios. Many pairs are used so that the median can be taken to eliminate ratios affected by celestial targets or cosmic rays. The median ratio is used to scale the reference data and thereby subtract the fringes. ``makefringe`` is used to make the reference fringe map. It works as follows: given a single run or a list of files, it reads them all in, optionally debiases, dark-subtracts and flat-fields them, calculates a median count level of each one which is subtracted from each CCD individually. The pixel-by-pixel median of all frames is then calculated. It is also possible (and a good idea) to apply the fringe pair ratio measurements to scale each frame before combining them, which allows frames of similar pattern but varying amplitude to be combined. Finally, the output can be smoothed because fringes are usually a medium- to large-scale pattern. Parameters: source : string [hidden] Data source, five options: | 'hs' : HiPERCAM server | 'hl' : local HiPERCAM FITS file | 'us' : ULTRACAM server | 'ul' : local ULTRACAM .xml/.dat files | 'hf' : list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. run : string [if source ends 's' or 'l'] run number to access, e.g. 'run034' first : int [if source ends 's' or 'l'] exposure number to start from. 1 = first frame ('0' is not supported). last : int [if source ends 's' or 'l'] last exposure number must be >= first or 0 for the whole lot. twait : float [if source ends 's' or 'l'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's' or 'l'; hidden] maximum time to wait between attempts to find a new exposure, seconds. flist : string [if source ends 'f'] name of file list. Assumed that bias, flat-fielding etc have been applied to these already. bias : str Name of bias frame to subtract, 'none' to ignore. dark : str Name of dark frame to subtract, 'none' to ignore. flat : str Name of flat field, 'none' to ignore. fpair : str FringePair file (see setfringe), or 'none' to ignore. If specified if will be used to scale the frames prior to combining. nhalf : int [if fpair is not 'none'] When calculating the differences for fringe measurement, a region extending +/-nhalf binned pixels will be used when measuring the amplitudes. Basically helps the stats. ccd : str CCD(s) to process, '0' for all, '1 3' for '1' and '3' only, etc. fwhm : float FWHM (unbinned pixels) of gaussian smoothing to apply to the output. 0 to ignore. Should be << smallest scale between fringes. Probably no more than 4. clobber : bool [hidden] clobber any pre-existing output files output : str output file. Set by default to match the last part of "run" (but it will have a different extension so they won't clash). The default is .. Note:: This routine writes the files returned by 'grab' to automatically generated files, typically in .hipercam/tmp, to avoid polluting the working directory. These are removed at the end, but may not be if you ctrl-C. You should check .hipercam/tmp for redundant files every so often """ command, args = utils.script_args(args) # get the inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("source", Cline.GLOBAL, Cline.HIDE) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("last", Cline.LOCAL, Cline.PROMPT) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("bias", Cline.LOCAL, Cline.PROMPT) cl.register("dark", Cline.LOCAL, Cline.PROMPT) cl.register("flat", Cline.LOCAL, Cline.PROMPT) cl.register("fpair", Cline.LOCAL, Cline.PROMPT) cl.register("nhalf", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("fwhm", Cline.LOCAL, Cline.PROMPT) cl.register("clobber", Cline.LOCAL, Cline.HIDE) cl.register("output", Cline.LOCAL, Cline.PROMPT) # get inputs source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", "hl", lvals=("hs", "hl", "us", "ul", "hf"), ) # set a flag server_or_local = source.endswith("s") or source.endswith("l") if server_or_local: resource = cl.get_value("run", "run name", "run005") root = os.path.basename(resource) cl.set_default('output', cline.Fname(root, hcam.HCAM)) first = cl.get_value("first", "first frame to average", 1, 1) last = cl.get_value("last", "last frame to average (0 for all)", first, 0) twait = cl.get_value("twait", "time to wait for a new frame [secs]", 1.0, 0.0) tmax = cl.get_value("tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0) else: resource = cl.get_value("flist", "file list", cline.Fname("files.lis", hcam.LIST)) first = 1 # bias frame (if any) bias = cl.get_value( "bias", "bias frame ['none' to ignore]", cline.Fname("bias", hcam.HCAM), ignore="none", ) # dark frame (if any) dark = cl.get_value( "dark", "dark frame ['none' to ignore]", cline.Fname("dark", hcam.HCAM), ignore="none", ) # flat field (if any) flat = cl.get_value( "flat", "flat field frame ['none' to ignore]", cline.Fname("flat", hcam.HCAM), ignore="none", ) fpair = cl.get_value("fpair", "fringe pair file", cline.Fname("fringe", hcam.FRNG), ignore="none") if fpair is not None: nhalf = cl.get_value("nhalf", "half-size of fringe measurement region", 2, 0) ccdinf = spooler.get_ccd_pars(source, resource) if len(ccdinf) > 1: ccd = cl.get_value("ccd", "CCD(s) to process [0 for all]", "0") if ccd == "0": ccds = list(ccdinf.keys()) else: ccds = ccd.split() else: ccds = list(ccdinf.keys()) fwhm = cl.get_value( "fwhm", "FWHM of gaussian smoothing to apply" " to final result [unbinned pixels, 0 to ignore]", 4., 0.) clobber = cl.get_value("clobber", "clobber any pre-existing files on output", False) output = cl.get_value( "output", "median output fringe frame", cline.Fname("hcam", hcam.HCAM, cline.Fname.NEW if clobber else cline.Fname.NOCLOBBER), ) # inputs done with. if server_or_local or bias is not None or dark is not None or flat is not None: print("\nCalling 'grab' ...") args = [None, "prompt", source, "yes", resource] if server_or_local: args += [str(first), str(last), str(twait), str(tmax)] args += [ "no", "none" if bias is None else bias, "none" if dark is None else dark, "none" if flat is None else flat, "none", "f32", ] resource = hcam.scripts.grab(args) # at this point 'resource' is a list of files, no matter the input # method. fnames = [] with CleanUp(resource, fnames, server_or_local or bias is not None or dark is not None) as cleanup: # Read all the files to determine mean levels (after bias # subtraction) save the bias-subtracted, flat-fielded, # mean-level subtracted and normalised results to temporary # files print("Reading all files in to determine their mean levels") bframe, fframe, dframe, fpairobj = None, None, None, None medians = {} for cnam in ccds: medians[cnam] = {} # We might have a load of temporaries from grab, but we are about to # make some more to save the bias-subtracted normalised versions. tdir = utils.temp_dir() mtype = [] fref = {} with spooler.HcamListSpool(resource) as spool: for mccd in spool: if fpair is not None: # prepare fringepairs if fpairobj is None: fpairobj = fringe.MccdFringePair.read(fpair) fpairobj = fpairobj.crop(mccd, nhalf) # here we determine the median levels, subtract them # then (optionally) normalise by the ratio of fringe # amplitudes # generate the name to save to automatically fd, fname = tempfile.mkstemp(suffix=hcam.HCAM, dir=tdir) for cnam in ccds: # its unlikely that fringe frames would be taken # with skips, but you never know. Eliminate them # from consideration now. ccd = mccd[cnam] if ccd.is_data(): cmedian = mccd[cnam].median() medians[cnam][fname] = cmedian mccd[cnam] -= cmedian if cnam not in fref: # save reference fref[cnam] = mccd[cnam] elif fpair is not None: fscale = fpairobj[cnam].scale( mccd[cnam], fref[cnam], nhalf) mccd[cnam] *= fscale print(f'{cnam}, fscale = {fscale:.3f}') # write to disk, save the name, close the filehandle fnames.append(fname) mccd.write(fname) os.close(fd) # a bit of progress info print(f"Saved {', '.join(mtype)} frame to {fname}") # now we go through CCD by CCD, using the first as a template # for the window names in which we will also store the results. template = hcam.MCCD.read(fnames[0]) # Now process each file CCD by CCD to reduce the memory # footprint for cnam in ccds: # Read files into memory, insisting that they # all have the same set of CCDs print(f"\nLoading all CCDs labelled '{cnam}'") accds = [] with spooler.HcamListSpool(fnames, cnam) as spool: mean = None for ccd in spool: if ccd.is_data(): accds.append(ccd) if len(accds) == 0: raise hcam.HipercamError( f"Found no valid examples of CCD {cnam}" f" in {fnames}") else: print(f"Loaded {len(accds)} CCDs") # Median combine for wnam, wind in template[cnam].items(): # build list of all data arrays arrs = [ccd[wnam].data for ccd in accds] # convert to 3D numpy array arr3d = np.stack(arrs) wind.data = np.median(arr3d, axis=0) # Add history template[cnam].head.add_history( f"Median combine of {len(accds)} images") print("Computed and stored their pixel-by-pixel median") # Remove any CCDs not included to avoid impression of having done # something to them dcnams = [] for cnam in template.keys(): if cnam not in ccds: dcnams.append(cnam) for cnam in dcnams: del template[cnam] if fwhm > 0: # apply gaussian smoothing print(f'Smoothing with FWHM = {fwhm}') sigma = fwhm / np.sqrt(8 * np.log(2)) kern = Gaussian2DKernel(sigma) for ccd in template.values(): for wind in ccd.values(): wind.data = convolve_fft(wind.data, kern, "fill", wind.median()) # write out template.write(output, clobber) print(f"\nFinal result written to {output}") print('makefringe finished')
def makeflat(args=None): """``makeflat [source] (run first last [twait tmax] | flist) ngroup bias dark ccd [clobber] output`` Averages a set of images to make a flat field. Typically flat-fields for HiPERCAM and ULTRA(CAM|SPEC) are taken with a strongly time-variable twilight sky as the Sun sets or rises. A typical flat field run may start out bright, or even saturated, but by the end be only a few thousand counts above bias. Moreover, there are very often stars visible in the images, so we usually take them while offsetting the telescope in a spiral pattern. The challenge is to combine these images while rejecting the stars and saturated frames and giving due weight to the better exposed images. This moreover has to be done for each CCD which vary significantly in sensitivity. 'makeflat' does this as follows: given an input list of files (or optionally a single run), it reads them all in, debiases them (optionally), and calculates the mean count level in each CCD, normalises by the mean and writes out the results to temporary files. For each CCD it then sorts the files by their (original) mean level, and for those that lie between defined limits it takes the median of the mean-mormalised frames in groups of defined size. Thus, say one had 75 OK images, then these would be divided into 10 groups, the first 9 having 7 frames, the last having 16. The median average of each of these would be taken. In each case the mean levels would be adjusted to be the same before taking the average to overcome the problem of taking a median of a time-variable sky. The assumption is that while the level may vary, the pattern of the image does not. It is up to the user to check that this is correct. Each of the medians is adjusted to have a mean equal to the sum of the means of the input frames. Finally the normal average of all of these median frames is taken and the mean level of the final output normalised to 1. The first step, taking the median in groups is designed to remove the stars assuming that the telescope was spiralled. The size of the groups ('ngroup' below is a crucial parameter in whether this works). A good strategy is to run makeflat for a succession of ever larger 'ngroup' and then to divide the results into each other to see if stars are visible. The final step, the average of the medians with adjusted mean levels, is to ensure that the flats are combined in a way that reflects the level of signal that they have, i.e. to avoid giving equal weights to the median of a series of flats with 20,000 counts per pixel and another series with 1,000 counts per pixel. This somewhat complex procedure is implemented through a series of temporary files which are written and read as the script runs, but deleted at its end. This allows very large numbers to be combined as long as there is enough memory to load 'ngroup' CCDs simultaneously, which should usually be fine. Parameters: source : str [hidden] Data source, five options: | 'hs' : HiPERCAM server | 'hl' : local HiPERCAM FITS file | 'us' : ULTRACAM server | 'ul' : local ULTRACAM .xml/.dat files | 'hf' : list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. run : str [if source ends 's' or 'l'] run number to access, e.g. 'run034' flist : str [if source ends 'f'] name of file list first : int [if source ends 's' or 'l'] exposure number to start from. 1 = first frame ('0' is not supported). last : int [if source ends 's' or 'l'] last exposure number must be >= first or 0 for the whole lot. twait : float [if source ends 's' or 'l'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's' or 'l'; hidden] maximum time to wait between attempts to find a new exposure, seconds. ngroup : int the number of frames. Probably should be at least 5, preferably more. Experiment to see its effect. bias : string Name of bias frame to subtract, 'none' to ignore. dark : str Name of dark frame to subtract, 'none' to ignore. Note that it is assumed all CCDs have the same exposure time when making a dark correction. ccd : str CCD(s) to process, '0' for all, '1 3' for '1' and '3' only, etc. Would almost always expect this to be set = '0'. lower : list of floats Lower limits to the mean count level for a flat to be included. The count level is determined after bias subtraction. Should be the same number as the selected CCDs, and will be assumed to be in the same order. Use this to elminate frames that are of so low a level that the accuracy of the bias subtraction could be a worry. Suggested hipercam values: 3000 for each CCD. Enter values separated by spaces. upper : list of floats Upper limits to the mean count level for a flat to be included. The count level is determined *after* bias subtraction. Should be the same number as the selected CCDs, and will be assumed to be in the same order. Use this to eliminate saturated, peppered or non-linear frames. Suggested hipercam values: 58000, 58000, 58000, 40000 and 40000 for CCDs 1, 2, 3, 4 and 5. Enter values separated by spaces. ULTRACAM values 49000, 29000, 27000 for CCDs 1, 2 and 3. clobber : bool [hidden] clobber any pre-existing output files output : str output file """ command, args = utils.script_args(args) # get the inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("source", Cline.GLOBAL, Cline.HIDE) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("last", Cline.LOCAL, Cline.PROMPT) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("ngroup", Cline.LOCAL, Cline.PROMPT) cl.register("bias", Cline.LOCAL, Cline.PROMPT) cl.register("dark", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("lower", Cline.LOCAL, Cline.PROMPT) cl.register("upper", Cline.LOCAL, Cline.PROMPT) cl.register("clobber", Cline.LOCAL, Cline.HIDE) cl.register("output", Cline.LOCAL, Cline.PROMPT) # get inputs source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", "hl", lvals=("hs", "hl", "us", "ul", "hf"), ) # set a flag server_or_local = source.endswith("s") or source.endswith("l") if server_or_local: resource = cl.get_value("run", "run name", "run005") first = cl.get_value("first", "first frame to average", 1, 1) last = cl.get_value("last", "last frame to average (0 for all)", first, 0) twait = cl.get_value("twait", "time to wait for a new frame [secs]", 1.0, 0.0) tmax = cl.get_value("tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0) else: resource = cl.get_value("flist", "file list", cline.Fname("files.lis", hcam.LIST)) first = 1 ngroup = cl.get_value("ngroup", "number of frames per median average group", 3, 1) # bias frame (if any) bias = cl.get_value( "bias", "bias frame ['none' to ignore]", cline.Fname("bias", hcam.HCAM), ignore="none", ) # dark frame (if any) dark = cl.get_value( "dark", "dark frame ['none' to ignore]", cline.Fname("dark", hcam.HCAM), ignore="none", ) ccdinf = spooler.get_ccd_pars(source, resource) if len(ccdinf) > 1: ccd = cl.get_value("ccd", "CCD(s) to process [0 for all]", "0") if ccd == "0": ccds = list(ccdinf.keys()) else: ccds = ccd.split() else: ccds = list(ccdinf.keys()) # need to check that the default has the right number of items, if not # overr-ride it lowers = cl.get_default("lower") if len(lowers) != len(ccds): cl.set_default("lower", len(ccds) * (5000, )) lowers = cl.get_value( "lower", "lower limits on mean count level for included flats, 1 per CCD", len(ccds) * (5000, ), ) uppers = cl.get_default("upper") if len(uppers) != len(ccds): cl.set_default("upper", len(ccds) * (50000, )) uppers = cl.get_value( "upper", "lower limits on mean count level for included flats, 1 per CCD", len(ccds) * (50000, ), ) clobber = cl.get_value("clobber", "clobber any pre-existing files on output", False) output = cl.get_value( "output", "output average", cline.Fname("hcam", hcam.HCAM, cline.Fname.NEW if clobber else cline.Fname.NOCLOBBER), ) # inputs done with. try: # big try / except section here to trap ctrl-C to allow the temporary # files to be deleted. First make a directory for the temporary files if server_or_local: print("\nCalling 'grab' ...") args = [ None, "prompt", source, resource, "yes", str(first), str(last), "no", str(twait), str(tmax), "none", "f32", ] resource = hcam.scripts.grab(args) # at this point 'resource' is a list of files, no matter the input # method. # Read all the files to determine mean levels (after bias subtraction) # save the bias-subtracted, mean-level normalised results to temporary # files print("Reading all files in to determine their mean levels") bframe, dframe = None, None means = {} for cnam in ccds: means[cnam] = {} # We might have a load of temporaries from grab, but we are about to # make some more to save the bias-subtracted normalised versions. tdir = os.path.join(tempfile.gettempdir(), "hipercam-{:s}".format(getpass.getuser())) os.makedirs(tdir, exist_ok=True) fnames = [] with spooler.HcamListSpool(resource) as spool: for mccd in spool: if bias is not None: # bias subtraction if bframe is None: bframe = hcam.MCCD.read(bias) bframe = bframe.crop(mccd) mccd -= bframe bexpose = bframe.head.get("EXPTIME", 0.0) else: bexpose = 0.0 if dark is not None: # dark subtraction if dframe is None: dframe = hcam.MCCD.read(dark) dframe = dframe.crop(mccd) # Factor to scale the dark frame by before # subtracting from flat. Assumes that all # frames have same exposure time. scale = (mccd.head["EXPTIME"] - bexpose) / dframe.head["EXPTIME"] # make dark correction mccd -= scale * dframe # here we determine the mean levels, store them # then normalise the CCDs by them and save the files # to disk # generate the name to save to automatically fd, fname = tempfile.mkstemp(suffix=hcam.HCAM, dir=tdir) for cnam in ccds: # its unlikely that flats would be taken with skips, but # you never know. Eliminate them from consideration now. ccd = mccd[cnam] if ccd.is_data(): cmean = mccd[cnam].mean() means[cnam][fname] = cmean mccd[cnam] /= cmean # write the disk, save the name, close the filehandle mccd.write(fname) fnames.append(fname) os.close(fd) # a bit of progress info if bias is not None: print("Saved debiassed, normalised" " flat to {:s}".format(fname)) else: print("Saved normalised flat to {:s}".format(fname)) # now we go through CCD by CCD, using the first as a template # for the window names in which we will also store the results. template = hcam.MCCD.read(fnames[0]) for cnam, lower, upper in zip(ccds, lowers, uppers): tccd = template[cnam] # get the keys (filenames) and corresponding mean values mkeys = np.array(list(means[cnam].keys())) mvals = np.array(list(means[cnam].values())) # chop down to acceptable ones ok = (mvals > lower) & (mvals < upper) mkeys = mkeys[ok] mvals = mvals[ok] # some more progress info print("Found {:d} frames for CCD {:s}".format(len(mkeys), cnam)) if len(mkeys) == 0: print((".. cannot average 0 frames;" " will skip CCD {:s}").format(cnam)) continue elif len(mkeys) < ngroup: print(("WARNING: fewer than ngroup = {:d} frames" " found. Output for CCD {:s} could be poor").format( ngroup, cnam)) nchunk = len(mkeys) // ngroup if nchunk == 0: nchunk = 1 # sort by mean value isort = mvals.argsort() mvals = mvals[isort] mkeys = mkeys[isort] # wsum used to sum all the eight factors to allow overall # normalisation at the end of the loop wsum = 0.0 for n in range(nchunk): # loop through in chunks of ngroup at a time with a # potentially larger group to sweep up the end ones. n1 = ngroup * n n2 = n1 + ngroup if n == nchunk: n2 = len(mkeys) # load the CCDs of this group ccdgroup = [] with spooler.HcamListSpool(list(mkeys[n1:n2]), cnam) as spool: for ccd in spool: ccdgroup.append(ccd) # take median of the group to get rid of jumping # stars. 'weight' used to weight the results when summing the # results together. this stage is like the 'n' option of # 'combine' except we have already cut out any junk frames and # we have normalised the remainder weight = mvals[n1:n2].sum() wsum += weight for wnam, wind in tccd.items(): # go through each window, building a list of all data # arrays arrs = [ccd[wnam].data for ccd in ccdgroup] arr3d = np.stack(arrs) # at this point, arr3d is a 3D array, with the first # dimension (axis=0) running over the images. We take the # median over this axis. The first time through we put # this straight into the output Window. afterwards we add # it in (with the appropriate weight) if n == 0: wind.data = weight * np.median(arr3d, axis=0) else: wind.data += weight * np.median(arr3d, axis=0) # Normalise the final result to a mean = 1. tccd /= wsum # Add some history tccd.head.add_history( ("result of makeflat on {:d}" " frames, ngroup = {:d}").format(len(mkeys), ngroup)) # Remove any CCDs not included to avoid impression of having done # something to them for cnam in template: if cnam not in ccds: del template[cnam] # write out template.write(output, clobber) print("\nFinal result written to {:s}".format(output)) except KeyboardInterrupt: print("\nmakeflat aborted") if server_or_local: # grab has created a load of temporaries, including the file list # 'resource' with open(resource) as fin: for fname in fin: os.remove(fname.strip()) os.remove(resource) # and another load were created during the later running of the script for fname in fnames: os.remove(fname) print("temporary files have been removed.")
def hfilter(args=None): """``hfilter input ccd nx filter [fwhm] output`` Filters a multi-CCD image. e.g. smooths it. Can be useful in some analysis steps, e.g. for picking out defects, division by a smoother version of an image can be useful. Parameters: input : string name of MCCD input file ccd : string CCD(s) to filter, '0' for all. If not '0' then '1', '2' or even '3 4' are possible inputs (without the quotes). filter : string [single character] type of filter. 'g' = gaussian. Uses an FFT-based approach which regards the boundaries as periodic, so you will get significant edge-effects if the values on opposite edges of a window are significantly different from each other. clobber : bool [hidden] clobber any pre-existing output files output : string name of MCCD output file """ command, args = utils.script_args(args) # get input section with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("input", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("filter", Cline.LOCAL, Cline.PROMPT) cl.register("fwhm", Cline.LOCAL, Cline.PROMPT) cl.register("clobber", Cline.LOCAL, Cline.HIDE) cl.register("output", Cline.LOCAL, Cline.PROMPT) # get inputs frame = cl.get_value("input", "frame to filter", cline.Fname("hcam", hcam.HCAM)) mccd = hcam.MCCD.read(frame) max_ccd = len(mccd) if max_ccd > 1: ccd = cl.get_value("ccd", "CCD(s) to filter [0 for all]", "0") if ccd == "0": ccds = list(mccd.keys()) else: ccds = ccd.split() else: ccds = list(mccd.keys()) # define the display intensities filt = cl.get_value("filter", "filter to apply: g(aussian)", "g", lvals=[ "g", ]) if filt == "g": fwhm = cl.get_value("fwhm", "FWHM for gaussian filter (binned pixels)", 4.0, 0.01) clobber = cl.get_value("clobber", "clobber any pre-existing files on output", False) output = cl.get_value( "output", "output file", cline.Fname( "filtered", hcam.HCAM, cline.Fname.NEW if clobber else cline.Fname.NOCLOBBER, ), ) # all inputs obtained, filter if filt == "g": # create filter kernal kern = Gaussian2DKernel(fwhm / np.sqrt(8 * np.log(2))) for cnam in ccds: ccd = mccd[cnam] for wnam, wind in ccd.items(): if filt == "g": wind.data = convolve_fft(wind.data, kern, "wrap") print("Filtered CCD {:s}".format(cnam)) # write out mccd.write(output, clobber) print("\nFiltered result written to {:s}".format(output))
def psfaper(args=None): """``psfaper mccd aper ccd [linput width height] nx method thresh niters gfac msub iset (ilo ihi | plo phi) [beta fwmin fwhm shbox smooth splot fhbox read gain rejthresh]`` Definition of apertures for PSF photometry. This occurs in three steps - first the user draws a box around the region of interest, then the user selects a reference star which is used to measure the PSF. Finally, all the stars in the region of interest are located following an iterative application of FINDing stars, FITting the image using a PSF model, SUBTRACTing the model. An aperture file is automatically created, which can be edited with |setaper| if necessary. Parameters: mccd : string name of an MCCD file, as produced by e.g. 'grab' aper : string the name of an aperture file. If it exists it will be read so that apertures can be added to it. If it does not exist, it will be created on exiting the routine. The aperture files are is a fairly readable / editiable text format ccd : string CCD(s) to plot, '0' for all. If not '0' then '1', '2' or even '3 4' are possible inputs (without the quotes). '3 4' will plot CCD '3' and CCD '4'. If you want to plot more than one CCD, then you will be prompted for the number of panels in the X direction. This parameter will not be prompted if there is only one CCD in the file. linput : string [hidden] sets the way in which the apertures are labelled. 'n' = numerical input, with the program just incrementing by 1 for each successive aperture; 's' = single character (without requiring the user to hit hit <CR>); 'm' = multi-character, ending with <CR>. Allowed characters are 0-9, a-z, A-Z, no spaces or punctuation, but a single '0' on its own is not permitted. width : float [hidden] plot width (inches). Set = 0 to let the program choose. height : float [hidden] plot height (inches). Set = 0 to let the program choose. BOTH width AND height must be non-zero to have any effect nx : int number of panels across to display, prompted if more than one CCD is to be plotted. method : string this defines the profile function. Either a gaussian or a moffat profile, 'g' or 'm'. The latter should usually be best. thresh : float this sets the threshold for detection of stars in the image, in multiples of the background RMS niters : int when detecting stars, this sets the number of iterations of the FIND-FIT-SUBTRACT loop. gfac : float in PSF fitting, stars are split into groups, and each group is fit seperately. This is to avoid fitting models with large numbers of free parameters. This number, multiplied by the FWHM, gives the maximum seperation for stars to be fitted within the same group. msub : bool True/False to subtract median from each window before scaling iset : string [single character] determines how the intensities are determined. There are three options: 'a' for automatic simply scales from the minimum to the maximum value found on a per CCD basis. 'd' for direct just takes two numbers from the user. 'p' for percentile dtermines levels based upon percentiles determined from the entire CCD on a per CCD bais. ilo : float [if iset=='d'] lower intensity level ihi : float [if iset=='d'] upper intensity level plo : float [if iset=='p'] lower percentile level phi : float [if iset=='p'] upper percentile level beta : float [if method == 'm'; hidden] default Moffat exponent fwmin : float [hidden] minimum FWHM to allow, unbinned pixels. fwhm : float [hidden] default FWHM, unbinned pixels. shbox : float [hidden] half width of box for searching for a star, unbinned pixels. The brightest target in a region +/- shbox around an intial position will be found. 'shbox' should be large enough to allow for likely changes in position from frame to frame, but try to keep it as small as you can to avoid jumping to different targets and to reduce the chances of interference by cosmic rays. smooth : float [hidden] FWHM for gaussian smoothing, binned pixels. The initial position for fitting is determined by finding the maximum flux in a smoothed version of the image in a box of width +/- shbox around the starter position. Typically should be comparable to the stellar width. Its main purpose is to combat cosmic rays which tend only to occupy a single pixel. fhbox : float [hidden] half width of box for profile fit, unbinned pixels. The fit box is centred on the position located by the initial search. It should normally be > ~2x the expected FWHM. read : float [hidden] readout noise, RMS ADU, for assigning uncertainties gain : float [hidden] gain, ADU/count, for assigning uncertainties rejthresh : float [hidden] thresh rejection threshold Various standard keyboard shortcuts (e.g. 's' to save) are disabled as they just confuse things and are of limited use in setaper in any case. Some aspects of the usage of matplotlib in psfaper are tricky. It is possible that particular 'backends' will cause problems. I have tested this with Qt4Agg, Qt5agg and GTK3Agg. One aspect is the cursor icon in pan mode is a rather indistinct hand where one can't tell what is being pointed at. I have therefore suppressed this, but only for the tested backends. Others would need require further investigation. """ command, args = utils.script_args(args) # get input section with Cline('HIPERCAM_ENV', '.hipercam', command, args) as cl: # register parameters cl.register('mccd', Cline.LOCAL, Cline.PROMPT) cl.register('aper', Cline.LOCAL, Cline.PROMPT) cl.register('ccd', Cline.LOCAL, Cline.PROMPT) cl.register('linput', Cline.LOCAL, Cline.HIDE) cl.register('width', Cline.LOCAL, Cline.HIDE) cl.register('height', Cline.LOCAL, Cline.HIDE) cl.register('nx', Cline.LOCAL, Cline.PROMPT) cl.register('method', Cline.LOCAL, Cline.PROMPT) cl.register('thresh', Cline.LOCAL, Cline.PROMPT) cl.register('niters', Cline.LOCAL, Cline.PROMPT) cl.register('gfac', Cline.LOCAL, Cline.PROMPT) cl.register('msub', Cline.GLOBAL, Cline.PROMPT) cl.register('iset', Cline.GLOBAL, Cline.PROMPT) cl.register('ilo', Cline.GLOBAL, Cline.PROMPT) cl.register('ihi', Cline.GLOBAL, Cline.PROMPT) cl.register('plo', Cline.GLOBAL, Cline.PROMPT) cl.register('phi', Cline.GLOBAL, Cline.PROMPT) cl.register('beta', Cline.LOCAL, Cline.HIDE) cl.register('fwhm', Cline.LOCAL, Cline.HIDE) cl.register('fwmin', Cline.LOCAL, Cline.HIDE) cl.register('shbox', Cline.LOCAL, Cline.HIDE) cl.register('smooth', Cline.LOCAL, Cline.HIDE) cl.register('fhbox', Cline.LOCAL, Cline.HIDE) cl.register('read', Cline.LOCAL, Cline.HIDE) cl.register('gain', Cline.LOCAL, Cline.HIDE) cl.register('rejthresh', Cline.LOCAL, Cline.HIDE) # get inputs mccd = cl.get_value('mccd', 'frame to plot', cline.Fname('hcam', hcam.HCAM)) mccd = hcam.MCCD.read(mccd) aper = cl.get_value('aper', 'name of aperture file', cline.Fname('hcam', hcam.APER, exist=False)) if os.path.exists(aper): # read in old apertures mccdaper = hcam.MccdAper.read(aper) print('Loaded existing file = {:s}'.format(aper)) else: # create empty container mccdaper = hcam.MccdAper() print('No file called {:s} exists; ' 'will create from scratch'.format(aper)) # define the panel grid try: nxdef = cl.get_default('nx') except KeyError: nxdef = 3 max_ccd = len(mccd) if max_ccd > 1: ccd = cl.get_value('ccd', 'CCD(s) to plot [0 for all]', '0') if ccd == '0': ccds = list(mccd.keys()) else: ccds = ccd.split() else: ccds = list(mccd.keys()) width = cl.get_value('width', 'plot width (inches)', 0.) height = cl.get_value('height', 'plot height (inches)', 0.) # number of panels in X if len(ccds) > 1: nxdef = min(len(ccds), nxdef) cl.set_default('nx', nxdef) nx = cl.get_value('nx', 'number of panels in X', 3, 1) else: nx = 1 # PSF fitting stuff method = cl.get_value('method', 'fit method g(aussian) or m(offat)', 'm', lvals=['g', 'm']) if method == 'm': beta = cl.get_value('beta', 'initial exponent for Moffat fits', 5., 0.5) else: beta = 0 fwhm_min = cl.get_value('fwmin', 'minimum FWHM to allow [unbinned pixels]', 1.5, 0.01) fwhm = cl.get_value('fwhm', 'initial FWHM [unbinned pixels] for profile fits', 6., fwhm_min) gfac = cl.get_value( 'gfac', 'multiple of FWHM used to group stars for fitting', 2.0, 0.1) thresh = cl.get_value( 'thresh', 'threshold for object detection (multiple of sky RMS)', 3.0, 0.1) niters = cl.get_value('niters', 'number of iterations of FIND-FIT-SUBTRACT', 2, 1) # define the display intensities msub = cl.get_value('msub', 'subtract median from each window?', True) iset = cl.get_value('iset', 'set intensity a(utomatically),' ' d(irectly) or with p(ercentiles)?', 'a', lvals=['a', 'A', 'd', 'D', 'p', 'P']) iset = iset.lower() plo, phi = 5, 95 ilo, ihi = 0, 1000 if iset == 'd': ilo = cl.get_value('ilo', 'lower intensity limit', 0.) ihi = cl.get_value('ihi', 'upper intensity limit', 1000.) elif iset == 'p': plo = cl.get_value('plo', 'lower intensity limit percentile', 5., 0., 100.) phi = cl.get_value('phi', 'upper intensity limit percentile', 95., 0., 100.) nxmax, nymax = 0, 0 for cnam in ccds: nxmax = max(nxmax, mccd[cnam].nxtot) nymax = max(nymax, mccd[cnam].nytot) # might be worth trying to improve this at some point xlo, xhi, ylo, yhi = 0, nxmax + 1, 0, nymax + 1 shbox = cl.get_value( 'shbox', 'half width of box for initial' ' location of target [unbinned pixels]', 11., 2.) smooth = cl.get_value( 'smooth', 'FWHM for smoothing for initial object' ' detection [binned pixels]', 6.) fhbox = cl.get_value( 'fhbox', 'half width of box for profile fit' ' [unbinned pixels]', 21., 3.) read = cl.get_value('read', 'readout noise, RMS ADU', 3.) gain = cl.get_value('gain', 'gain, ADU/e-', 1.) rejthresh = cl.get_value('rejthresh', 'RMS rejection threshold for sky fitting', 4.) # Inputs obtained. # re-configure keyboard shortcuts to avoid otherwise confusing behaviour # quit_all does not seem to be universal, hence the try/except try: mpl.rcParams['keymap.back'] = '' mpl.rcParams['keymap.forward'] = '' mpl.rcParams['keymap.fullscreen'] = '' mpl.rcParams['keymap.grid'] = '' mpl.rcParams['keymap.home'] = '' mpl.rcParams['keymap.pan'] = '' mpl.rcParams['keymap.quit'] = '' mpl.rcParams['keymap.save'] = '' mpl.rcParams['keymap.pan'] = '' mpl.rcParams['keymap.save'] = '' mpl.rcParams['keymap.xscale'] = '' mpl.rcParams['keymap.yscale'] = '' mpl.rcParams['keymap.zoom'] = '' except KeyError: pass # start plot if width > 0 and height > 0: fig = plt.figure(figsize=(width, height)) else: fig = plt.figure() # get the navigation toolbar. toolbar = fig.canvas.manager.toolbar nccd = len(ccds) ny = nccd // nx if nccd % nx == 0 else nccd // nx + 1 # we need to store some stuff ax = None cnams = {} anams = {} # this is a container for all the objects used to plot apertures to allow # deletion. This is Group of Group objects supporting tuple storage. The # idea is that pobjs[cnam][anam] returns the objects used to plot aperture # anam of CCD cnam. It is initially empty, pobjs = hcam.Group(hcam.Group) for n, cnam in enumerate(ccds): if ax is None: axes = ax = fig.add_subplot(ny, nx, n + 1) axes.set_aspect('equal', adjustable='box') axes.set_xlim(xlo, xhi) axes.set_ylim(ylo, yhi) else: axes = fig.add_subplot(ny, nx, n + 1, sharex=ax, sharey=ax) axes.set_aspect('equal') if msub: # subtract median from each window pccd = deepcopy(mccd) for wind in pccd[cnam].values(): wind -= wind.median() else: pccd = mccd hcam.mpl.pCcd(axes, pccd[cnam], iset, plo, phi, ilo, ihi, 'CCD {:s}'.format(cnam)) # keep track of the CCDs associated with each axes cnams[axes] = cnam # and axes associated with each CCD anams[cnam] = axes if cnam in mccdaper: # plot any pre-existing apertures, keeping track of # the plot objects pobjs[cnam] = hcam.mpl.pCcdAper(axes, mccdaper[cnam]) else: # add in an empty CcdApers for any CCD not already present mccdaper[cnam] = hcam.CcdAper() # and an empty container for any new plot objects pobjs[cnam] = hcam.Group(tuple) print(""" Now use the mouse and the pan/zoom tools to zoom in to the region of interest (ROI) for PSF photometry. All existing apertures inside this region will be retained, but apertures outside this region will be deleted. Once you are happy with your selection, use the key commands listed below to select one or more bright, isolated, stars to use as references to determine the PSF. These stars will also be set as reference objects in the final aperture file. Key commands: a(dd) : add an aperture d(elete) : delete an aperture u(nlink) : unlink axes for all CCDs so that you can tweak ROI for CCDs independently q(uit) : quit interactive step and find all stars in ROI. Hitting 'd' will delete the aperture nearest to the cursor, as long as it is close enough. """) try: plt.tight_layout() except: pass # create a class for picking reference star and zooming in picker = PickRef(mccd, cnams, anams, toolbar, fig, mccdaper, method, beta, fwhm, gfac, niters, thresh, fwhm_min, shbox, smooth, fhbox, read, gain, rejthresh, pobjs, aper) # squeeze space a bit plt.subplots_adjust(wspace=0.1, hspace=0.1) # finally show stuff .... plt.show()
def combine(args=None): """``combine list bias dark flat method (sigma) adjust (usemean) [plot clobber] output`` Combines a series of images defined by a list using median or clipped mean combination. Only combines those CCDs for which is_data() is true (i.e. it skips blank frames caused by NSKIP / NBLUE options) Parameters: list : string list of hcm files with images to combine. The formats of the images should all match bias : string Name of bias frame to subtract, 'none' to ignore. dark : string Name of dark frame to subtract, 'none' to ignore. flat : string Name of flat field frame to subtract, 'none' to ignore. method : string 'm' for median, 'c' for clipped mean. See below for pros and cons. sigma : float [if method == 'c'] With clipped mean combination, pixels that deviate by more than sigma RMS from the mean are kicked out. This is carried out in an iterative manner. sigma <= 0 implies no rejection, just a straight average. sigma=3 is typical. adjust : string adjustments to make: 'i' = ignore; 'n' = normalise the mean or median of all frames to match the first; 'b' = add offsets so that the mean or median of all frames is the same as the first. Option 'n' is useful for twilight flats and fringe frames; 'b' is good for combining biases. usemean : bool [if adjust == 'n' or 'b'] True to use the mean rather than the median for normalisation or biass offsetting. plot : bool [hidden, if adjust == 'n' or 'b'; defaults to False] make a plot of the mean versus frame number. This can provide a quick check that the frames are not too different. clobber : bool [hidden] clobber any pre-existing output files output : string output file Clipped mean can work well for large numbers of frames but gets worse for small numbers as the RMS can be heavily influenced by a single bad value. The median can be better in such cases, but has the downside of digitisation noise. For instance, the average of 100 bias frames could have a noise level significantly below 1 count, depending upon the readout noise, and the +/- 0.5 count uncertainty of median combination may be worse than this. .. Note:: This routine reads all inputs into memory, so can be a bit of a hog. However, it does so one CCD at a time to alleviate this. It will fail if it cannot find a valid frame for any CCD. """ command, args = utils.script_args(args) # get the inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("list", Cline.GLOBAL, Cline.PROMPT) cl.register("bias", Cline.LOCAL, Cline.PROMPT) cl.register("dark", Cline.LOCAL, Cline.PROMPT) cl.register("flat", Cline.LOCAL, Cline.PROMPT) cl.register("method", Cline.LOCAL, Cline.PROMPT) cl.register("sigma", Cline.LOCAL, Cline.PROMPT) cl.register("adjust", Cline.LOCAL, Cline.PROMPT) cl.register("usemean", Cline.LOCAL, Cline.PROMPT) cl.register("plot", Cline.LOCAL, Cline.HIDE) cl.register("clobber", Cline.LOCAL, Cline.HIDE) cl.register("output", Cline.LOCAL, Cline.PROMPT) # get inputs flist = cl.get_value("list", "list of files to combine", cline.Fname("files", hcam.LIST)) # bias frame (if any) bias = cl.get_value( "bias", "bias frame ['none' to ignore]", cline.Fname("bias", hcam.HCAM), ignore="none", ) if bias is not None: # read the bias frame bias = hcam.MCCD.read(bias) # dark frame (if any) dark = cl.get_value( "dark", "dark frame ['none' to ignore]", cline.Fname("dark", hcam.HCAM), ignore="none", ) if dark is not None: # read the dark frame dark = hcam.MCCD.read(dark) # flat frame (if any) flat = cl.get_value( "flat", "flat frame ['none' to ignore]", cline.Fname("flat", hcam.HCAM), ignore="none", ) if flat is not None: # read the flat frame flat = hcam.MCCD.read(flat) method = cl.get_value("method", "c(lipped mean), m(edian)", "c", lvals=("c", "m")) if method == "c": sigma = cl.get_value("sigma", "number of RMS deviations to clip", 3.0) adjust = cl.get_value( "adjust", "i(gnore), n(ormalise), b(ias offsets)", "i", lvals=("i", "n", "b"), ) if adjust == "n" or adjust == "b": usemean = cl.get_value( "usemean", "use the mean for normalisation / offsetting [else median]", True, ) plot = cl.get_value( "plot", "plot mean levels versus frame number?" if usemean else "plot median levels versus frame number?", False, ) else: plot = False usemean = False clobber = cl.get_value("clobber", "clobber any pre-existing files on output", False) outfile = cl.get_value( "output", "output file", cline.Fname("hcam", hcam.HCAM, cline.Fname.NEW if clobber else cline.Fname.NOCLOBBER), ) # inputs done with # Read the first file of the list to act as a template # for the CCD names etc. with open(flist) as fin: for line in fin: if not line.startswith("#") and not line.isspace(): template_name = line.strip() break else: raise hcam.HipercamError("List = {:s} is empty".format(flist)) template = hcam.MCCD.read(utils.add_extension(template_name, hcam.HCAM)) if bias is not None: # crop the bias bias = bias.crop(template) if dark is not None: # crop the dark dark = dark.crop(template) if flat is not None: # crop the flat flat = flat.crop(template) # Now process each file CCD by CCD to reduce the memory # footprint for cnam in template: # Read files into memory, insisting that they # all have the same set of CCDs print("\nLoading all CCDs labelled '{:s}' from {:s}".format( cnam, flist)) ccds, means = [], [] nrej, ntot = 0, 0 with spooler.HcamListSpool(flist, cnam) as spool: if bias is not None: # extract relevant CCD from the bias bccd = bias[cnam] bexpose = bias.head.get("EXPTIME", 0.0) else: bexpose = 0.0 if dark is not None: # extract relevant CCD from the dark dccd = dark[cnam] dexpose = dark.head["EXPTIME"] if flat is not None: # extract relevant CCD from the flat fccd = flat[cnam] mean = None for ccd in spool: if ccd.is_data(): if bias is not None: # subtract bias ccd -= bccd if dark is not None: # subtract dark scale = (ccd.head["EXPTIME"] - bexpose) / dexpose ccd -= scale * dccd if flat is not None: # apply flat ccd /= fccd # keep the result ccds.append(ccd) if (adjust == "b" or adjust == "n") and mean is None: # store the first mean [median] if usemean: mean = ccd.mean() else: mean = ccd.median() if len(ccds) == 0: raise hcam.HipercamError("Found no valid examples of CCD {:s}" " in list = {:s}".format(cnam, flist)) else: print("Loaded {:d} CCDs".format(len(ccds))) if adjust == "b" or adjust == "n": # adjust the means print("Computing and adjusting their mean levels") # now carry out the adjustments for ccd in ccds: if usemean: cmean = ccd.mean() else: cmean = ccd.median() means.append(cmean) if adjust == "b": ccd += mean - cmean elif adjust == "n": ccd *= mean / cmean if plot: plt.plot(means) plt.plot(means, ".k") plt.text(len(means) + 1, means[-1], cnam, va="center", ha="left") # Finally, combine if method == "m": print("Combining them (median) and storing the result") elif method == "c": print("Combining them (clipped mean) and storing the result") else: raise NotImplementedError( "method = {:s} not implemented".format(method)) for wnam, wind in template[cnam].items(): # build list of all data arrays arrs = [ccd[wnam].data for ccd in ccds] arr3d = np.stack(arrs) # at this point, arr3d is a 3D array, with the first dimension # (axis=0) running over the images. We want to average / median # over this axis. if method == "m": # median wind.data = np.median(arr3d, axis=0) elif method == "c": # Cython routine avgstd requires np.float32 input arr3d = arr3d.astype(np.float32) if sigma > 0.0: avg, std, num = support.avgstd(arr3d, sigma) nrej += len(ccds) * num.size - num.sum() ntot += len(ccds) * num.size else: avg = np.mean(arr3d, axis=0) wind.data = avg # Add history if method == "m": template[cnam].head.add_history( "Median combine of {:d} images".format(len(ccds))) elif method == "c": print("Rejected {:d} pixels = {:.3f}% of the total".format( nrej, 100 * nrej / ntot)) template[cnam].head.add_history( "Clipped mean combine of {:d} images, sigma = {:.1f}".format( len(ccds), sigma)) # write out template.write(outfile, clobber) print("\nFinal result written to {:s}".format(outfile)) # finally if plot: plt.xlabel("Frame number") plt.ylabel("Mean counts") plt.show()
def redanal(args=None): """``redanal aper log`` This provides some basic stats on a log file such as the fraction of duff points for each aperture of each CCD, how much targets have moved and the like. The aim is to help with setting parameters in the reduce file in difficult cases and to diagnose problems. It will probably be added to with time. Parameters: aper : string the aperture file used for the reduction. log : string the log file. """ command, args = utils.script_args(args) # get input section with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("aper", Cline.LOCAL, Cline.PROMPT) cl.register("log", Cline.LOCAL, Cline.PROMPT) # get inputs aper_file = cl.get_value( "aper", "aperture file used for reduction", cline.Fname("run001", hcam.APER) ) aper = hcam.MccdAper.read(aper_file) log_file = cl.get_value( "log", "ASCII reduction log file to analyse", cline.Fname("run001", hcam.LOG), ) log = hcam.hlog.Hlog.rascii(log_file) for cnam in sorted(log): print() mjds = log[cnam]["MJD"] mjdoks = log[cnam]["MJDok"] diffs = mjds[1:] - mjds[:-1] tgap_max = diffs.max() tgap_mean = diffs.mean() print( "CCD {:s} mean / maximum time gap = {:.2f} / {:.2f} seconds".format( cnam, 86400.0 * tgap_mean, 86400.0 * tgap_max ) ) apnams = log.apnames[cnam] for apnam in apnams: if not aper[cnam][apnam].linked: x = log[cnam]["x_{:s}".format(apnam)] xe = log[cnam]["xe_{:s}".format(apnam)] y = log[cnam]["y_{:s}".format(apnam)] ye = log[cnam]["ye_{:s}".format(apnam)] ok = (xe > 0) & (ye > 0) xdiffs = x[ok][1:] - x[ok][:-1] ydiffs = x[ok][1:] - x[ok][:-1] jitt = np.sqrt(xdiffs ** 2 + ydiffs ** 2) print( "CCD {:s}, ap {:s} has {:d}/{:d}/{:d} OK/NOK/total points. Min/mean/median/max jitter = {:.2f}/{:.2f}/{:.2f}/{:.2f} pixels {:s}".format( cnam, apnam, len(x[ok]), len(x) - len(x[ok]), len(x), jitt.min(), jitt.mean(), np.median(jitt), jitt.max(), "[reference]" if aper[cnam][apnam].ref else "[non-reference]", ) ) else: print( "CCD {:s}, aperture {:s} is linked to aperture {:s}".format( cnam, apnam, aper[cnam][apnam].link ) )
def filtid(args=None): """``filtid [source] (run first last [twait tmax] | flist) trim ([ncol nrow]) ccdref maxlev plot`` Computes the ratio of the mean values in each CCD versus those of a reference CCD. This is supposed to be applied to flat fields. The hope is that this might be characteristic of the filter combination. Obviously requires > 1 CCD, i.e. not ULTRASPEC. Parameters: source : str [hidden] Data source, five options: | 'hs' : HiPERCAM server | 'hl' : local HiPERCAM FITS file | 'us' : ULTRACAM server | 'ul' : local ULTRACAM .xml/.dat files | 'hf' : list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. The standard start-off default for ``source'' can be set using the environment variable HIPERCAM_DEFAULT_SOURCE. e.g. in bash :code:`export HIPERCAM_DEFAULT_SOURCE="us"` would ensure it always started with the ULTRACAM server by default. If unspecified, it defaults to 'hl'. run : str [if source ends 's' or 'l'] run number to access, e.g. 'run034' flist : str [if source ends 'f'] name of file list first : int [if source ends 's' or 'l'] exposure number to start from. 1 = first frame; set = 0 to always try to get the most recent frame (if it has changed). For data from the |hiper| server, a negative number tries to get a frame not quite at the end. i.e. -10 will try to get 10 from the last frame. This is mainly to sidestep a difficult bug with the acquisition system. last : int [if source ends 's' or 'l'] last frame to reduce. 0 to just continue until the end. twait : float [if source ends 's' or 'l'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's' or 'l'; hidden] maximum time to wait between attempts to find a new exposure, seconds. trim : bool [if source starts with 'u'] True to trim columns and/or rows off the edges of windows nearest the readout which can sometimes contain bad data. ncol : int [if trim, hidden] Number of columns to remove (on left of left-hand window, and right of right-hand windows) nrow : int [if trim, hidden] Number of rows to remove (bottom of windows) ccdref : str The reference CCD (usually choose the g-band one) bias : str bias frame to subtract (required) lower : list of floats Lower limits to the mean count level for a flat to be included (after bias subtraction). Should be the same number as the number of CCDs, and will be assumed to be in the same order. Separate with spaces. Prevents low exposure data from being included. upper : list of floats Upper limits to the mean count level for a flat to be included. Should be the same number as the selected CCDs, and will be assumed to be in the same order. Use to eliminate saturated, peppered or non-linear frames. Suggested hipercam values: 58000, 58000, 58000, 40000 and 40000 for CCDs 1, 2, 3, 4 and 5. Enter values separated by spaces. ULTRACAM values 49000, 29000, 27000 for CCDs 1, 2 and 3. plot: bool Plot the fit or not. .. Note:: This is currently adapted specifically for ULTRACAM data """ command, args = utils.script_args(args) # get the inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("source", Cline.GLOBAL, Cline.HIDE) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("last", Cline.LOCAL, Cline.PROMPT) cl.register("trim", Cline.GLOBAL, Cline.PROMPT) cl.register("ncol", Cline.GLOBAL, Cline.HIDE) cl.register("nrow", Cline.GLOBAL, Cline.HIDE) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("ccdref", Cline.LOCAL, Cline.PROMPT) cl.register("bias", Cline.LOCAL, Cline.PROMPT) cl.register("lower", Cline.LOCAL, Cline.PROMPT) cl.register("upper", Cline.LOCAL, Cline.PROMPT) cl.register("plot", Cline.LOCAL, Cline.PROMPT) # get inputs default_source = os.environ.get('HIPERCAM_DEFAULT_SOURCE', 'hl') source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", default_source, lvals=("hs", "hl", "us", "ul", "hf"), ) # set some flags server_or_local = source.endswith("s") or source.endswith("l") if server_or_local: resource = cl.get_value("run", "run name", "run005") first = cl.get_value("first", "first frame to consider", 1, 1) last = cl.get_value("last", "last frame to consider", 0, 0) if last and last < first + 2: raise hcam.HipercamError( "Must consider at least 2 exposures, and preferably more") twait = cl.get_value("twait", "time to wait for a new frame [secs]", 1.0, 0.0) tmax = cl.get_value("tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0) else: resource = cl.get_value("flist", "file list", cline.Fname("files.lis", hcam.LIST)) first = 1 trim = cl.get_value("trim", "do you want to trim edges of windows?", True) if trim: ncol = cl.get_value("ncol", "number of columns to trim from windows", 0) nrow = cl.get_value("nrow", "number of rows to trim from windows", 0) # define the panel grid. first get the labels and maximum dimensions ccdinf = spooler.get_ccd_pars(source, resource) if len(ccdinf) == 1: raise hcam.HipercamError( "Only one CCD; this routine only works for > 1 CCD") ccds = list(ccdinf.keys()) ccdref = cl.get_value("ccdref", "the reference CCD for the ratios", ccds[1], lvals=ccds) bias = cl.get_value("bias", "bias frame", cline.Fname("bias", hcam.HCAM)) # read the bias frame bias = hcam.MCCD.read(bias) if len(bias) != len(ccds): raise ValueError('bias has an incompatible number of CCDs') # need to check that the default has the right number of items, if not, # over-ride it lowers = cl.get_default("lower") if lowers is not None and len(lowers) != len(ccds): cl.set_default("lower", len(ccds) * (5000, )) lowers = cl.get_value( "lower", "lower limits on mean count level for included flats, 1 per CCD", len(ccds) * (5000, ), ) lowers = {k: v for k, v in zip(ccds, lowers)} uppers = cl.get_default("upper") if uppers is not None and len(uppers) != len(ccds): cl.set_default("upper", len(ccds) * (50000, )) uppers = cl.get_value( "upper", "lower limits on mean count level for included flats, 1 per CCD", len(ccds) * (50000, ), ) uppers = {k: v for k, v in zip(ccds, uppers)} plot = cl.get_value("plot", "plot the results?", True) ################################################################ # # all the inputs have now been obtained. Get on with doing stuff total_time = 0. xdata, ydata = {}, {} # only want to look at CCDs other than ccdref ccds.remove(ccdref) # access images with spooler.data_source(source, resource, first, full=False) as spool: # 'spool' is an iterable source of MCCDs nframe = 0 for mccd in spool: if server_or_local: # Handle the waiting game ... give_up, try_again, total_time = spooler.hang_about( mccd, twait, tmax, total_time) if give_up: print("rtplot stopped") break elif try_again: continue # Trim the frames: ULTRACAM windowed data has bad columns # and rows on the sides of windows closest to the readout # which can badly affect reduction. This option strips # them. if trim: hcam.ccd.trim_ultracam(mccd, ncol, nrow) if nframe == 0: # crop the bias on the first frame only bias = bias.crop(mccd) # bias subtraction mccd -= bias # indicate progress tstamp = Time(mccd.head["TIMSTAMP"], format="isot", precision=3) print(f"{mccd.head.get('NFRAME',nframe+1)}, utc= {tstamp.iso} " + f"({'ok' if mccd.head.get('GOODTIME', True) else 'nok'})") # add in data if it is in range xd = mccd[ccdref].mean() if xd > lowers[ccdref] and xd < uppers[ccdref]: for cnam in ccds: yd = mccd[cnam].mean() if yd > lowers[cnam] and yd < uppers[cnam]: if cnam in xdata: xdata[cnam].append(xd) ydata[cnam].append(yd) else: xdata[cnam] = [xd] ydata[cnam] = [yd] if last and nframe == last: break # update the frame number nframe += 1 print() invalid = True for cnam in ccds: if cnam in xdata: xdata[cnam] = np.array(xdata[cnam]) ydata[cnam] = np.array(ydata[cnam]) print( f'{cnam}-vs-{ccdref}: found {len(xdata[cnam])} valid points ranging from' + f' {xdata[cnam].min():.1f} to {xdata[cnam].max():.1f} (CCD {ccdref}), ' + f' {ydata[cnam].min():.1f} to {ydata[cnam].max():.1f} (CCD {cnam})' ) invalid = False if invalid: raise hcam.HipercamError(f"No valid points found at all") print() if plot: fig, axes = plt.subplots(len(ccds), 1, sharex=True) for cnam, ax in zip(ccds, axes): if cnam in xdata: xds = xdata[cnam] ratios = ydata[cnam] / xds mratio = ratios.mean() ax.plot(xds, ratios, '.g') ax.plot([xds.min(), xds.max()], [mratio, mratio], 'r--') ax.set_ylabel(f'Ratio CCD {cnam} / CCD {ccdref}') if cnam == ccds[-1]: ax.set_xlabel(f'CCD = {ccdref} counts/pixel') print(f'{cnam}-vs-{ccdref} = {mratio:.4f}') plt.show() else: for cnam in ccds: if cnam in xdata: ratios = ydata[cnam] / xdata[cnam] mratio = ratios.mean() print(f'{cnam}-vs-{ccdref} = {mratio:.4f}')
def genred(args=None): """``genred apfile rfile comment bias flat dark linear inst [ncpu extendx ccd location smoothfwhm method beta betamax fwhm fwhmmin searchwidth thresh hminref hminnrf rfac rmin rmax sinner souter scale psfgfac psfwidth psfpostweak]`` Generates a reduce file as needed by |reduce| or |psf_reduce|. You give it the name of an aperture file and a few other parameters and it will write out a reduce file which you can then refine by hand. A few simplifying assumptions are made, e.g. that the target is called '1'; see below for more. This script effectively defines the format of reduce files. The script attempts above all to generate a self-consistent reduce file. e.g. if there are no apertures in CCD 5, it does not attempt to plot any corresponding light curves. To avoid excessive prompting, |genred| has many hidden parameters. The very first time you use it on a run, specify ``prompt`` on the command line to see all of these. They are chosen to be the parameters most likely to vary with telescope or conditions; many others are left at default values and require editing to change. If you find yourself repeatedly editing a parameter, let me know and I will add it to this routine. Parameters: apfile : string the input aperture file created using |setaper| (default extension .ape). This will be read for the targets. The main target will be assumed to have been called '1', the main comparison '2'. If there is a '3' it will be plotted relative to '2'; all others will be ignored for plotting purposes. Target '2' will be used to define the position and transmission plots for one CCD only [user definable]. Target '1' will be used for the seeing plot unless it is linked when target '2' will be used instead. rfile : string the output reduce file created using |setaper|. This will be read for the targets. The main target will be assumed to have been called '1', the main comparison '2'. If there is a '3' it will be plotted relative to '2'; all others will be ignored for plotting purposes. comment : string comment to add near the top of the reduce file. Obvious things to say are the target name and the name of the observer for instance. bias : string Name of bias frame; 'none' to ignore. flat : string Name of flat field frame; 'none' to ignore. dark : string Name of dark frame; 'none' to ignore. linear : string light curve plot linear (else magnitudes) inst : string the instrument (needed to set nonlinearity and saturation levels for warning purposes. Possible options listed. ncpu : int [hidden] some increase in speed can be obtained by running the reduction in parallel. This parameter is the number of CPUs to use. The parellisation is over the CCDs, and there is no point having ncpu greater than the number of CCDs, but it should ideally equal the number of CCDs. There is no point using this for single CCD reduction. ngroup : int [hidden, if ncpu > 1] to reduce parallelisation overheads, this parameter means that ngroup frames are read before being split up for the parallisation step is applied. extendx : float [hidden] how many minutes to extend light curve plot by at a time ccd : string [hidden] label of the (single) CCD used for the position plot location : string [hidden] whether to reposition apertures or leave them fixed. toffset : int [hidden] integer offset to subtract from the MJD times in order to reduce round-off. i.e. rather 55678.123456789, if you specified toffset=55600, you would reduce the round-off error by a factor ~1000. With typical MJDs, round off is around the 0.5 microsecond level. If you want to improve on that, set an appropriate offset value. It is set by default to 0, i.e. it will be 0 unless you explicitly set it otherwise. The value used is recorded in the log file. toffset must be >= 0. smoothfwhm : float [hidden] FWHM to use for smoothing during initial search [binned pixels] fft : bool [hidden] whether or not to use FFTs when carrying out the convolution operation used in the initial search. No effect on results, but could be faster for large values of smoothfwhm. method : string profile fitting method. 'g' for gaussian, 'm' for moffat beta : float [hidden] default Moffat exponent to use to start fitting betamax : float [hidden] maximum Moffat exponent to pass on to subsequent fits. Prevents it wandering to silly values which can happen. fwhm : float [hidden] the default FWHM to use when fitting [unbinned pixels]. fwhmmin : float [hidden] the default FWHM to use when fitting [unbinned pixels]. searchwidth : int [hidden] half width in (binned) pixels for the target searches fitwidth : int [hidden] half width in (binned) pixels for the profile fits maxshift : float [hidden] maximum shift of non-reference targets relative to the initial positions derived from the reference targets. The reference targets should give good initial positions, thus this can be set to quite a small value to improve robustness against duff positions, caused e.g. by cosmic rays or field crowding in periods of bad seeing. Use linked apertures for more difficult cases still. thresh : float [hidden] RMS rejection threshold for profile fits. hminref : float [hidden] minimum peak height for a fit to a reference aperture to be accepted. This applies to the peak height in the *smoothed* image used during the initial search as well as the peak height after profile fitting. It is the check on the smoothed image that is more consistent since the seeing-limited peak height can be highly variable, and the more stringent since smoothing reduces the peak height by seeing**2/(seeing**2+smoothfwhm**2) where 'seeing' is the FWHM of the seeing in binned pixels. If smoothfwhm is chosen to be larger than seeing is likely to be, this makes the peak heights more consistent so that the thresholding is better behaved. But, it means you should use a value lower than you might guess from the images. A peak of height `h` in a smoothed image will contain about (seeing**2+smoothfwhm**2)*h counts in total (but fewer if binned). hminnrf : float [hidden] minimum peak height for a fit to a non-reference aperture to be accepted. This applies above all to the smoothed peak height as for hminref. alpha : float [hidden] amount by which non-reference apertures are corrected relative to their expected positions when reference apertures are enabled. The idea is that the positions of non-reference relative to reference apertures should vary little, so rather than simply re-positioning independently every frame, one might want to build in a bit of past history. This can be done by setting alpha small. If alpha = 1, then that simply returns to fully independent positioning for each frame. diff : float [hidden] maximum difference in the shifts of reference apertures, when more than one has been defined on a CCD. If exceeded, no extraction is performed. This is to guard against perturbations of the reference apertures by meteors, cosmic rays and the like. [unbinned pixels] rfac : float [hidden] target aperture radius relative to the FWHM for 'variable' aperture photometry. Usual values 1.5 to 2.5. rmin : float [hidden] minimum target aperture radius [unbinned pixels] rmax : float [hidden] maximum target aperture radius [unbinned pixels] sinner : float [hidden] inner sky aperture radius [unbinned pixels] souter : float [hidden] outer sky aperture radius [unbinned pixels] readout : float | string [hidden] readout noise, RMS ADU. Can either be a single value or an hcm file. gain : float [hidden] gain, electrons per ADU. Can either be a single value or an hcm file. scale : float [hidden] image scale in arcsec/pixel psfgfac : float [hidden] multiple of FWHM used to group objects together for PSF fitting psfwidth : int [hidden] half-width of box used to extract data around objects for PSF fitting psfpostweak : string [hidden] During PSF fitting, either hold positions at aperture location ('fixed'), or fit as part of PSF model ('variable') """ # print(my_version) command, args = utils.script_args(args) with Cline('HIPERCAM_ENV', '.hipercam', command, args) as cl: # register parameters cl.register('apfile', Cline.LOCAL, Cline.PROMPT) cl.register('rfile', Cline.GLOBAL, Cline.PROMPT) cl.register('comment', Cline.LOCAL, Cline.PROMPT) cl.register('bias', Cline.LOCAL, Cline.PROMPT) cl.register('flat', Cline.LOCAL, Cline.PROMPT) cl.register('dark', Cline.LOCAL, Cline.PROMPT) cl.register('linear', Cline.LOCAL, Cline.PROMPT) cl.register('inst', Cline.LOCAL, Cline.HIDE) cl.register('ncpu', Cline.LOCAL, Cline.HIDE) cl.register('ngroup', Cline.LOCAL, Cline.HIDE) cl.register('extendx', Cline.LOCAL, Cline.HIDE) cl.register('ccd', Cline.LOCAL, Cline.HIDE) cl.register('location', Cline.LOCAL, Cline.HIDE) cl.register('toffset', Cline.LOCAL, Cline.HIDE) cl.register('smoothfwhm', Cline.LOCAL, Cline.HIDE) cl.register('fft', Cline.LOCAL, Cline.HIDE) cl.register('beta', Cline.LOCAL, Cline.HIDE) cl.register('betamax', Cline.LOCAL, Cline.HIDE) cl.register('fwhm', Cline.LOCAL, Cline.HIDE) cl.register('method', Cline.LOCAL, Cline.HIDE) cl.register('fwhmmin', Cline.LOCAL, Cline.HIDE) cl.register('searchwidth', Cline.LOCAL, Cline.HIDE) cl.register('fitwidth', Cline.LOCAL, Cline.HIDE) cl.register('maxshift', Cline.LOCAL, Cline.HIDE) cl.register('thresh', Cline.LOCAL, Cline.HIDE) cl.register('hminref', Cline.LOCAL, Cline.HIDE) cl.register('hminnrf', Cline.LOCAL, Cline.HIDE) cl.register('alpha', Cline.LOCAL, Cline.HIDE) cl.register('diff', Cline.LOCAL, Cline.HIDE) cl.register('rfac', Cline.LOCAL, Cline.HIDE) cl.register('rmin', Cline.LOCAL, Cline.HIDE) cl.register('rmax', Cline.LOCAL, Cline.HIDE) cl.register('sinner', Cline.LOCAL, Cline.HIDE) cl.register('souter', Cline.LOCAL, Cline.HIDE) cl.register('readout', Cline.LOCAL, Cline.HIDE) cl.register('gain', Cline.LOCAL, Cline.HIDE) cl.register('scale', Cline.LOCAL, Cline.HIDE) cl.register('psfgfac', Cline.LOCAL, Cline.HIDE) cl.register('psfwidth', Cline.LOCAL, Cline.HIDE) cl.register('psfpostweak', Cline.LOCAL, Cline.HIDE) # get inputs # the aperture file apfile = cl.get_value('apfile', 'aperture input file', cline.Fname('aper.ape', hcam.APER)) # Read the aperture file aper = hcam.MccdAper.read(apfile) # the reduce file rfile = cl.get_value( 'rfile', 'reduce output file', cline.Fname('reduce.red', hcam.RED, cline.Fname.NEW)) # user comment string comment = cl.get_value( 'comment', 'user comment to add [<cr>' ' for newline to get multilines]', '') if comment == '': comment = '# There was no user comment\n' else: comment_lines = comment.split('<cr>') comment = '# User comment:\n#\n# ' + '\n# '.join(comment_lines) # ones you might quite often want to change # bias frame bias = cl.get_value('bias', "bias frame ['none' to ignore]", cline.Fname('bias', hcam.HCAM), ignore='none') bias = '' if bias is None else bias # flat field frame flat = cl.get_value('flat', "flat field frame ['none' to ignore]", cline.Fname('flat', hcam.HCAM), ignore='none') flat = '' if flat is None else flat # dark frame dark = cl.get_value('dark', "dark field frame ['none' to ignore]", cline.Fname('dark', hcam.HCAM), ignore='none') dark = '' if dark is None else dark inst = cl.get_value( 'inst', 'instrument (hipercam, ultracam, ultraspec)', 'hipercam', lvals=['hipercam', 'ultracam', 'ultraspec', 'ignore']) if inst == 'hipercam': warn_levels = """# Warning levels for instrument = HiPERCAM warn = 1 50000 64000 warn = 2 50000 64000 warn = 3 50000 64000 warn = 4 50000 64000 warn = 5 50000 64000 """ maxcpu = 5 elif inst == 'ultracam': warn_levels = """# Warning levels for instrument = ULTRACAM warn = 1 28000 64000 warn = 2 28000 64000 warn = 3 50000 64000 """ maxcpu = 3 elif inst == 'ultraspec': warn_levels = """# Warning levels for instrument = ULTRASPEC warn = 1 60000 64000 """ maxcpu = 1 else: warn_levels = """# No warning levels have been set!!""" maxcpu = 20 if maxcpu > 1: ncpu = cl.get_value('ncpu', 'number of CPUs to use (<= number of CCDs)', 1, 1, maxcpu) else: ncpu = 1 if ncpu > 1: ngroup = cl.get_value( 'ngroup', 'number of frames per group to reduce parallelisation overheads', 1, 1) else: ngroup = 1 linear = cl.get_value('linear', 'linear light curve plot?', False) linear = 'yes' if linear else 'no' # hidden parameters extendx = cl.get_value('extendx', 'how much to extend light curve plot [mins]', 10., 0.01) ccd = cl.get_value('ccd', 'label for the CCD used for the position plot', '2') if ccd not in aper: raise hcam.HipercamError( 'CCD {:s} not found in aperture file {:s}'.format(ccd, apfile)) # hidden parameters location = cl.get_value('location', 'aperture location, f(ixed) or v(ariable)', 'v', lvals=['f', 'v']) location = 'variable' if location == 'v' else 'fixed' comm_seeing = '#' if location == 'fixed' else '' comm_position = '#' if location == 'fixed' else '' cl.set_default('toffset', 0) toffset = cl.get_value( 'toffset', 'offset to subtract from the MJD times (to reduce round-off) [days]', 0, 0) smooth_fwhm = cl.get_value('smoothfwhm', 'search smoothing FWHM [binned pixels]', 6., 3.) smooth_fft = cl.get_value('fft', 'use FFT when smoothing', False) profile_type = cl.get_value( 'method', 'profile fit method, g(aussian) or m(offat)', 'g', lvals=['g', 'm']) profile_type = 'gaussian' if profile_type == 'g' else 'moffat' beta = cl.get_value('beta', 'starting value of beta', 4., 3.) beta_max = cl.get_value( 'betamax', 'maximum value of beta to start consecutive fits', 20., beta) fwhm = cl.get_value('fwhm', 'starting FWHM, unbinned pixels', 5., 1.5) fwhm_min = cl.get_value('fwhmmin', 'minimum FWHM, unbinned pixels', 1.5, 0.) search_half_width = cl.get_value( 'searchwidth', 'half width for initial searches, unbinned pixels', 11, 3) fit_half_width = cl.get_value( 'fitwidth', 'half width for profile fits, unbinned pixels', 21, 5) fit_max_shift = cl.get_value( 'maxshift', 'maximum non-reference shift, unbinned pixels', 15., 0.) thresh = cl.get_value('thresh', 'RMS rejection threshold for fits (sigma)', 5., 2.) height_min_ref = cl.get_value( 'hminref', 'minimum peak height for a fit to reference aperture [counts]', 10., 0.) height_min_nrf = cl.get_value( 'hminnrf', 'minimum peak height for a fit to non-reference aperture [counts]', 5., 0.) fit_alpha = cl.get_value( 'alpha', 'non-reference aperture fractional shift parameter (range: (0,1])', 1., 1.e-5, 1.) fit_diff = cl.get_value( 'diff', 'maximum differential reference aperture shift', 2., 1.e-5) rfac = cl.get_value('rfac', 'target aperture scale factor', 1.8, 1.0) rmin = cl.get_value( 'rmin', 'minimum target aperture radius [unbinned pixels]', 6., 1.) rmax = cl.get_value( 'rmax', 'maximum target aperture radius [unbinned pixels]', 30., rmin) sinner = cl.get_value('sinner', 'inner sky aperture radius [unbinned pixels]', 30., rmax) souter = cl.get_value('souter', 'outer sky aperture radius [unbinned pixels]', 50., sinner + 1) readout = cl.get_value('readout', 'readout noise, RMS ADU (float or file name)', '4.5') gain = cl.get_value('gain', 'gain, electrons/ADU, (float or file name)', '1.1') scale = cl.get_value('scale', 'image scale [arcsec/unbinned pixel]', 0.3, 0.001) psfgfac = cl.get_value( 'psfgfac', 'multiple of FWHM used to group objects for PSF fitting', 3, 0.1) psfwidth = cl.get_value('psfwidth', 'half width for PSF fits, unbinned pixels', 15, 5) psfpostweak = cl.get_value( 'psfpostweak', 'locations during PSF fitting stage, f(ixed) or v(ariable)', 'f', lvals=['f', 'v']) psfpostweak = 'variable' if psfpostweak == 'v' else 'fixed' ################################################################ # # all the inputs have now been obtained. Get on with doing stuff # Generate the extraction lines. Note that the aperture location # parameter maps into the same names as the aperture re-size # parameter extraction = '' for cnam in aper: extraction += ('{:s} = {:s} normal' ' {:.2f} {:.1f} {:.1f}' ' 2.5 {:.1f} {:.1f}' ' 3.0 {:.1f} {:.1f}\n').format(cnam, location, rfac, rmin, rmax, sinner, sinner, souter, souter) # standard colours for CCDs if inst == 'hipercam': CCD_COLS = { '1': 'purple', '2': 'green', '3': 'orange', '4': 'red', '5': 'darkred' } elif inst == 'ultracam': CCD_COLS = { '1': 'red', '2': 'green', '3': 'blue', } elif inst == 'ultraspec': CCD_COLS = { '1': 'green', } # Generate the light curve plot lines light_plot = '' no_light = True for cnam in aper: ccdaper = aper[cnam] if '1' in ccdaper and '2' in ccdaper: light_plot += ( 'plot = {:s} 1 2 0 1 {:10s} ! ' ' # ccd, targ, comp, off, fac, dcol, ecol\n').format( cnam, CCD_COLS[cnam]) no_light = False elif '1' in ccdaper and '2' not in ccdaper: light_plot += ( 'plot = {:s} 1 ! 0 1 {:10s} ! ' ' # ccd, targ, comp, off, fac, dcol, ecol\n').format( cnam, CCD_COLS[cnam]) no_light = False if '2' in ccdaper and '3' in ccdaper: light_plot += ( 'plot = {:s} 3 2 0 1 {:10s} ! ' ' # ccd, targ, domp, off, fac, dcol, ecol\n').format( cnam, CCD_COLS[cnam]) no_light = False if no_light: raise hcam.HipercamError( 'Found no targets for light curve plots in any CCD; cannot make light curve plot' ) # Generate the position plot lines position_plot = '' ccdaper = aper[ccd] no_position = True if '2' in ccdaper: position_plot += ('{:s}plot = {:s} 2 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( comm_position, ccd, CCD_COLS[ccd]) no_position = False elif '3' in ccdaper: position_plot += ('{:s}plot = {:s} 3 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( comm_position, ccd, CCD_COLS[ccd]) no_position = False elif '1' in ccdaper: position_plot += ('{:s}plot = {:s} 1 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( comm_position, ccd, CCD_COLS[ccd]) no_position = False if no_position: raise hcam.HipercamError( 'Targets 1, 2 and 3 not found in ' 'CCD = {:s}; cannot make position plot'.format(ccd)) # Generate the transmission plot lines transmission_plot = '' no_transmission = True for cnam in aper: ccdaper = aper[cnam] if '2' in ccdaper: transmission_plot += ('plot = {:s} 2 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( cnam, CCD_COLS[cnam]) no_transmission = False elif '3' in ccdaper: transmission_plot += ('plot = {:s} 3 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( cnam, CCD_COLS[cnam]) no_transmission = False elif '1' in ccdaper: transmission_plot += ('plot = {:s} 1 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( cnam, CCD_COLS[cnam]) no_transmission = False if no_transmission: raise hcam.HipercamError('Targets 1, 2 and 3 not found in any CCDs;' ' cannot make transmission plot') # Generate the seeing plot lines seeing_plot = '' no_seeing = True for cnam in aper: ccdaper = aper[cnam] if '1' in ccdaper and not ccdaper['1'].linked: seeing_plot += ('{:s}plot = {:s} 1 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( comm_seeing, cnam, CCD_COLS[cnam]) no_seeing = False elif '2' in ccdaper and not ccdaper['2'].linked: seeing_plot += ('{:s}plot = {:s} 2 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( comm_seeing, cnam, CCD_COLS[cnam]) no_seeing = False elif '3' in ccdaper and not ccdaper['3'].linked: seeing_plot += ('{:s}plot = {:s} 3 {:10s} ! ' ' # ccd, targ, dcol, ecol\n').format( comm_seeing, cnam, CCD_COLS[cnam]) no_seeing = False if no_seeing: raise hcam.HipercamError( 'Targets 1, 2 and 3 not found in any CCD' ' (or they are linked); cannot make seeing plot') # monitor targets (whole lot by default) targs = set() for cnam in aper: ccdaper = aper[cnam] for targ in ccdaper: targs.add(targ) monitor = '' for targ in sorted(targs): monitor += ('{:s} = NO_EXTRACTION TARGET_SATURATED TARGET_AT_EDGE' ' TARGET_NONLINEAR NO_SKY NO_FWHM NO_DATA SKY_AT_EDGE\n' ).format(targ) # time stamp tstamp = strftime("%d %b %Y %H:%M:%S (UTC)", gmtime()) # finally write out the reduce file. with open(rfile, 'w') as fout: # write out file fout.write( TEMPLATE.format(version=hcam.REDUCE_FILE_VERSION, apfile=apfile, fwhm=fwhm, fwhm_min=fwhm_min, extraction=extraction, bias=bias, flat=flat, dark=dark, smooth_fwhm=smooth_fwhm, linear=linear, light_plot=light_plot, position_plot=position_plot, transmission_plot=transmission_plot, seeing_plot=seeing_plot, monitor=monitor, comment=comment, tstamp=tstamp, hipercam_version=hipercam_version, location=location, comm_seeing=comm_seeing, extendx=extendx, comm_position=comm_position, scale=scale, warn_levels=warn_levels, ncpu=ncpu, ngroup=ngroup, search_half_width=search_half_width, fit_half_width=fit_half_width, profile_type=profile_type, height_min_ref=height_min_ref, height_min_nrf=height_min_nrf, beta=beta, beta_max=beta_max, thresh=thresh, readout=readout, gain=gain, fit_max_shift=fit_max_shift, fit_alpha=fit_alpha, fit_diff=fit_diff, psfgfac=psfgfac, psfpostweak=psfpostweak, psfwidth=psfwidth, toffset=toffset, smooth_fft='yes' if smooth_fft else 'no')) print('Reduce file written to {:s}'.format(rfile))
def makefield(args=None): """Script to generate an artificial star field which is saved to a disk file, a first step in generating fake data. The resulting files are needed by makedata if you want to add in artificial star fields. If the name supplied corresponds to an existing file, an attempt will be made to read it in first and then add to it. In this way a complex field can be generated. The targets are distributed at random, with random peak heights based on constant luminosity objects distributed throughout 3D space, and random angles uniform over the input range. All targets otherwise have the same shape thus multiple calls are needed to generate a field of objects of multiple shapes. Ellipsoidal "Moffat" functions [1/(1+r^2)^beta] are used. Arguments:: fname : (string) file to add to. Will be created if it does not exist. [input, optional / output] ntarg : (int) The number of targets to add to the field. x1 : (float) The left-hand limit of the field [unbinned pixels] x2 : (float) The right-hand limit of the field [unbinned pixels] y1 : (float) The lowest limit of the field [unbinned pixels] y2 : (float) The highest limit of the field [unbinned pixels] h1 : (float) Minimum peak height [counts per unbinned pixel] h2 : (float) Maximum peak height [counts per unbinned pixel] angle1 : (float) Lower limit on axis 1, anti-clockwise from X-axis [degrees] angle2 : (float) Upper limit on axis 1, anti-clockwise from X-axis [degrees] fwhm1 : (float) FWHM along axis 1 [unbinned pixels] fwhm2 : (float) FWHM along axis 2 [unbinned pixels] beta : (float) Moffat function exponent """ command, args = utils.script_args(args) # create Cline object with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("fname", Cline.LOCAL, Cline.PROMPT) cl.register("ntarg", Cline.LOCAL, Cline.PROMPT) cl.register("x1", Cline.LOCAL, Cline.PROMPT) cl.register("x2", Cline.LOCAL, Cline.PROMPT) cl.register("y1", Cline.LOCAL, Cline.PROMPT) cl.register("y2", Cline.LOCAL, Cline.PROMPT) cl.register("h1", Cline.LOCAL, Cline.PROMPT) cl.register("h2", Cline.LOCAL, Cline.PROMPT) cl.register("angle1", Cline.LOCAL, Cline.PROMPT) cl.register("angle2", Cline.LOCAL, Cline.PROMPT) cl.register("fwhm1", Cline.LOCAL, Cline.PROMPT) cl.register("fwhm2", Cline.LOCAL, Cline.PROMPT) cl.register("beta", Cline.LOCAL, Cline.PROMPT) cl.register("fmin", Cline.LOCAL, Cline.PROMPT) # get inputs fname = cl.get_value( "fname", "file to save field to", cline.Fname("field", hcam.FIELD, cline.Fname.NEW), ) if os.path.exists(fname): # Initialise the field from a file field = hcam.Field.rjson(fname) print(">> Loaded a field of", len(field), "objects from", fname) else: # Create an empty field field = hcam.Field() print(">> Created an empty field.") ntarg = cl.get_value("ntarg", "number of targets", 100, 1) x1 = cl.get_value("x1", "left-hand limit of field", -10.0) x2 = cl.get_value("x2", "right-hand limit of field", 2000.0, x1) y1 = cl.get_value("y1", "lower limit of field", -10.0) y2 = cl.get_value("y2", "upper limit of field", 1000.0, y1) h1 = cl.get_value("h1", "lower peak height limit", 0.1, 1.0e-6) h2 = cl.get_value("h2", "upper peak height limit", 1000.0, h1) angle1 = cl.get_value("angle1", "lower limit of axis 1 angle", 0.0, -360.0, 360.0) angle2 = cl.get_value("angle2", "angle of major axis", 0.0, angle1, 360.0) fwhm1 = cl.get_value("fwhm1", "FWHM along axis 1", 4.0, 1.0e-6) fwhm2 = cl.get_value("fwhm2", "FWHM along axis 2", 4.0, 1.0e-6) beta = cl.get_value("beta", "Moffat exponent", 4.0, 1.0) fmin = cl.get_value("fmin", "minimum flux level (counts/pix)", 0.001, 0.0) # add targets field.add_random(ntarg, x1, x2, y1, y2, h1, h2, angle1, angle2, fwhm1, fwhm2, beta, fmin) # save result field.wjson(fname) print(">> Saved a field of", len(field), "objects to", fname)
def mstats(args=None): """``mstats [source] run [temp] (ndigit) first last [twait tmax] bias [dtype]`` This downloads a sequence of images from a raw data file and writes out stats (min, max, mean, median, rms) for each window to a file Parameters: source : string [hidden] Data source, four options: | 'hs' : HiPERCAM server | 'hl' : local HiPERCAM FITS file | 'us' : ULTRACAM server | 'ul' : local ULTRACAM .xml/.dat files run : string run name to access first : int First frame to access last : int Last frame to access, 0 for the lot twait : float [hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [hidden] maximum time to wait between attempts to find a new exposure, seconds. bias : string Name of bias frame to subtract, 'none' to ignore. format : string output format for numbers. e.g. the default '9.3f' might give 12345.678 (9 characters, 3 digits after d.p.) outfile : string file for output (extension .stats) """ command, args = utils.script_args(args) # get inputs with Cline('HIPERCAM_ENV', '.hipercam', command, args) as cl: # register parameters cl.register('source', Cline.GLOBAL, Cline.HIDE) cl.register('run', Cline.GLOBAL, Cline.PROMPT) cl.register('first', Cline.LOCAL, Cline.PROMPT) cl.register('last', Cline.LOCAL, Cline.PROMPT) cl.register('twait', Cline.LOCAL, Cline.HIDE) cl.register('tmax', Cline.LOCAL, Cline.HIDE) cl.register('bias', Cline.GLOBAL, Cline.PROMPT) cl.register('format', Cline.LOCAL, Cline.HIDE) cl.register('outfile', Cline.LOCAL, Cline.PROMPT) # get inputs source = cl.get_value('source', 'data source [hs, hl, us, ul]', 'hl', lvals=('hs', 'hl', 'us', 'ul')) # OK, more inputs resource = cl.get_value('run', 'run name', 'run005') first = cl.get_value('first', 'first frame to grab', 1, 0) last = cl.get_value('last', 'last frame to grab', 0) if last < first and last != 0: sys.stderr.write('last must be >= first or 0') sys.exit(1) twait = cl.get_value('twait', 'time to wait for a new frame [secs]', 1., 0.) tmax = cl.get_value('tmax', 'maximum time to wait for a new frame [secs]', 10., 0.) bias = cl.get_value('bias', "bias frame ['none' to ignore]", cline.Fname('bias', hcam.HCAM), ignore='none') cl.set_default('format', '9.3f') form = cl.get_value('format', 'output format for stats', '9.3f') outfile = cl.get_value('outfile', 'output file for stats', cline.Fname('stats', '.stats', cline.Fname.NEW)) # Now the actual work. # strip off extensions if resource.endswith(hcam.HRAW): resource = resource[:resource.find(hcam.HRAW)] # initialisations total_time = 0 # time waiting for new frame nframe = first root = os.path.basename(resource) bframe = None with spooler.data_source(source, resource, first) as spool: with open(outfile, 'w') as stats: stats.write("""# # This file was generated by mstats running on file {run} # # The columns are: # # nframe ccd window minimum maximum mean median rms # # where ccd and window are string labels, nframe is the frame # number an an integer, while the rest are floats. # """.format(run=resource)) for mccd in spool: # Handle the waiting game ... give_up, try_again, total_time = spooler.hang_about( mccd, twait, tmax, total_time) if give_up: print('mstats stopped') break elif try_again: continue if bias is not None: # read bias after first frame so we can # chop the format if bframe is None: # read the bias frame bframe = hcam.MCCD.read(bias) # reformat bframe = bframe.crop(mccd) mccd -= bframe for cnam, ccd in mccd.items(): for wnam, wind in ccd.items(): stats.write( ('{1:5d} {2:5s} {3:5s} {4:{0:s}} {5:{0:s}}' ' {6:{0:s}} {7:{0:s}} {8:{0:s}}\n').format( form, nframe, cnam, wnam, wind.min(), wind.max(), wind.mean(), wind.median(), wind.std())) # flush the output stats.flush() # progress info print('Written stats of frame {:d} to {:s}'.format( nframe, outfile)) # update the frame number nframe += 1 if last and nframe > last: break
def aligntool(args=None): """ Measures alignment of HiperCAM CCDs w.r.t a reference image of the blue CCD. Arguments:: ref : (string) name of an MCCD file to use as reference , as produced by e.g. 'grab' rccd : (string) CCD to use as reference thresh : (float) threshold for object detection, in multiples of background RMS source : (string) [hidden] Data source, five options:: 'hs' : HiPERCAM server 'hl' : local HiPERCAM FITS file 'us' : ULTRACAM server 'ul' : local ULTRACAM .xml/.dat files 'hf' : list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. device : (string) [hidden] Plot device. PGPLOT is used so this should be a PGPLOT-style name, e.g. '/xs', '1/xs' etc. At the moment only ones ending /xs are supported. width : (float) [hidden] plot width (inches). Set = 0 to let the program choose. height : (float) [hidden] plot height (inches). Set = 0 to let the program choose. BOTH width AND height must be non-zero to have any effect run : (string) [if source == 's' or 'l'] run number to access, e.g. 'run034' flist : (string) [if source == 'f'] name of file list first : (int) [if source='s' or 'l'] exposure number to start from. 1 = first frame; set = 0 to always try to get the most recent frame (if it has changed) twait : (float) [if source == 's'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : (float) [if source == 's'; hidden] maximum time to wait between attempts to find a new exposure, seconds. pause : (float) [hidden] seconds to pause between frames (defaults to 0) ccd : (string) CCD to measure aligment of. bias : (string) Name of bias frame to subtract, 'none' to ignore. msub : (bool) subtract the median from each window before scaling for the image display or not. This happens after any bias subtraction. iset : (string) [single character] determines how the intensities are determined. There are three options: 'a' for automatic simply scales from the minimum to the maximum value found on a per CCD basis. 'd' for direct just takes two numbers from the user. 'p' for percentile dtermines levels based upon percentiles determined from the entire CCD on a per CCD basis. ilo : (float) [if iset='d'] lower intensity level ihi : (float) [if iset='d'] upper intensity level plo : (float) [if iset='p'] lower percentile level phi : (float) [if iset='p'] upper percentile level small_spots : (bool) [hidden] use the small spots for alignment instead of the big spots """ command, args = utils.script_args(args) with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("ref", Cline.LOCAL, Cline.PROMPT) cl.register("thresh", Cline.LOCAL, Cline.PROMPT) cl.register("small_spots", Cline.LOCAL, Cline.HIDE) cl.register("source", Cline.LOCAL, Cline.HIDE) cl.register("device", Cline.LOCAL, Cline.HIDE) cl.register("width", Cline.LOCAL, Cline.HIDE) cl.register("height", Cline.LOCAL, Cline.HIDE) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("rccd", Cline.LOCAL, Cline.PROMPT) cl.register("pause", Cline.LOCAL, Cline.HIDE) cl.register("bias", Cline.GLOBAL, Cline.PROMPT) cl.register("msub", Cline.GLOBAL, Cline.PROMPT) cl.register("iset", Cline.GLOBAL, Cline.PROMPT) cl.register("ilo", Cline.GLOBAL, Cline.PROMPT) cl.register("ihi", Cline.GLOBAL, Cline.PROMPT) cl.register("plo", Cline.GLOBAL, Cline.PROMPT) cl.register("phi", Cline.LOCAL, Cline.PROMPT) cl.register("xlo", Cline.GLOBAL, Cline.PROMPT) cl.register("xhi", Cline.GLOBAL, Cline.PROMPT) cl.register("ylo", Cline.GLOBAL, Cline.PROMPT) cl.register("yhi", Cline.GLOBAL, Cline.PROMPT) # get inputs reference = cl.get_value("ref", "reference frame to align to", cline.Fname("hcam", hcam.HCAM)) ref_mccd = hcam.MCCD.read(reference) thresh = cl.get_value("thresh", "detection threshold (sigma)", 5.0) small_spots = cl.get_value("small_spots", "use small spots for alignment", False) # get inputs source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", "hl", lvals=("hs", "hl", "us", "ul", "hf"), ) # set some flags server_or_local = source.endswith("s") or source.endswith("l") inst = source[0] # plot device stuff device = cl.get_value("device", "plot device", "1/xs") width = cl.get_value("width", "plot width (inches)", 0.0) height = cl.get_value("height", "plot height (inches)", 0.0) if server_or_local: resource = cl.get_value("run", "run name", "run005") first = cl.get_value("first", "first frame to plot", 1, 1) twait = cl.get_value("twait", "time to wait for a new frame [secs]", 1.0, 0.0) tmax = cl.get_value("tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0) else: # set inst = 'h' as only lists of HiPERCAM files are supported inst = "h" resource = cl.get_value("flist", "file list", cline.Fname("files.lis", hcam.LIST)) first = 1 flist = source.endswith("f") server = source.endswith("s") if inst == "u": instrument = "ULTRA" elif inst == "h": instrument = "HIPER" # get the labels and maximum dimensions and padding factors ccdinf = spooler.get_ccd_pars(source, resource) if len(ccdinf) > 1: ccdnam = cl.get_value("ccd", "CCD to plot alignment of", "1") rccdnam = cl.get_value("rccd", "CCD to use as reference", "5") cl.set_default("pause", 0.0) pause = cl.get_value("pause", "time delay to add between frame plots [secs]", 0.0, 0.0) # bias frame (if any) bias = cl.get_value( "bias", "bias frame ['none' to ignore]", cline.Fname("bias", hcam.HCAM), ignore="none", ) if bias is not None: # read the bias frame bias = hcam.MCCD.read(bias) msub = cl.get_value("msub", "subtract median from each window?", True) iset = cl.get_value( "iset", "set intensity a(utomatically)," " d(irectly) or with p(ercentiles)?", "a", lvals=["a", "A", "d", "D", "p", "P"], ) iset = iset.lower() plo, phi = 5, 95 ilo, ihi = 0, 1000 if iset == "d": ilo = cl.get_value("ilo", "lower intensity limit", 0.0) ihi = cl.get_value("ihi", "upper intensity limit", 1000.0) elif iset == "p": plo = cl.get_value("plo", "lower intensity limit percentile", 5.0, 0.0, 100.0) phi = cl.get_value("phi", "upper intensity limit percentile", 95.0, 0.0, 100.0) # region to plot nxtot, nytot, nxpad, nypad = ccdinf[ccdnam] xmin, xmax = float(-nxpad), float(nxtot + nxpad + 1) ymin, ymax = float(-nypad), float(nytot + nypad + 1) xlo = cl.get_value("xlo", "left-hand X value", xmin, xmin, xmax) xhi = cl.get_value("xhi", "right-hand X value", xmax, xmin, xmax) ylo = cl.get_value("ylo", "lower Y value", ymin, ymin, ymax) yhi = cl.get_value("yhi", "upper Y value", ymax, ymin, ymax) # arguments defined, let's do something! # open image plot device imdev = hcam.pgp.Device(device) if width > 0 and height > 0: pgpap(width, height / width) pgsubp(1, 2) for i in range(2): pgsci(hcam.pgp.Params["axis.ci"]) pgsch(hcam.pgp.Params["axis.number.ch"]) pgenv(xlo, xhi, ylo, yhi, 1, 0) pglab("X", "Y", ("reference", "data")[i]) total_time = 0 # time waiting for new frame with spooler.data_source(source, resource, first) as spool: # 'spool' is an iterable source of MCCDs for n, mccd in enumerate(spool): if server_or_local: # Handle the waiting game ... give_up, try_again, total_time = spooler.hang_about( mccd, twait, tmax, total_time) if give_up: print("alignment tool stopped") break elif try_again: continue # indicate progress print( "Frame {:d}, time = {:s}; ".format(mccd.head["NFRAME"], mccd.head["TIMSTAMP"]), end="", ) if n == 0 and bias is not None: # crop the bias on the first frame only bias = bias.crop(mccd) ccd = mccd[ccdnam] ref_ccd = ref_mccd[rccdnam] if bias is not None: ccd -= bias[ccdnam] if msub: for wind in ccd.values(): wind -= wind.median() for wind in ref_ccd.values(): wind -= wind.median() # plot images message = "" for i in range(2): this_ccd = (ref_ccd, ccd)[i] name = ("ref: ", "data: ")[i] pgpanl(1, i + 1) vmin, vmax = hcam.pgp.pCcd(this_ccd, iset, plo, phi, ilo, ihi) message += " {:s}: {:.2f} to {:.2f}".format(name, vmin, vmax) print(message) # time for measurement of spots! try: xpos, ypos, fwhm, flux = measureSpots(mccd, ccdnam, thresh) lx, ly, lf, lp, bx, by, bf, bp = separateBigSmallSpots( xpos, ypos, fwhm, flux) if not small_spots: # use big spots x, y, f, flux = bx, by, bf, bp else: x, y, f, flux = lx, ly, lf, lp except Exception as err: print("Failed to find spots in frame: ", end=" ") print(str(err)) continue # also measure spots in reference image try: xpos, ypos, fwhm, rflux = measureSpots(ref_mccd, rccdnam, thresh) lx, ly, lf, lp, bx, by, bf, bp = separateBigSmallSpots( xpos, ypos, fwhm, rflux) if not small_spots: # use big spots xref, yref, fref = bx, by, bf else: xref, yref, fref = lx, ly, lf except Exception as err: print("Failed to find spots in reference: ", end=" ") print(str(err)) continue plt.clf() displayFWHM(x, y, xref, yref, f, flux, fix_fwhm_scale=False) plt.draw() plt.pause(0.05) plt.ioff() plt.show()
def hplot(args=None): """``hplot input [device] ccd nx msub ([cmap]) hsbox iset (ilo ihi | plo phi) xlo xhi ylo yhi [width height]`` Plots a multi-CCD image. Can use PGPLOT or matplotlib. The matplotlib version is slightly clunky in its choice of the viewing area but has some features that could be useful, in particular, the interactive plot (device='/mpl') allows one to pan and zoom and to compare the same part of multiple CCDs easily. Parameters: input : string name of MCCD file device : string [hidden] Plot device name. Uses characters after a final trailing '/' to identify the type in PGPLOT style. Thus: | /xs : PGPLOT xserver interactive plot | 1/xs : PGPLOT xserver interactive plot called '1' | plot.ps/cps : PGPLOT colour postscript called 'plot.ps' | plot.ps/vps : PGPLOT B&W portrait oriented plot | /mpl : matplotlib interactive plot | plot.pdf/mpl : matplotlib PDF plot ccd : string CCD(s) to plot, '0' for all. If not '0' then '1', '2' or even '3 4' are possible inputs (without the quotes). '3 4' will plot CCD '3' and CCD '4'. If you want to plot more than one CCD, then you will be prompted for the number of panels in the X direction. This parameter will not be prompted if there is only one CCD in the file. nx : int number of panels across to display, prompted if more than one CCD is to be plotted. msub : bool True/False to subtract median from each window before scaling cmap : str [if matplotlib; hidden] The colour map to use. "Greys" is the usual, but there are many others. Typing an incorrect one will give a list. "none" for matplotlib default. hsbox : int [if device = '/mpl'; hidden] half-width in binned pixels of stats box as offset from central pixel hsbox = 1 gives a 3x3 box; hsbox = 2 gives 5x5 etc. iset : string [single character] determines how the intensities are determined. There are three options: 'a' for automatic simply scales from the minimum to the maximum value found on a per CCD basis. 'd' for direct just takes two numbers from the user. 'p' for percentile dtermines levels based upon percentiles determined from the entire CCD on a per CCD bais. ilo : float [if iset=='d'] lower intensity level ihi : float [if iset=='d'] upper intensity level plo : float [if iset=='p'] lower percentile level phi : float [if iset=='p'] upper percentile level xlo : float left X-limit, for PGPLOT plots. Applies to matplotlib plots to restrict region used to compute percentile limits. This is useful in case where bias strips otherwise distort the plot limits (e.g. ultraspec full frame images) xhi : float right X-limit. See comments for xlo as well. ylo : float bottom Y-limit. See comments for xlo as well. yhi : float top Y-limit. See comments for xlo as well. width : float [hidden] plot width (inches). Set = 0 to let the program choose. height : float [hidden] plot height (inches). Set = 0 to let the program choose. BOTH width AND height must be non-zero to have any effect """ global fig, mccd, caxes, hsbox command, args = utils.script_args(args) # get input section with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("input", Cline.LOCAL, Cline.PROMPT) cl.register("device", Cline.LOCAL, Cline.HIDE) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("nx", Cline.LOCAL, Cline.PROMPT) cl.register("msub", Cline.GLOBAL, Cline.PROMPT) cl.register("cmap", Cline.LOCAL, Cline.HIDE) cl.register("hsbox", Cline.GLOBAL, Cline.HIDE) cl.register("iset", Cline.GLOBAL, Cline.PROMPT) cl.register("ilo", Cline.GLOBAL, Cline.PROMPT) cl.register("ihi", Cline.GLOBAL, Cline.PROMPT) cl.register("plo", Cline.GLOBAL, Cline.PROMPT) cl.register("phi", Cline.GLOBAL, Cline.PROMPT) cl.register("xlo", Cline.GLOBAL, Cline.PROMPT) cl.register("xhi", Cline.GLOBAL, Cline.PROMPT) cl.register("ylo", Cline.GLOBAL, Cline.PROMPT) cl.register("yhi", Cline.GLOBAL, Cline.PROMPT) cl.register("width", Cline.LOCAL, Cline.HIDE) cl.register("height", Cline.LOCAL, Cline.HIDE) # get inputs frame = cl.get_value("input", "frame to plot", cline.Fname("hcam", hcam.HCAM)) mccd = hcam.MCCD.read(frame) device = cl.get_value("device", "plot device name", "/mpl") # set type of plot (PGPLOT or matplotlib) and the name of the file # if any in the case of matplotlib fslash = device.rfind("/") if fslash > -1: if device[fslash + 1:] == "mpl": ptype = "MPL" hard = device[:fslash].strip() else: ptype = "PGP" else: raise ValueError( "Could not identify plot type from device = {:s}".format( device)) # define the panel grid nxdef = cl.get_default("nx", 3) max_ccd = len(mccd) if max_ccd > 1: ccd = cl.get_value("ccd", "CCD(s) to plot [0 for all]", "0") if ccd == "0": ccds = list(mccd.keys()) else: ccds = ccd.split() if len(ccds) > 1: nxdef = min(len(ccds), nxdef) cl.set_default("nx", nxdef) nx = cl.get_value("nx", "number of panels in X", 3, 1) else: nx = 1 else: ccds = list(mccd.keys()) nx = 1 # define the display intensities msub = cl.get_value("msub", "subtract median from each window?", True) if ptype == "MPL": cmap = cl.get_value("cmap", "colour map to use ['none' for mpl default]", "Greys") cmap = None if cmap == "none" else cmap if ptype == "MPL" and hard == "": hsbox = cl.get_value("hsbox", "half-width of stats box (binned pixels)", 2, 1) iset = cl.get_value( "iset", "set intensity a(utomatically), d(irectly) or with p(ercentiles)?", "a", lvals=["a", "A", "d", "D", "p", "P"], ) iset = iset.lower() plo, phi = 5, 95 ilo, ihi = 0, 1000 if iset == "d": ilo = cl.get_value("ilo", "lower intensity limit", 0.0) ihi = cl.get_value("ihi", "upper intensity limit", 1000.0) elif iset == "p": plo = cl.get_value("plo", "lower intensity limit percentile", 5.0, 0.0, 100.0) phi = cl.get_value("phi", "upper intensity limit percentile", 95.0, 0.0, 100.0) # region to plot for i, cnam in enumerate(ccds): ccd = mccd[cnam] nxtot, nytot, nxpad, nypad = ccd.nxtot, ccd.nytot, ccd.nxpad, ccd.nypad if i == 0: xmin, xmax = float(-nxpad), float(nxtot + nxpad + 1) ymin, ymax = float(-nypad), float(nytot + nypad + 1) else: xmin = min(xmin, float(-nxpad)) xmax = max(xmax, float(nxtot + nxpad + 1)) ymin = min(ymin, float(-nypad)) ymax = max(ymax, float(nytot + nypad + 1)) xlo = cl.get_value("xlo", "left-hand X value", xmin, xmin, xmax, enforce=False) xhi = cl.get_value("xhi", "right-hand X value", xmax, xmin, xmax, enforce=False) ylo = cl.get_value("ylo", "lower Y value", ymin, ymin, ymax, enforce=False) yhi = cl.get_value("yhi", "upper Y value", ymax, ymin, ymax, enforce=False) width = cl.get_value("width", "plot width (inches)", 0.0) height = cl.get_value("height", "plot height (inches)", 0.0) # all inputs obtained, plot if ptype == "MPL": if width > 0 and height > 0: fig = plt.figure(figsize=(width, height)) else: fig = plt.figure() mpl.rcParams["xtick.labelsize"] = hcam.mpl.Params["axis.number.fs"] mpl.rcParams["ytick.labelsize"] = hcam.mpl.Params["axis.number.fs"] nccd = len(ccds) ny = nccd // nx if nccd % nx == 0 else nccd // nx + 1 ax = None caxes = {} for n, cnam in enumerate(ccds): if ax is None: axes = ax = fig.add_subplot(ny, nx, n + 1) axes.set_aspect("equal", adjustable="box") else: axes = fig.add_subplot(ny, nx, n + 1, sharex=ax, sharey=ax) axes.set_aspect("equal") # store the CCD associated with these axes for the cursor callback caxes[axes] = cnam axes.set_xlim(xlo, xhi) axes.set_ylim(ylo, yhi) if msub: # subtract median from each window for wind in mccd[cnam].values(): wind -= wind.median() vmin, vmax, _ = hcam.mpl.pCcd(axes, mccd[cnam], iset, plo, phi, ilo, ihi, "CCD {:s}".format(cnam), xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi, cmap=cmap) print("CCD =", cnam, "plot range =", vmin, "to", vmax) try: plt.tight_layout() except: pass if hard == "": # add in the callback fig.canvas.mpl_connect("button_press_event", buttonPressEvent) print("\nClick points in windows for stats in a {:d}x{:d} box". format(2 * hsbox + 1, 2 * hsbox + 1)) plt.subplots_adjust(wspace=0.1, hspace=0.1) plt.show() else: plt.savefig(hard, bbox_inches="tight", pad_inches=0) elif ptype == "PGP": # open the plot dev = hcam.pgp.Device(device) if width > 0 and height > 0: pgpap(width, height / width) nccd = len(ccds) ny = nccd // nx if nccd % nx == 0 else nccd // nx + 1 # set up panels and axes pgsubp(nx, ny) for cnam in ccds: pgsci(hcam.pgp.Params["axis.ci"]) pgsch(hcam.pgp.Params["axis.number.ch"]) pgenv(xlo, xhi, ylo, yhi, 1, 0) vmin, vmax = hcam.pgp.pCcd(mccd[cnam], iset, plo, phi, ilo, ihi, "CCD {:s}".format(cnam)) print("CCD =", cnam, "plot range =", vmin, "to", vmax)
def flagcloud(args=None): """``flagcloud hlog aper1 aper2 ccd delta output`` Interactive flagging of cloud-affected or otherwise bad points in a |hiper| log file. You either mark a range of times as cloudy, or individual points as junk. If you mark a time range, then *all* apertures of *all* CCDs will be flagged with the bitmask value CLOUDS. Individual points will be flagged as JUNK. Note that nothing is done to the data apart from changing the bitmask flags, so it is then up to you to test for these later on. It is also possible to flag individual points as CLOUDS but these will not propagate across CCDs so it is probably not advisable to do this. Junk points are marked red, cloudy points orange. OK aperture 1 points are plotted green, aperture 2 blue, their ratio black. What is meant by 'junk' as opposed to 'cloud' is really down to the user. I tend to reserve junk for one-off points affected by bad cosmic rays and satellites, but in general it is probably sensible to think of junk as points you never want to see again versus clouds meaning data that you might want to mask or use down the line or perhaps just grey out in plots. Some genuine "cloudy" data will be so bad that it will be better flagged as junk however. You can flag the same point as both "cloud" and "junk", but "junk" is the stronger condition. Bitmasks propagate when data are combined so a point flagged junk in aperture 2 but not aperture 1 will be flagged junk in the ratio of 1 divided by 2. You can also recover points in this routine; doing so will clear both their junk and/or cloud status. At the moment this is only possible on a point-by-point basis. Interaction is via the cursor and hitting specific keys. Common options are lower case; less common ones upper case. Hitting the X on the plot will abort without saving the results. 'q' to quite saves the results. 'h' will give some help on the options. Parameters: hlog : str ASCII log file, as produced by |reduce|. aper1 : str the name of first aperture to look at aper2 : str the name of second aperture to look at. The ratio aper1 / aper2 will be plotted along with the two separately, scaled by their maximum, all in the same panel. ccd : str CCD(s) to plot, '0' for all. If not '0' then '1', '2', or even '1 2 3' are possible inputs (without the quotes). Note the space separation when multiple CCDs are specified. If you want to plot more than one CCD, then you will get multiple panels in the Y direction, but their X-axes are kept in lock step when panning or zooming. delta : float separation to use to space the plots in a given panel, each of which is normalised to 1. A value of 1 is recommended because then the second aperture should end with a typical level of 0, and any dips below this show the extent of the cloud. e.g. -0.9 would suggest an approximate 90% loss of flux due to cloud, ignoring extinction. output : str name of modified version of the Hlog for output. Can overwrite the original if you dare. """ command, args = utils.script_args(args) # get input section with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("hlog", Cline.LOCAL, Cline.PROMPT) cl.register("aper1", Cline.LOCAL, Cline.PROMPT) cl.register("aper2", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("delta", Cline.LOCAL, Cline.PROMPT) cl.register("output", Cline.LOCAL, Cline.PROMPT) # get inputs hlog = cl.get_value("hlog", "hipercam ASCII log file", cline.Fname("hcam", hcam.LOG)) hlog = hcam.hlog.Hlog.read(hlog) cnams = sorted(hlog.keys()) apnames = set() for cnam in cnams: apnames |= set(hlog.apnames[cnam]) apnames = sorted(apnames) aper1 = cl.get_value("aper1", "first aperture", apnames[0], lvals=apnames) if len(apnames) > 1: aper2 = cl.get_value("aper2", "second aperture", apnames[-1], lvals=apnames) else: aper2 = None max_ccd = len(hlog) if max_ccd > 1: ccd = cl.get_value("ccd", "CCD(s) to plot [0 for all]", "0") if ccd == "0": ccds = cnams else: ccds = ccd.split() if set(ccds) <= set(cnams): print( f' selected CCDs ({ccds}) not amongst those in the reduce log ({cnams})' ) return else: ccds = cnams delta = cl.get_value("delta", "vertical separation between plots", 1, 0.) output = cl.get_value( "output", "name for output log file", cline.Fname("run", hcam.LOG, cline.Fname.NEW), ) # Inputs obtained. print() # re-configure keyboard shortcuts to avoid otherwise confusing behaviour # quit_all does not seem to be universal, hence the try/except try: mpl.rcParams["keymap.back"] = "" mpl.rcParams["keymap.forward"] = "" mpl.rcParams["keymap.fullscreen"] = "" mpl.rcParams["keymap.grid"] = "" mpl.rcParams["keymap.home"] = "" mpl.rcParams["keymap.pan"] = "" mpl.rcParams["keymap.quit"] = "" mpl.rcParams["keymap.save"] = "" mpl.rcParams["keymap.pan"] = "" mpl.rcParams["keymap.save"] = "" mpl.rcParams["keymap.xscale"] = "" mpl.rcParams["keymap.yscale"] = "" mpl.rcParams["keymap.zoom"] = "" except KeyError: pass cnams, anams, plots = {}, {}, {} ax = None fig, axs = plt.subplots(len(ccds), 1, sharex=True) if len(ccds) == 1: axs = [axs] # get the navigation toolbar. Go straight into pan mode where we # want to stay. toolbar = fig.canvas.manager.toolbar if backend != "TkAgg": toolbar.pan() ny = len(ccds) T0 = None for cnam, ax in zip(ccds, axs): # prep data a1 = hlog.tseries(cnam, aper1) a1.normalise() if T0 is None: T0 = a1.t[0] if aper2: a2 = hlog.tseries(cnam, aper2) a2.normalise() rat = a1 / a2 a2.t -= T0 rat.t -= T0 rat += delta a2 -= delta rat.mplot(ax, COL_RAT, bitmask=hcam.JUNK | hcam.CLOUDS) rat.mplot(ax, COL_CLOUD, bitmask=hcam.CLOUDS, flagged=True) rat.mplot(ax, COL_JUNK, bitmask=hcam.JUNK, flagged=True) a2.mplot(ax, COL_AP2, bitmask=hcam.JUNK | hcam.CLOUDS) a2.mplot(ax, COL_CLOUD, bitmask=hcam.CLOUDS, flagged=True) a2.mplot(ax, COL_JUNK, bitmask=hcam.JUNK, flagged=True) a1.t -= T0 a1.mplot(ax, COL_AP1, bitmask=hcam.JUNK | hcam.CLOUDS) a1.mplot(ax, COL_CLOUD, bitmask=hcam.CLOUDS, flagged=True) a1.mplot(ax, COL_JUNK, bitmask=hcam.JUNK, flagged=True) # store the plots needed to identify which point has been selected # along with names of apertures needed to flag points plots[cnam] = { "aper1": aper1, "a1": a1, "aper2": aper2, "a2": a2 if aper2 is not None else None, "rat": rat if aper2 is not None else None } # keep track of the CCD associated with each axes cnams[ax] = cnam # and the axes associated with each CCD anams[cnam] = ax ax.set_ylabel("CCD {:s}".format(cnam)) ax.set_xlabel(f'Time [MJD - {T0}]') # create the picker picker = PickPoints(fig, hlog, cnams, anams, plots, T0, output) try: plt.tight_layout() except: pass PickPoints.action_prompt(False) # squeeze space a bit plt.subplots_adjust(hspace=0.1) # finally show stuff .... plt.xlabel("Time [MJD - {:.7f}]".format(T0)) plt.show()
def flagcloud(args=None): """``flagcloud hlog aper1 aper2 ccd output`` Interactive flagging of cloud-affected or bad points in a |hipercam| log file. Parameters: hlog : string ASCII log file. aper1 : string the name of first aperture to look at aper2 : string the name of second aperture to look at. The ratio aper1 / aper2 will be plotted along with the two separately, scaled by their maximum, all in the same panel. ccd : string CCD(s) to plot, '0' for all. If not '0' then '1', '2' or even '3 4' are possible inputs (without the quotes). '3 4' will plot CCD '3' and CCD '4'. If you want to plot more than one CCD, then you will be prompted for the number of panels in the X direction. output : string name of modified version of the Hlog for output. """ command, args = utils.script_args(args) # get input section with Cline('HIPERCAM_ENV', '.hipercam', command, args) as cl: # register parameters cl.register('hlog', Cline.LOCAL, Cline.PROMPT) cl.register('aper1', Cline.LOCAL, Cline.PROMPT) cl.register('aper2', Cline.LOCAL, Cline.PROMPT) cl.register('ccd', Cline.LOCAL, Cline.PROMPT) cl.register('output', Cline.LOCAL, Cline.PROMPT) # get inputs hlog = cl.get_value( 'hlog', 'hipercam ASCII log file', cline.Fname('hcam', hcam.LOG) ) hlog = hcam.hlog.Hlog.read(hlog) aper1 = cl.get_value('aper1', 'first aperture', '2') aper2 = cl.get_value('aper2', 'second aperture', '3') max_ccd = len(hlog) if max_ccd > 1: ccd = cl.get_value('ccd', 'CCD(s) to plot [0 for all]', '0') if ccd == '0': ccds = list(hlog.keys()) else: ccds = ccd.split() else: ccds = list(hlog.keys()) output = cl.get_value( 'output', 'name for output log file', cline.Fname('run', hcam.LOG, cline.Fname.NEW) ) # Inputs obtained. # re-configure keyboard shortcuts to avoid otherwise confusing behaviour # quit_all does not seem to be universal, hence the try/except try: mpl.rcParams['keymap.back'] = '' mpl.rcParams['keymap.forward'] = '' mpl.rcParams['keymap.fullscreen'] = '' mpl.rcParams['keymap.grid'] = '' mpl.rcParams['keymap.home'] = '' mpl.rcParams['keymap.pan'] = '' mpl.rcParams['keymap.quit'] = '' mpl.rcParams['keymap.save'] = '' mpl.rcParams['keymap.pan'] = '' mpl.rcParams['keymap.save'] = '' mpl.rcParams['keymap.xscale'] = '' mpl.rcParams['keymap.yscale'] = '' mpl.rcParams['keymap.zoom'] = '' except KeyError: pass cnams, anams = {}, {} plots = {} ax = None fig = plt.figure() ny = len(ccds) T0 = None for n, cnam in enumerate(ccds): if ax is None: axes = ax = fig.add_subplot(ny, 1, n+1) else: axes = fig.add_subplot(ny, 1, n+1, sharex=ax) # prep data a1 = hlog.tseries(cnam,aper1) a2 = hlog.tseries(cnam,aper1) rat = (a1/a2).normalise() a1 /= np.percentile(a1.y,99) a2 /= np.percentile(a2.y,99) if T0 is None: T0 = a1.t[0] a1.t -= T0 a2.t -= T0 rat.t -= T0 # three vector plots (rat+0.1).mplot(plt,'k') a1.mplot(plt,'g') (a2-0.1).mplot(plt,'b') # store the plots needed to identify which point has been selected plots[cnam] = {'a1' : a1, 'a2' : a2-0.1, 'rat' : rat+0.1} # keep track of the CCD associated with each axes cnams[axes] = cnam # and the axes associated with each CCD anams[cnam] = axes plt.ylabel('CCD {:s}'.format(cnam)) # create the picker picker = PickPoints(fig, hlog, cnams, anams, plots, output) try: plt.tight_layout() except: pass PickPoints.action_prompt(False) # squeeze space a bit plt.subplots_adjust(hspace=0.1) # finally show stuff .... plt.xlabel('Time [MJD - {:.7f}]'.format(T0)) plt.show()
def hlog2fits(args=None): """``hlog2fits log [origin dir]`` Converts a |hiper| ASCII log into a FITS file. As well as a modest reduction in file size (~40%, the ASCII logs are written relatively efficiently), the resulting file is faster to read than the ASCII log so this may be useful for very large log files [test of 78,000 frame file: 12.9 seconds to read the ASCII file, 1.9 to read the FITS version]. The FITS log is also much easier to understand than the ASCII files, but they don't have all the header information, so are not a replacement. At the moment no significant header information is transferred beyond the CCD names. Each CCD appears as a single binary table, starting at the second HDU (or HDU 1 if you number them 0,1,2 ..). This can be read using :meth:`hipercam.hlog.Hlog.from_fits`. Parameters: log : str name of the log file (should end .log). The output FITS file will have the same root name but end .fits. The routine will abort if there is a pre-existing file of the same name. origin : str [hidden] 'h' or 'u' depending upon whether the log file was created with the hipercam or old ultracam pipeline. Defaults to 'h'. dir : str [hidden] directory for the output; defaults to the present working directory NB Because of the danger of over-writing raw data (also ends .fits), this routine will not over-write pre-existing files. You should delete clashing files if you really want to proceed. """ command, args = utils.script_args(args) # get input section with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("log", Cline.LOCAL, Cline.PROMPT) cl.register("origin", Cline.LOCAL, Cline.HIDE) cl.register("dir", Cline.LOCAL, Cline.HIDE) # get inputs log = cl.get_value( "log", 'name of log file from "reduce" to convert to FITS', cline.Fname("red", hcam.LOG), ) cl.set_default('origin', 'h') origin = cl.get_value("origin", "h(ipercam) or u(ltracam) pipeline?", "h", lvals=["h", "u"]) cl.set_default('dir', '.') dir = cl.get_value( "dir", "directory for output", ".", ) root = os.path.splitext(os.path.basename(log))[0] oname = os.path.join(dir, root + ".fits") if os.path.exists(oname): raise hcam.HipercamError(f"A file called {oname} already exists and" " will not be over-written; aborting") # Read in the ASCII log if origin == "h": hlg = hcam.hlog.Hlog.rascii(log) elif origin == "u": hlg = hcam.hlog.Hlog.fulog(log) print(f"Loaded ASCII log = {log}") # Generate HDU list # First the primary HDU (no data) phdr = fits.Header() phdr["LOGFILE"] = (os.path.basename(log), "Original log file") phdu = fits.PrimaryHDU(header=phdr) hdul = [ phdu, ] # Now a BinTable for each CCD for cnam in sorted(hlg): hdr = fits.Header() hdr["CCDNAME"] = (cnam, "CCD name") hdul.append(fits.BinTableHDU(hlg[cnam], header=hdr, name=f"CCD {cnam}")) hdul = fits.HDUList(hdul) # finally write to disk print(f"Writing to disk in file = {oname}") hdul.writeto(oname) print(f"Converted {log} to {oname}")
def rupdate(args=None): """``rupdate rfile`` As changes are made to 'reduce', old reduce files can become obsolete. This script tries to bring old reduce files up to date by adding in the new options but in a way that should give the old behaviour. The file is modified in place. There are some cases where a full update might not be possible and a further manual edit of the reduce file might be required. Parameters: rfile : string the output reduce file created using |setaper|. This will be read for the targets. The main target will be assumed to have been called '1', the main comparison '2'. If there is a '3' it will be plotted relative to '2'; all others will be ignored for plotting purposes. """ command, args = utils.script_args(args) with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("rfile", Cline.GLOBAL, Cline.PROMPT) # get inputs # the reduce file rfile = cl.get_value( "rfile", "reduce output file", cline.Fname("reduce.red", hcam.RED, cline.Fname.OLD), ) lines = [] fversion = False nversion = 0 with open(rfile) as fin: for line in fin: if line.startswith("version ="): # extract the version number version = line[9:].strip() if version.find(" ") > -1: version = version[:version.find(" ")] if version.find("#") > -1: version = version[:version.find("#")] if version == hcam.REDUCE_FILE_VERSION: print(f"reduce file = {rfile} is up to date") exit(0) elif version == "20181107": # update version number line = ("version = {:s} # must be" " compatible with the" " version in reduce\n").format( hcam.REDUCE_FILE_VERSION) # Insert modified version and extra lines which go into the 'general' # section along with the version number. lines.append(line) lines.append( "\n# Next line was automatically added by rupdate\n") lines.append("skipbadt = no\n\n") # record version in case we need other actions later nversion = 1 elif version == "20200207": # update version number line = ("version = {:s} # must be" " compatible with the" " version in reduce\n").format( hcam.REDUCE_FILE_VERSION) lines.append(line) # record version in case we need other actions later nversion = 2 elif version == "20200223": # update version number line = ("version = {:s} # must be" " compatible with the" " version in reduce\n").format( hcam.REDUCE_FILE_VERSION) lines.append(line) # record version in case we need other actions later nversion = 3 elif version == "20200318": # update version number lines.append( f"version = {hcam.REDUCE_FILE_VERSION} # must be" " compatible with the version in reduce\n") # record version in case we need other actions later nversion = 4 elif version == "20210523": # update version number lines.append( f"version = {hcam.REDUCE_FILE_VERSION} # must be" " compatible with the version in reduce\n") # record version in case we need other actions later nversion = 5 else: print("Version = {:s} not recognised".format(version)) print("Aborting update; nothing changed.") exit(1) if nversion <= 5: lines.append( "\n# Next lines were automatically added by rupdate\n") lines.append( "instrument = UNKNOWN # instrument-telescope\n") lines.append( "scale = UNKNOWN # scale, arcsec/unbinned pixel\n") elif line.startswith("fit_fwhm_min ="): lines.append(line) if nversion <= 3: lines.append( "\n# Next line was automatically added by rupdate\n") lines.append( "fit_fwhm_max = 1000 # Maximum FWHM, unbinned pixels\n" ) elif line.startswith("dark ="): lines.append(line) if nversion <= 4: lines.append( "\n# Next lines were automatically added by rupdate\n") lines.append("fmap = # Fringe map, blank to ignore\n") lines.append( "fpair = # FringePair file, ignored if fringe blank\n") lines.append( "nhalf = 3 # Half-width, ignored if fringe blank\n") lines.append( "rmin = -2 # minimum ratio, ignored if fringe blank\n") lines.append( "rmax = 1 # maximum ratio ignored if fringe blank\n\n") elif line.startswith("scale =") and nversion <= 5: arr = line.split() try: scale = float(arr[2]) except: scale = 'UNKNOWN' else: # Default action is just to store save the line lines.append(line) # Write out modified file with open(rfile, "w") as fout: for line in lines: if line.startswith('scale =') and scale != 'UNKNOWN': fout.write( f'scale = {scale:.3f} # scale, arcsec per unbinned pixel\n' ) else: fout.write(line) # This could be the point at which extra lines are tacked # on to the end of the file. # if nversion == XX etc if nversion == 1 or nversion == 2: fout.write("""# # Next lines were added automatically by 'rupdate' to bring this reduce file # up to date. The changes are designed to produce the same behaviour as the # old version of the reduce file as far as is possible. [focal_mask] demask = no dthresh = 4 """) print(f"Updated reduce file = {rfile}")
def arith(args=None): """ Carries out operations of the form output = input1 [op] input2 where [op] is '+', '-', '*' or '/' referred to by 'add', 'sub', 'mul' and 'div'. Parameters: input1 : string first input hcm file input2 : string second input hcm file ccd : string [hidden, defaults to 'all'] the CCD or CCDs to apply the operation to. 'all' for the whole lot which it returns to by default. Can be several e.g. '2 4' or just one '3' win : string [hidden, defaults to 'all'] the CCD or CCDs to apply the operation to. 'all' for the whole lot which it returns to by default. Can be several e.g. 'E2 G1' or just one 'H1'. If you specify windows in this manner, it is assumed that all the CCDs chosen in the previous input have the named windows; 'all' just applies the operation to all windows regardless. crop : bool [hidden, defaults to False] set True to try to crop input2 to have the same format as input1 (it must enclose it and have compatible binning for this to work). output : string output hcm file name. Can be same as either input1 or input2 in which case the input file will be over-written. """ command, args = utils.script_args(args) # get inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("input1", Cline.LOCAL, Cline.PROMPT) cl.register("input2", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.HIDE) cl.register("win", Cline.LOCAL, Cline.HIDE) cl.register("crop", Cline.LOCAL, Cline.HIDE) cl.register("output", Cline.LOCAL, Cline.PROMPT) prompts = { "add": "add", "sub": "subtract", "mul": "multiply by", "div": "divide by", } infile1 = cl.get_value("input1", "first input file", cline.Fname("hcam", hcam.HCAM)) mccd1 = hcam.MCCD.read(infile1) infile2 = cl.get_value("input2", "second input file", cline.Fname("hcam", hcam.HCAM)) mccd2 = hcam.MCCD.read(infile2) if len(mccd1) > 1: cl.set_default("ccd", "all") ccd = cl.get_value("ccd", "CCD(s) to process", "all") if ccd == "all": ccds = list(mccd1.keys()) else: ccds = ccd.split() else: ccd = "all" ccds = list(mccd1.keys()) tccd = mccd1[ccds[0]] if len(tccd) > 1: cl.set_default("win", "all") win = cl.get_value("win", "window(s) to process", "all") if win == "all": wins = "all" else: wins = win.split() else: win = "all" wins = "all" cl.set_default("crop", False) crop = cl.get_value("crop", "try to crop input2 to the same format as input1", False) if crop: mccd2 = mccd2.crop(mccd1) print("cropped {:s} to match {:s} before operation".format( infile2, infile1)) outfile = cl.get_value("output", "output file", cline.Fname("hcam", hcam.HCAM, cline.Fname.NEW)) # carry out operation if command == "add": # addition for cnam in ccds: ccd1 = mccd1[cnam] ccd2 = mccd2[cnam] if wins == "all": ccd1 += ccd2 else: for wnam in wins: ccd1[wnam] += ccd2[wnam] elif command == "sub": # subtraction for cnam in ccds: ccd1 = mccd1[cnam] ccd2 = mccd2[cnam] if wins == "all": ccd1 -= ccd2 else: for wnam in wins: ccd1[wnam] -= ccd2[wnam] elif command == "mul": # multiplication for cnam in ccds: ccd1 = mccd1[cnam] ccd2 = mccd2[cnam] if wins == "all": ccd1 *= ccd2 else: for wnam in wins: ccd1[wnam] *= ccd2[wnam] elif command == "div": # multiplication for cnam in ccds: ccd1 = mccd1[cnam] ccd2 = mccd2[cnam] if wins == "all": ccd1 /= ccd2 else: for wnam in wins: ccd1[wnam] /= ccd2[wnam] # Add a history line mccd1.head.add_history("{:s} {:s} {:s} {:s} {:s} {:s}".format( command, utils.sub_extension(infile1, hcam.HCAM), utils.sub_extension(infile2, hcam.HCAM), ccd, win, utils.sub_extension(outfile, hcam.HCAM), )) # save result mccd1.write(outfile, True)
def pfolder(args=None): """``pfolder log [device width height] ccd aper`` Folds data from reduce logs on user specified ephemeris (in MJD) and plots the results. Rather specific routine to help with timing tests where the LED switches on on the second and off on the half second. Parameters: log : string name of reduce log file (text file with loads of columns) device : string [hidden, defaults to 'term'] 'term' for interactive plot, file name such as 'plot.pdf' for a hardcopy. width : float [hidden] plot width (inches). Set = 0 to let the program choose. height : float [hidden] plot height (inches). Set = 0 to let the program choose. BOTH width AND height must be non-zero to have any effect ccd : string the CCD to consider, e.g. '1' aper : string the aperture to consider t0 : float zero point of the ephemeris in MJD (i.e. no light travel correction) period : float period to fold on [seconds] """ command, args = utils.script_args(args) # get input section with Cline('HIPERCAM_ENV', '.hipercam', command, args) as cl: # register parameters cl.register('log', Cline.LOCAL, Cline.PROMPT) cl.register('device', Cline.LOCAL, Cline.HIDE) cl.register('width', Cline.LOCAL, Cline.HIDE) cl.register('height', Cline.LOCAL, Cline.HIDE) cl.register('ccd', Cline.LOCAL, Cline.PROMPT) cl.register('aper', Cline.LOCAL, Cline.PROMPT) cl.register('t0', Cline.LOCAL, Cline.PROMPT) cl.register('period', Cline.LOCAL, Cline.PROMPT) # get inputs log = cl.get_value( 'log', 'reduce log file to plot', cline.Fname('hcam', hcam.LOG) ) device = cl.get_value('device', 'plot device name', 'term') width = cl.get_value('width', 'plot width (inches)', 0.) height = cl.get_value('height', 'plot height (inches)', 0.) ccd = cl.get_value('ccd', 'first CCD to plot', '1') aper = cl.get_value('aper', 'first aperture', '1') t0 = cl.get_value('t0', 'zero point of ephemeris [MJD]', 55000.) period = cl.get_value('period', 'period of ephemeris [seconds]', 1., 1e-6) # load the reduce log hlog = hcam.hlog.Hlog.fromLog(log) if width > 0 and height > 0: fig = plt.figure(figsize=(width,height)) else: fig = plt.figure() # load counts data, fold, plot two cycles data = hlog.tseries(ccd, aper, 'counts') data.t = np.mod(86400.*(data.t-t0)/period,1) xlabel = 'Phase [cycles]' ylabel = 'Counts' data.mplot(plt, mask=hcam.ALL_OK) data.t += 1 data.mplot(plt, mask=hcam.ALL_OK) plt.title('{:s}, CCD {:s}, Aperture {:s}'.format(log,ccd,aper)) if device == 'term': plt.show() else: plt.savefig(device)
def plog(args=None): """``plog log [device width height] ccd1 aper1 param1 ccd2 (aper2 param2 scheme) [title]`` Provides quick-look plots of HiPERCAM |reduce| logs. Parameters: log : string name of |reduce| ASCII log file (text file with loads of columns) device : string [hidden, defaults to 'term'] 'term' for interactive plot, file name such as 'plot.pdf' for a hardcopy. width : float [hidden] plot width (inches). Set = 0 to let the program choose. height : float [hidden] plot height (inches). Set = 0 to let the program choose. BOTH width AND height must be non-zero to have any effect ccd1 : str first CCD to consider, e.g. '1' aper1 : str first aperture to consider param1 : str first parameter to consider. Choices are 'x' = X position, 'y' = Y position, 'f' = FWHM, 'b' = Moffat beta, 's' = sky. ccd2 : str second CCD to consider; '!' to ignore. Can be (and typically would be) the same as ccd1. aper2 : str [if ccd2 != '!'] second aperture to consider param2 : str [if ccd2 != '!'] second parameter. See param1 for choices scheme : str [if ccd2 != '!'] how to plot if both apertures are chosen. Choices: | 'd' = difference, i.e. plot 1-2 | 'b' = both plotted on same panel | 'r' = ratio, i.e. 1 / 2, good for relative photom | 's' = scatter plot, 2 vs 1. title : str [hidden] plot title. Defaults to the run number if not specified .. Note:: Points with negative errors are ignored. Be careful with linked apertures where all x, y, FWHM, beta automatically have negative errors since they are not fitted. """ command, args = utils.script_args(args) # get input section with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("log", Cline.LOCAL, Cline.PROMPT) cl.register("device", Cline.LOCAL, Cline.HIDE) cl.register("width", Cline.LOCAL, Cline.HIDE) cl.register("height", Cline.LOCAL, Cline.HIDE) cl.register("ccd1", Cline.LOCAL, Cline.PROMPT) cl.register("aper1", Cline.LOCAL, Cline.PROMPT) cl.register("param1", Cline.LOCAL, Cline.PROMPT) cl.register("ccd2", Cline.LOCAL, Cline.PROMPT) cl.register("aper2", Cline.LOCAL, Cline.PROMPT) cl.register("param2", Cline.LOCAL, Cline.PROMPT) cl.register("scheme", Cline.LOCAL, Cline.PROMPT) cl.register("title", Cline.LOCAL, Cline.HIDE) # get inputs log = cl.get_value("log", "reduce log file to plot", cline.Fname("hcam", hcam.LOG)) device = cl.get_value("device", "plot device name", "term") width = cl.get_value("width", "plot width (inches)", 0.0) height = cl.get_value("height", "plot height (inches)", 0.0) ccd1 = cl.get_value("ccd1", "first CCD to plot", "1") aper1 = cl.get_value("aper1", "first aperture", "1") param1 = cl.get_value( "param1", "first parameter [x, y, f(whm), b(eta), c(ounts), s(ky)]", "c", lvals=("x", "y", "f", "b", "c", "s"), ) lab1 = "[CCD={:s},ap={:s},p={:s}]".format(ccd1, aper1, param1) ccd2 = cl.get_value("ccd2", "second CCD to plot ['!' to ignore]", ccd1) if ccd2 != "!": aper2 = cl.get_value("aper2", "second aperture", "1") param2 = cl.get_value( "param2", "second parameter [x, y, f(whm), b(eta), c(ounts), s(ky)]", "c", lvals=("x", "y", "f", "b", "c", "s"), ) lab2 = "[CCD={:s},ap={:s},p={:s}]".format(ccd2, aper2, param2) # map to names used in reduce log files MAP = { "x": "x", "y": "y", "f": "fwhm", "b": "beta", "c": "counts", "s": "sky" } pname1 = MAP[param1] if ccd2 != "!": pname2 = MAP[param2] scheme = cl.get_value( "scheme", "b(oth), d(ifference), r(atio), s(catter)", "b", lvals=("b", "d", "r", "s"), ) cl.set_default("title", log) title = cl.get_value("title", "plot title", "Plot Title") # load the reduce log hlog = hcam.hlog.Hlog.read(log) if width > 0 and height > 0: fig = plt.figure(figsize=(width, height)) else: fig = plt.figure() dat1 = hlog.tseries(ccd1, aper1, pname1) if ccd2 != "!": dat2 = hlog.tseries(ccd2, aper2, pname2) if scheme == "b": # plots both together dat1.mplot(plt, "b", bitmask=hcam.BAD_TIME) dat2.mplot(plt, "r", bitmask=hcam.BAD_TIME) xlabel = "Time [MJD]" ylabel = "{:s} & {:s}".format(lab1, lab2) elif scheme == "r": # ratio ratio = dat1 / dat2 ratio.mplot(plt, "b", bitmask=hcam.BAD_TIME) xlabel = "Time [MJD]" ylabel = "{:s} / {:s}".format(lab1, lab2) elif scheme == "d": # difference diff = dat1 - dat2 diff.mplot(plt, "b", bitmask=hcam.BAD_TIME) xlabel = "Time [MJD]" ylabel = "{:s} - {:s}".format(lab1, lab2) elif scheme == "s": mask1 = dat1.get_mask(bitmask=hcam.BAD_TIME, invert=True) mask2 = dat1.get_mask(bitmask=hcam.BAD_TIME, invert=True) ok = ~mask1 & ~mask2 plt.errorbar(dat1.y[ok], dat2.y[ok], dat2.ye[ok], dat1.ye[ok], ".", capsize=0) xlabel = lab1 ylabel = lab2 else: # just one dat1.mplot(plt, bitmask=hcam.BAD_TIME) xlabel = "Time [MJD]" ylabel = lab1 plt.xlabel(xlabel) plt.ylabel(ylabel) plt.title(title) if device == "term": plt.show() else: plt.savefig(device)
def ncal(args=None): """``ncal [source] (run first last [twait tmax] | flist) trim ([ncol nrow]) (ccd) bias dark flat xybox read gain grain`` Calibrates noise characteristics of CCDs by plotting estimator of RMS vs signal level from a series of frames. The estimate is the mean of the absolute difference between each pixel and the mean of its 8 near-neighbours. This is very local and fairly robust. Assuming gaussian noise, the RMS is sqrt(4*Pi/9) times this value, and this is what is plotted as the RMS by this routine. `ncal` is best to applied to a series of frames with a large dynamic range, ideally starting from bias-like frames to well exposed sky flats. A long flat-field run going to low levels, or a run into twilight at the end of the night could be good places to start. Parameters: source : str [hidden] Data source, five options: | 'hs' : HiPERCAM server | 'hl' : local HiPERCAM FITS file | 'us' : ULTRACAM server | 'ul' : local ULTRACAM .xml/.dat files | 'hf' : list of HiPERCAM hcm FITS-format files 'hf' is used to look at sets of frames generated by 'grab' or converted from foreign data formats. The standard start-off default for ``source`` can be set using the environment variable HIPERCAM_DEFAULT_SOURCE. e.g. in bash :code:`export HIPERCAM_DEFAULT_SOURCE="us"` would ensure it always started with the ULTRACAM server by default. If unspecified, it defaults to 'hl'. run : str [if source ends 's' or 'l'] run number to access, e.g. 'run034' flist : str [if source ends 'f'] name of file list first : int [if source ends 's' or 'l'] exposure number to start from. 1 = first frame; set = 0 to always try to get the most recent frame (if it has changed). For data from the |hiper| server, a negative number tries to get a frame not quite at the end. i.e. -10 will try to get 10 from the last frame. This is mainly to sidestep a difficult bug with the acquisition system. last : int [if source ends 's' or 'l'] Last frame to access, 0 for the lot twait : float [if source ends 's' or 'l'; hidden] time to wait between attempts to find a new exposure, seconds. tmax : float [if source ends 's' or 'l'; hidden] maximum time to wait between attempts to find a new exposure, seconds. trim : bool [if source starts with 'u'] True to trim columns and/or rows off the edges of windows nearest the readout which can sometimes contain bad data. ncol : int [if trim, hidden] Number of columns to remove (on left of left-hand window, and right of right-hand windows) nrow : int [if trim, hidden] Number of rows to remove (bottom of windows) ccd : str The CCD to analyse. bias : str Name of bias frame to subtract, 'none' to ignore. dark : str Name of dark frame to subtract, 'none' to ignore flat : str Name of flat field to divide by, 'none' to ignore. Should normally only be used in conjunction with a bias, although it does allow you to specify a flat even if you haven't specified a bias. xybox : int the stats will be taken over boxes of xybox-squared pixels to keep the number of points and scatter under control. read : float readout noise, RMS ADU, for overplotting a model gain : float gain, e-/count, for overploting a model grain : float fractional RMS variation due to flat-field variations, if you didn't include a flat field. """ command, args = utils.script_args(args) # get the inputs with Cline("HIPERCAM_ENV", ".hipercam", command, args) as cl: # register parameters cl.register("source", Cline.GLOBAL, Cline.HIDE) cl.register("run", Cline.GLOBAL, Cline.PROMPT) cl.register("first", Cline.LOCAL, Cline.PROMPT) cl.register("last", Cline.LOCAL, Cline.PROMPT) cl.register("trim", Cline.GLOBAL, Cline.PROMPT) cl.register("ncol", Cline.GLOBAL, Cline.HIDE) cl.register("nrow", Cline.GLOBAL, Cline.HIDE) cl.register("twait", Cline.LOCAL, Cline.HIDE) cl.register("tmax", Cline.LOCAL, Cline.HIDE) cl.register("flist", Cline.LOCAL, Cline.PROMPT) cl.register("ccd", Cline.LOCAL, Cline.PROMPT) cl.register("bias", Cline.GLOBAL, Cline.PROMPT) cl.register("dark", Cline.GLOBAL, Cline.PROMPT) cl.register("flat", Cline.GLOBAL, Cline.PROMPT) cl.register("xybox", Cline.LOCAL, Cline.PROMPT) cl.register("read", Cline.LOCAL, Cline.PROMPT) cl.register("gain", Cline.LOCAL, Cline.PROMPT) cl.register("grain", Cline.LOCAL, Cline.PROMPT) # get inputs default_source = os.environ.get('HIPERCAM_DEFAULT_SOURCE', 'hl') source = cl.get_value( "source", "data source [hs, hl, us, ul, hf]", default_source, lvals=("hs", "hl", "us", "ul", "hf"), ) # set some flags server_or_local = source.endswith("s") or source.endswith("l") if server_or_local: resource = cl.get_value("run", "run name", "run005") if source == "hs": first = cl.get_value("first", "first frame to plot", 1) else: first = cl.get_value("first", "first frame to plot", 1, 0) last = cl.get_value("last", "last frame to grab", 0) if last < first and last != 0: sys.stderr.write("last must be >= first or 0") sys.exit(1) twait = cl.get_value("twait", "time to wait for a new frame [secs]", 1.0, 0.0) tmax = cl.get_value("tmax", "maximum time to wait for a new frame [secs]", 10.0, 0.0) else: resource = cl.get_value("flist", "file list", cline.Fname("files.lis", hcam.LIST)) first = 1 trim = cl.get_value("trim", "do you want to trim edges of windows?", True) if trim: ncol = cl.get_value("ncol", "number of columns to trim from windows", 0) nrow = cl.get_value("nrow", "number of rows to trim from windows", 0) else: ncol, nrow = None, None # define the panel grid. first get the labels and maximum dimensions ccdinf = spooler.get_ccd_pars(source, resource) if len(ccdinf) > 1: cnam = cl.get_value("ccd", "CCD to analyse", "1", lvals=list(ccdinf.keys())) else: cnam = ccdinf.keys()[0] # bias frame (if any) bias = cl.get_value( "bias", "bias frame ['none' to ignore]", cline.Fname("bias", hcam.HCAM), ignore="none", ) if bias is not None: # read the bias frame bias = hcam.MCCD.read(bias) fprompt = "flat frame ['none' to ignore]" else: fprompt = "flat frame ['none' is normal choice with no bias]" # dark (if any) dark = cl.get_value("dark", "dark frame to subtract ['none' to ignore]", cline.Fname("dark", hcam.HCAM), ignore="none") if dark is not None: # read the dark frame dark = hcam.MCCD.read(dark) # flat (if any) flat = cl.get_value("flat", fprompt, cline.Fname("flat", hcam.HCAM), ignore="none") if flat is not None: # read the flat frame flat = hcam.MCCD.read(flat) xybox = cl.get_value("xybox", "box size for averaging results [binned pixels]", 11, 1) read = cl.get_value("read", "readout noise, RMS ADU", 4.0, 0.0) gain = cl.get_value("gain", "gain, ADU/e-", 1.0, 0.001) grain = cl.get_value("grain", "flat field graininess", 0.01, 0.) ###################################### # Phew. We finally have all the inputs # Now on with the analysis total_time, nframe = 0, 0 with spooler.data_source(source, resource, first, full=False) as spool: for mccd in spool: if server_or_local: # Handle the waiting game ... some awkward stuff # involving updating on a cycle faster than twait to # make the plots more responsive, if twait is long. give_up, try_again, total_time = spooler.hang_about( mccd, twait, tmax, total_time) if give_up: print("ncal stopped") break elif try_again: continue # Trim the frames: ULTRACAM windowed data has bad columns # and rows on the sides of windows closest to the readout # which can badly affect reduction. This option strips # them. if trim: hcam.ccd.trim_ultracam(mccd, ncol, nrow) # indicate progress tstamp = Time(mccd.head["TIMSTAMP"], format="isot", precision=3) nfrm = mccd.head.get("NFRAME", nframe + 1) print( f'{nfrm}, utc= {tstamp.iso} ({"ok" if mccd.head.get("GOODTIME", True) else "nok"}), ' ) if nframe == 0: # get the bias, dark, flat # into shape first time through if bias is not None: # crop the bias on the first frame only bias = bias.crop(mccd) bexpose = bias.head.get("EXPTIME", 0.0) else: bexpose = 0. if dark is not None: # crop the dark on the first frame only dark = dark.crop(mccd) if flat is not None: # crop the flat on the first frame only flat = flat.crop(mccd) # extract the CCD of interest ccd = mccd[cnam] if ccd.is_data(): # "is_data" indicates genuine data as opposed to junk # that results from nskip > 0. # subtract the bias if bias is not None: ccd -= bias[cnam] # subtract the dark if dark is not None: dexpose = dark.head["EXPTIME"] cexpose = ccd.head["EXPTIME"] scale = (cexpose - bexpose) / dexpose ccd -= scale * dark[cnam] # divide out the flat if flat is not None: ccd /= flat[cnam] # at this point we have the data in the right state to # start processing. for wind in ccd.values(): data = wind.data ny, nx = data.shape if nx - 2 >= xybox and ny - 2 >= xybox: means, stds = procdata(wind.data, xybox) plt.loglog(means, stds, ',b') # update the frame number nframe += 1 if server_or_local and last and nframe > last: break count = 10**np.linspace(0, 5, 200) sigma = np.sqrt(read**2 + count / gain + (grain * count)**2) plt.plot(count, sigma, 'r', lw=2) plt.xlim(1, 100000) plt.ylim(1, 1000) plt.xlabel('Mean count level') plt.ylabel('RMS [counts]') plt.title(f'RMS vs level, CCD {cnam}') plt.show()