Exemple #1
0
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 = [], [], []
Exemple #2
0
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
Exemple #3
0
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')
Exemple #4
0
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()
Exemple #5
0
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.")
Exemple #6
0
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()
Exemple #7
0
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}')
Exemple #8
0
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")
Exemple #9
0
def reduce(args=None):
    """``reduce [source] rfile (run first last (trim [ncol nrow]) twait
    tmax | flist) 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 : 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.

        run : string [if source ends 's' or 'l']
           run number to access, e.g. 'run034'

        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.

        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]
           Number of columns to remove (on left of left-hand window, and right
           of right-hand windows)

        nrow : int [if trim]
           Number of rows to remove (bottom of windows)

        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

    .. 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('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('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('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)
            print('*** reduce aborted')
            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('*** reduce aborted')
                exit(1)

            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.)
            tmx = 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
            last = 0
            trim = False

        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.)

        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.)
                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)

        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 a tzero has been set
        tzset = 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, tzero, 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, tzero,
                                     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 tzset:
                    # This is the first frame  which allows us to make
                    # some checks and initialisations.
                    tzero, 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
                    tzset = 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.)
                else:
                    # no bias subtraction
                    pccd = mccd.copy()
                    bexpose = 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

                # 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, tzero, 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, tzero, lpanel, xpanel, ypanel, tpanel,
                         spanel, tkeep, lbuffer, xbuffer, ybuffer, tbuffer,
                         sbuffer)

            print('reduce finished')
Exemple #10
0
def ftargets(args=None):
    """``ftargets [source device width height] (run first [twait tmax] |
    flist) trim ([ncol nrow]) (ccd (nx)) [pause] thresh fwhm minpix
    output bias flat msub iset (ilo ihi | plo phi) xlo xhi ylo yhi``

    This script carries out the following steps for each of a series
    of images:

      (1) detects the sources,
      (2) identifies isolated targets suited to profile fits,
      (3) fits 2D Moffat profiles to these,
      (4) Saves results to disk.

    The profile fits are carried out because `sep` does not return
    anything that can be used reliably for a FWHM.

    Several parameters depends on the object detection threshold
    retuned by the source detection. This is referred to as
    `threshold`. The source detection is carried out using `sep` which
    runs according to the usual source extractor algorithm of Bertin.

    The script plots the frames, with ellipses at 3*a,3*b indicated in
    red, green boxes indicating the range of pixels identified by
    `sep`, and blue boxes marking the targets selected for FWHM
    fitting (the boxes indicate the fit region).


    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.

        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 : 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.

        pause : float [hidden]
           seconds to pause between frames (defaults to 0)

        thresh : float
           threshold (mutiple of RMS) to use for object detection. Typical
           values 2.5 to 4. The higher it is, the fewer objects will be located,
           but the fewer false detections will be made.

        fwhm : float
           FWHM to use for smoothing during object detection. Should be
           comparable to the seeing.

        minpix : int
           Minimum number of pixels above threshold before convolution to count
           as a detection. Useful in getting rid of cosmics and high dark count
           pixels.

        rmin : float
           Closest distance of any other detected object for an attempt
           to be made to fit the FWHM of an object [unbinned pixels].

        pmin : float
           Minimum peak height for an attempt to be made to fit the
           FWHM of an object. This should be a multiple of the object
           detection threshold (returned by `sep` for each object).

        pmax : float
           Maximum peak height for an attempt to be made to fit the
           FWHM of an object. Use to exclude saturated targets
           [counts]

        emax : float
           Maximum elongation (major/minor axis ratio = a/b), > 1. Use
           to reduce very non-stellar profiles.

        nmax : int
           Maximum number of FWHMs to measure. Will take the brightest first,
           judging by the flux.

        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.

        output: string
           Name of file for storage of results. Will be a fits file, with
           results saved to the HDU 1 as a table.

        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)

    """

    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("nx", Cline.LOCAL, Cline.PROMPT)
        cl.register("pause", Cline.LOCAL, Cline.HIDE)
        cl.register("thresh", Cline.LOCAL, Cline.PROMPT)
        cl.register("fwhm", Cline.LOCAL, Cline.PROMPT)
        cl.register("minpix", Cline.LOCAL, Cline.PROMPT)
        cl.register("rmin", Cline.LOCAL, Cline.PROMPT)
        cl.register("pmin", Cline.LOCAL, Cline.PROMPT)
        cl.register("pmax", Cline.LOCAL, Cline.PROMPT)
        cl.register("emax", Cline.LOCAL, Cline.PROMPT)
        cl.register("nmax", Cline.LOCAL, Cline.PROMPT)
        cl.register("gain", Cline.LOCAL, Cline.PROMPT)
        cl.register("rej", Cline.LOCAL, Cline.PROMPT)
        cl.register("bias", Cline.GLOBAL, Cline.PROMPT)
        cl.register("flat", Cline.GLOBAL, Cline.PROMPT)
        cl.register("output", Cline.LOCAL, 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")

        # 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")
            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)

            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)

        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.0)
        pause = cl.get_value(
            "pause", "time delay to add between" " frame plots [secs]", 0.0, 0.0
        )

        thresh = cl.get_value("thresh", "source detection threshold [RMS]", 3.0)
        fwhm = cl.get_value("fwhm", "FWHM for source detection [binned pixels]", 4.0)
        minpix = cl.get_value("minpix", "minimum number of pixels above threshold", 3)
        rmin = cl.get_value(
            "rmin", "nearest neighbour for FWHM fits [unbinned pixels]", 20.0
        )
        pmin = cl.get_value(
            "pmin", "minimum peak value for profile fits [multiple of threshold]", 5.0
        )
        pmax = cl.get_value(
            "pmax", "maximum peak value for profile fits [counts]", 60000.0
        )
        emax = cl.get_value(
            "emax", "maximum elongation (a/b) for profile fits", 1.2, 1.0
        )
        nmax = cl.get_value("nmax", "maximum number of profile fits", 10, 1)
        gain = cl.get_value("gain", "CCD gain [electrons/ADU]", 1.0, 0.0)
        rej = cl.get_value(
            "rej", "rejection threshold for profile fits [RMS]", 6.0, 2.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]"

        # 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)

        output = cl.get_value(
            "output",
            "output file for results",
            cline.Fname("sources", hcam.SEP, cline.Fname.NEW),
        )

        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.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)

    ################################################################
    #
    # 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

    nhdu = len(ccds) * [0]
    thetas = np.linspace(0, 2 * np.pi, 100)

    # values of various parameters
    fwhm_min, beta, beta_min, beta_max, readout, max_nfev = (
        2.0,
        4.0,
        1.5,
        10.0,
        5.5,
        100,
    )

    # number of failed fits
    nfail = 0

    # open the output file for results
    with fitsio.FITS(output, "rw", clobber=True) as fout:

        # plot images
        with spooler.data_source(source, resource, first, full=False) as spool:

            # 'spool' is an iterable source of MCCDs
            n = 0
            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, tmax, total_time
                    )

                    if give_up:
                        print("ftargets 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} ({:s}), ".format(
                        mccd.head["NFRAME"],
                        tstamp.iso,
                        "ok" if mccd.head.get("GOODTIME", True) else "nok",
                    ),
                    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)

                    # compute maximum length of window name strings
                    lsmax = 0
                    for ccd in mccd.values():
                        for wnam in ccd:
                            lsmax = max(lsmax, len(wnam))

                # display the CCDs chosen
                message = ""
                pgbbuf()
                for nc, cnam in enumerate(ccds):
                    ccd = mccd[cnam]

                    if 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]

                        # Where the fancy stuff happens ...
                        # estimate sky background, look for stars
                        objs, dofwhms = [], []
                        nobj = 0
                        for wnam in ccd:
                            try:
                                # chop window, find objects
                                wind = ccd[wnam].window(xlo, xhi, ylo, yhi)
                                wind.data = wind.data.astype("float")
                                objects, bkg = findStars(wind, thresh, fwhm, True)

                                # subtract background from frame for display
                                # purposes.
                                bkg.subfrom(wind.data)
                                ccd[wnam] = wind

                                # remove targets with too few pixels
                                objects = objects[objects["tnpix"] >= minpix]

                                # run nearest neighbour search on all
                                # objects, but select only a subset
                                # with right count levels for FWHM
                                # measurement, and which are not too
                                # elongated
                                results = isolated(objects["x"], objects["y"], rmin)

                                peaks = objects["peak"]
                                ok = (
                                    (peaks < pmax)
                                    & (peaks > pmin * objects["thresh"])
                                    & (objects["a"] < emax * objects["b"])
                                )
                                dfwhms = np.array(
                                    [
                                        i
                                        for i in range(len(results))
                                        if ok[i] and len(results[i]) == 1
                                    ], dtype=int
                                )

                                # pick the brightest. 'dfwhms' are the
                                # indices of the selected targets for
                                # FWHM measurement
                                if len(dfwhms):
                                    fluxes = objects["flux"][dfwhms]
                                    isort = np.argsort(fluxes)[::-1]
                                    dfwhms = dfwhms[isort[:nmax]]

                                # buffer for storing the FWHMs, including NaNs
                                # for the ones thet are skipped
                                fwhms = np.zeros_like(peaks, dtype=np.float32)
                                betas = np.zeros_like(peaks, dtype=np.float32)
                                nfevs = np.zeros_like(peaks, dtype=np.int32)

                                for i, (x, y, peak, fwhm) in enumerate(
                                        zip(
                                            objects["x"],
                                            objects["y"],
                                            peaks,
                                            objects["fwhm"],
                                        )
                                ):
                                    # fit FWHMs of selected targets

                                    if i in dfwhms:

                                        try:
                                            # extract fit Window
                                            fwind = wind.window(
                                                x - rmin, x + rmin, y - rmin, y + rmin
                                            )

                                            # fit profile
                                            ofwhm = fwhm
                                            obeta = beta
                                            (
                                                (sky, height, x, y, fwhm, beta),
                                                epars,
                                                (
                                                    wfit,
                                                    X,
                                                    Y,
                                                    sigma,
                                                    chisq,
                                                    nok,
                                                    nrej,
                                                    npar,
                                                    nfev,
                                                ),
                                            ) = hcam.fitting.fitMoffat(
                                                fwind,
                                                None,
                                                peak,
                                                x,
                                                y,
                                                fwhm,
                                                2.0,
                                                False,
                                                beta,
                                                beta_max,
                                                False,
                                                readout,
                                                gain,
                                                rej,
                                                1,
                                                max_nfev,
                                            )
                                            fwhms[i] = fwhm
                                            betas[i] = beta
                                            nfevs[i] = nfev

                                            # keep value of beta for next round under control
                                            beta = min(beta_max, max(beta_min, beta))

                                        except hcam.HipercamError as err:
                                            emessages.append(
                                                " >> Targ {:d}: fit failed ***: {!s}".format(
                                                    i, err
                                                )
                                            )
                                            fwhms[i] = np.nan
                                            betas[i] = np.nan
                                            nfevs[i] = 0
                                            nfail += 1
                                    else:
                                        # skip this one
                                        fwhms[i] = np.nan
                                        betas[i] = np.nan
                                        nfevs[i] = 0

                                # tack on frame number & window name
                                frames = (nf + first) * np.ones(
                                    len(objects), dtype=np.int32
                                )
                                wnams = np.array(
                                    len(objects) * [wnam], dtype="U{:d}".format(lsmax)
                                )
                                objects = append_fields(
                                    objects,
                                    ("ffwhm", "beta", "nfev", "nframe", "wnam"),
                                    (fwhms, betas, nfevs, frames, wnams),
                                )

                                # save the objects and the
                                objs.append(objects)
                                dofwhms.append(dfwhms + nobj)
                                print(dfwhms,nobj,dofwhms)
                                nobj += len(objects)

                            except hcam.HipercamError:
                                # window may have no overlap with xlo, xhi
                                # ylo, yhi
                                pass

                        if len(objs):
                            # Plot targets found

                            # concatenate results of all Windows
                            objs = np.concatenate(objs)
                            dofwhms = np.concatenate(dofwhms)

                            # 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,
                            )

                            pgsci(core.CNAMS["red"])
                            As, Bs, Thetas, Xs, Ys = (
                                objs["a"],
                                objs["b"],
                                objs["theta"],
                                objs["x"],
                                objs["y"],
                            )
                            for a, b, theta0, x, y in zip(As, Bs, Thetas, Xs, Ys):
                                xs = x + 3 * a * np.cos(thetas + theta0)
                                ys = y + 3 * b * np.sin(thetas + theta0)
                                pgline(xs, ys)

                            pgsci(core.CNAMS["green"])
                            for xmin, xmax, ymin, ymax in zip(
                                objs["xmin"], objs["xmax"], objs["ymin"], objs["ymax"]
                            ):
                                xs = [xmin, xmax, xmax, xmin, xmin]
                                ys = [ymin, ymin, ymax, ymax, ymin]
                                pgline(xs, ys)

                            pgsci(core.CNAMS["blue"])
                            if len(dofwhms):
                                print(dofwhms)
                                for x, y in zip(objs["x"][dofwhms], objs["y"][dofwhms]):
                                    xs = [x - rmin, x + rmin, x + rmin, x - rmin, x - rmin]
                                    ys = [y - rmin, y - rmin, y + rmin, y + rmin, y - rmin]
                                    pgline(xs, ys)

                            # remove some less useful fields to save a
                            # bit more space prior to saving to disk
                            objs = remove_field_names(
                                objs,
                                (
                                    "x2",
                                    "y2",
                                    "xy",
                                    "cxx",
                                    "cxy",
                                    "cyy",
                                    "hfd",
                                    "cflux",
                                    "cpeak",
                                    "xcpeak",
                                    "ycpeak",
                                    "xmin",
                                    "xmax",
                                    "ymin",
                                    "ymax",
                                ),
                            )

                            # save to disk
                            if nhdu[nc]:
                                fout[nhdu[nc]].append(objs)
                            else:
                                fout.write(objs)
                                nhdu[nc] = nc + 1

                        # 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 pause > 0.0:
                    # pause between frames
                    time.sleep(pause)

                # update the frame number
                n += 1

    print("Number of failed fits =", nfail)
Exemple #11
0
def makemovie(args=None):
    """``makemovie [source] (run first last | flist) trim ([ncol nrow])
    (ccd (nx)) bias flat defect log (targ comp ymin ymax yscales
    yoffset location fraction lpad) cmap width height dstore ndigit fext msub iset
    (ilo ihi | plo phi) xlo xhi ylo yhi [dpi (style (ms lw))]``

    ``makemovie`` is for generating stills to combine into a movie for
    presentations. It can optionally also read a log file from the run
    to display an evolving light curve. There are lots of fiddly
    parameters mostly to do with the plot positioning, so try it out
    on a small number of frames first before going mad.

    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.

        last : int [if source ends 's' or 'l']
           last exposure

        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
           CCD(s) to plot, '0' for all, '1 3' to plot '1' and '3' only, etc. If
           you plot more than one, then a legend is added to any light curve panel
           to distinguish the light curves.

        nx : int [if more than 1 CCD]
           number of panels across to display for the image display.

        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.

        defect : str
           Name of defect file, 'none' to ignore.

        fmap : str
           Name of fringe map (see e.g. `makefringe`), 'none' to ignore.

        fpair : str [if fringe is not 'none']
           Name of fringe pair file (see e.g. `setfringe`). Required if
           a fringe map has been specified.

        nhalf : int [if fringe 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 fringe 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 fringe 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.

        log : str
           Name of reduce log file for light curve plot, 'none' to ignore

        targ : str [if log defined]
           Target aperture

        comp : str [if log defined]
           Comparison aperture

        ymin : float [if log defined]
           Minimum Y value for light curve plot

        ymax : float [if log defined]
           Maximum Y value for light curve plot

        ynorm : list(float) [if log defined]
           Normalisation factors, one per CCD for light curve plot

        yoffset : list(float) [if log defined]
           Offsets, one per CCD for light curve plot

        location : str [if log defined]
           Offsets, one per CCD for light curve plot

        fraction : float [if log defined]
           Fraction of figure to occupy, by height if location is South,
           by width if it is East

        lpad : tuple(float) [if log defined]
           padding on left, bottom, right and top of light curve plot as fraction
           of allocated width and height

        cmap : str
           The matplotlib colour map to use. "Greys" gives the usual greyscale.
           "none" will give whatever the current default is. Many other choices:
           "viridis", "jet", "hot", "Oranges", etc. Enter an invalid one and
           the program will fail but return a huge list of possibles in the
           process.

        width : float
           plot width in inches.

        height : float
           plot height in inches.

        dstore : str
           root directory for plot files. Will get names like dstore/run003_001.png.

        ndigit : int
           number of digits in frame counter, i.e. the '001' of the previous section.

        fext : str
           file extension 'png', 'jpeg' for images generated

        msub : bool
           subtract the median from each window before scaling for the
           image display or not. This happens after any bias subtraction.

        iset : str [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 : list(float) [if iset='d']
           lower intensity level, one per image

        ihi : list(float) [if iset='d']
           upper intensity level, one per image

        plo : float [if iset='p']
           lower percentile level

        phi : float [if iset='p']
           upper percentile level

        xlo : float
           left-hand X-limit for plot, initially at least since it is
           possible to re-size. For iset='p' these limits also set the
           region of the frame over which the percentil will be
           calculated. You will usually want yhi-ylo ~ xhi-xlo in
           magnitude because the aspect ratio is preserved.

        xhi : float
           right-hand X-limit for plot (can be < xlo to invert the display)

        ylo : float
           lower Y-limit for plot

        yhi : float
           upper Y-limit for plot (can be < ylo)

        dpi : int [hidden]
           dots per inch of output. Default 72. Allows control over font size versus image size,
           in combination with width and height.

        style : str [hidden, if log defined]
           style for light curves 'dots', 'line', 'both'. The line will be grey for the 'both' option.

        ms : float [hidden, if log defined and style==dots or both]
           markersize. Controls dot size which is useful when fiddling with dpi

        lw : float [hidden, if log defined and style==line or both]
           line width
    """

    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("flist", Cline.LOCAL, Cline.PROMPT)
        cl.register("ccd", Cline.LOCAL, Cline.PROMPT)
        cl.register("nx", 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("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("defect", Cline.GLOBAL, Cline.PROMPT)
        cl.register("log", Cline.LOCAL, Cline.PROMPT)
        cl.register("targ", Cline.LOCAL, Cline.PROMPT)
        cl.register("comp", Cline.LOCAL, Cline.PROMPT)
        cl.register("ymin", Cline.LOCAL, Cline.PROMPT)
        cl.register("ymax", Cline.LOCAL, Cline.PROMPT)
        cl.register("ynorm", Cline.LOCAL, Cline.PROMPT)
        cl.register("yoffset", Cline.LOCAL, Cline.PROMPT)
        cl.register("location", Cline.LOCAL, Cline.PROMPT)
        cl.register("fraction", Cline.LOCAL, Cline.PROMPT)
        cl.register("lpad", Cline.LOCAL, Cline.PROMPT)
        cl.register("cmap", Cline.LOCAL, Cline.PROMPT)
        cl.register("width", Cline.LOCAL, Cline.PROMPT)
        cl.register("height", Cline.LOCAL, Cline.PROMPT)
        cl.register("dstore", Cline.LOCAL, Cline.PROMPT)
        cl.register("ndigit", Cline.LOCAL, Cline.PROMPT)
        cl.register("fext", Cline.LOCAL, Cline.PROMPT)
        cl.register("msub", Cline.GLOBAL, Cline.PROMPT)
        cl.register("iset", Cline.GLOBAL, Cline.PROMPT)
        cl.register("ilo", Cline.LOCAL, Cline.PROMPT)
        cl.register("ihi", Cline.LOCAL, 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("dpi", Cline.LOCAL, Cline.HIDE)
        cl.register("style", Cline.LOCAL, Cline.HIDE)
        cl.register("ms", Cline.LOCAL, Cline.HIDE)
        cl.register("lw", Cline.LOCAL, Cline.HIDE)

        # 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 plot", 1)
            last = cl.get_value("last",
                                "last frame to plot [0 to go to the end]",
                                max(1, first), 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)

        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()
                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())

        # 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)

        # 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:
            # read the fringe map
            fmap = hcam.MCCD.read(fmap)
            fpair = cl.get_value("fpair", "fringe pair file",
                                 cline.Fname("fpair", hcam.FRNG))
            fpair = fringe.MccdFringePair.read(fpair)

            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)

        # 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)

        # reduce log file (if any)
        rlog = cl.get_value(
            "log",
            "reduce log file ['none' to ignore]",
            cline.Fname("reduce", hcam.LOG),
            ignore="none",
        )

        if rlog is not None:
            # Read reduce file
            hlg = hlog.Hlog.rascii(rlog)
            targ = cl.get_value("targ", "target aperture", "1")
            comp = cl.get_value("comp", "comparison aperture", "2")
            fmin = cl.get_value("ymin", "minimum Y value for light curve plot",
                                0.)
            fmax = cl.get_value("ymax", "maxmum Y value for light curve plot",
                                1.)

            # need to check that the default has the right number of
            # items, if not overr-ride it
            ynorm = cl.get_default("ynorm")
            if ynorm is not None:
                if len(ynorm) > len(ccds):
                    cl.set_default("ynorm", ynorm[:len(ccds)])
                elif len(ynorm) < len(ccds):
                    cl.set_default("ynorm",
                                   ynorm + (len(ccds) - len(ynorm)) * [1.])

            ynorm = cl.get_value(
                "ynorm",
                "normalisation factors for light curves (one per CCD)",
                len(ccds) * [1.])

            yoffset = cl.get_default("yoffset")
            if yoffset is not None:
                if len(yoffset) > len(ccds):
                    cl.set_default("yoffset", yoffset[:len(ccds)])
                elif len(yoffset) < len(ccds):
                    cl.set_default("yoffset",
                                   yoffset + (len(ccds) - len(yoffset)) * [0.])

            yoffset = cl.get_value(
                "yoffset", "vertical offsets for light curves (one per CCD)",
                len(ccds) * [0.])

            location = cl.get_value(
                "location",
                "position of light curve plot relative to images",
                "s",
                lvals=['s', 'e', 'S', 'E'])
            if location.lower() == 's':
                fraction = cl.get_value(
                    "fraction",
                    "fraction of figure in terms of height occupied by light curve",
                    0.5)
            elif location.lower() == 'e':
                fraction = cl.get_value(
                    "fraction",
                    "fraction of figure in terms of width occupied by light curve",
                    0.67)
            lpad = cl.get_value(
                "lpad",
                "padding (left,bottom,right,top) around light curve plot",
                (0.05, 0.05, 0.02, 0.02))

            # trim down to the specified frames
            lcs = []
            T0, tmax = None, None
            for cnam, yn, yo in zip(ccds, ynorm, yoffset):
                nframes = hlg[cnam]['nframe']
                keep = (nframes >= first) and (last == 0 or (nframes <= last))
                hlg[cnam] = hlg[cnam][keep]
                lc = (hlg.tseries(cnam, targ) /
                      hlg.tseries(cnam, comp)) / yn + yo
                if T0 is None:
                    T0 = lc.t.min()
                lc.ttrans(lambda t: 1440 * (t - T0))
                if tmax is None:
                    tmax = lc.t.max()
                else:
                    tmax = max(tmax, lc.t.max())
                lcs.append((nframes[keep], lc))

        # Some settings for the image plots
        cmap = cl.get_value("cmap",
                            "colour map to use ['none' for mpl default]",
                            "Greys")
        cmap = None if cmap == "none" else cmap

        width = cl.get_value("width", "plot width [inches]", 10., 0.5)

        height = cl.get_value("height", "plot height [inches]", 10., 0.5)

        dstore = cl.get_value("dstore", "directory for images", "tmp")
        if not os.path.isdir(dstore):
            raise hcam.HipercamError(f"'{dstore}' is not a directory")

        ndigit = cl.get_value("ndigit",
                              "number of digits for appended frame counter", 4,
                              1)
        fext = cl.get_value("fext",
                            "file extension for images",
                            "png",
                            lvals=["png", "jpg"])

        # 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
        ilos, ihis = len(ccds) * [0], len(ccds) * [1000]
        if iset == "d":
            # fiddle with the defaults
            ilo = cl.get_default("ilo")
            if ilo is not None:
                if len(ilo) > len(ccds):
                    cl.set_default("ilo", ilo[:len(ccds)])
                elif len(ilo) < len(ccds):
                    cl.set_default("ilo", ilo + (len(ccds) - len(ilo)) * [0.])

            ihi = cl.get_default("ihi")
            if ihi is not None:
                if len(ihi) > len(ccds):
                    cl.set_default("ihi", ihi[:len(ccds)])
                elif len(ihi) < len(ccds):
                    cl.set_default("ihi", ihi + (len(ccds) - len(ihi))[1000.])

            ilos = cl.get_value("ilo", "lower intensity limit",
                                len(ccds) * [0.])
            ihis = cl.get_value("ihi", "upper intensity limit",
                                len(ccds) * [1000.])

        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)
        dpi = cl.get_value("dpi", "dots per inch", 200)
        if rlog is not None:
            style = cl.get_value("style",
                                 "light curve plot style",
                                 "dots",
                                 lvals=('dots', 'line', 'both'))
            ms, lw = 0, 0
            if style == 'dots' or style == 'both':
                ms = cl.get_value("ms", "markersize", 2.)
            if style == 'line' or style == 'both':
                lw = cl.get_value("lw", "line width", 2.)

    ###############################################################################

    # Phew. We finally have all the inputs and now can now display stuff.

    # track which CCDs have been plotted at least once for the profile fits
    nccd = len(ccds)
    plotted = np.array(nccd * [False])
    current_ccds = nccd * [None]
    ny = nccd // nx if nccd % nx == 0 else nccd // nx + 1

    first_fig = True

    # Now go through data
    with spooler.data_source(source, resource, first, full=False) as spool:

        for nframe, mccd in enumerate(spool):

            # 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(f'{mccd.head.get("NFRAME",nframe+1)}, utc= {tstamp.iso}')

            if nframe == 0:
                # get the bias and 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)

                if fmap is not None:
                    # crop the fringe map and pair file
                    fmap = fmap.crop(mccd)
                    fpair = fpair.crop(mccd, nhalf)

            # wind through the CCDs to display, accumulating stuff
            # to send to the plot manager
            message = ""

            skipped = True
            for nc, cnam in enumerate(ccds):
                ccd = mccd[cnam]

                if ccd.is_data():
                    # "is_data" indicates genuine data as opposed to junk
                    # that results from nskip > 0.
                    plotted[nc] = True

                    # subtract the bias
                    if bias is not None:
                        ccd -= bias[cnam]

                    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]

                    # Remove fringes
                    if fmap is not None and cnam in fmap and cnam in fpair:
                        fscale = fpair[cnam].scale(ccd, fmap[cnam], nhalf,
                                                   rmin, rmax)
                        ccd -= fscale * fmap[cnam]

                    if msub:
                        # subtract median from each window
                        for wind in ccd.values():
                            wind -= wind.median()

                    # keep list of current CCDs
                    current_ccds[nc] = ccd

            # at this point current_ccds contains the current set of CCDs to plot

            if plotted.all():

                # Finally have at least one proper exposure of all
                # CCDs and can make plots every time with skipped
                # frames staying unchanged Set up the plot. ny by nx
                # rows x columns of images + 1 for optional light
                # curve which is either South or East of images

                prop_cycle = plt.rcParams['axes.prop_cycle']
                colors = prop_cycle.by_key()['color'][:len(ccds)]

                # create the figure just once to avoid memory
                # problems, i.e. we re-use the same figure for every
                # plot. plt.close() which I thought work instead
                # didn't and the programme went a bit bonkers at the
                # end releasing resources. Putting '0' didn't work either.
                if first_fig:
                    fig = plt.figure(figsize=(width, height))
                    first_fig = False

                if rlog is not None:
                    # plot light curve

                    # first the location
                    if location.lower() == 's':
                        # light curve "South" of the images
                        rect = [
                            lpad[0], fraction * lpad[1], 1 - lpad[0] - lpad[2],
                            fraction * (1 - lpad[1] - lpad[3])
                        ]
                        gs = GridSpec(ny, nx, figure=fig, bottom=fraction)
                    elif location.lower() == 'e':
                        # light curve "East" of the images
                        rect = [
                            1 - fraction + fraction * lpad[0], lpad[1],
                            fraction * (1 - lpad[0] - lpad[2]),
                            1 - lpad[1] - lpad[3]
                        ]
                        gs = GridSpec(ny, nx, right=1 - fraction)

                    # light curve axes
                    ax = fig.add_axes(rect)
                    ax.set_xlim(0, tmax)
                    ax.set_ylim(fmin, fmax)
                    ax.set_xlabel(f'Time [mins, since MJD = {T0:.4f}]')
                    ax.set_ylabel(f'Target / Comparison')
                    ax.tick_params(axis="x", direction="in")
                    ax.tick_params(axis="y", direction="in", rotation=90)
                    ax.tick_params(bottom=True,
                                   top=True,
                                   left=True,
                                   right=True)
                    for cnam, (nframes, lc), col in zip(ccds, lcs, colors):
                        plot = (nframes >= first) & (nframes <= first + nframe)
                        lct = lc[plot]
                        if style == 'dots':
                            fmt = '.'
                            color = None
                        elif style == 'line':
                            fmt = '-'
                            color = None
                            col = None
                        elif style == 'both':
                            fmt = '.-'
                            color = '0.7'

                        lct.mplot(ax,
                                  fmt=fmt,
                                  color=color,
                                  mfc=col,
                                  mec=col,
                                  ms=ms,
                                  lw=lw,
                                  label=f'CCD {cnam}')

                    if len(ccds) > 1:
                        ax.legend(loc='upper right')
                else:
                    # images only
                    gs = GridSpec(ny, nx, figure=fig)

                # plot images
                for n, (cnam, ccd, ilo,
                        ihi) in enumerate(zip(ccds, current_ccds, ilos, ihis)):
                    ax = fig.add_subplot(gs[n // nx, n % nx])
                    mpl.pCcd(ax,
                             ccd,
                             iset,
                             plo,
                             phi,
                             ilo,
                             ihi,
                             f'CCD {cnam}',
                             xlo=xlo,
                             xhi=xhi,
                             ylo=ylo,
                             yhi=yhi,
                             cmap=cmap)
                    ax.set_xlim(xlo, xhi)
                    ax.set_ylim(ylo, yhi)
                    ax.tick_params(axis="x", direction="in")
                    ax.tick_params(axis="y", direction="in", rotation=90)
                    ax.tick_params(bottom=True,
                                   top=True,
                                   left=True,
                                   right=True)

                # save to disk, clear figure
                oname = os.path.join(
                    dstore,
                    f'{os.path.basename(resource)}_{first+nframe:0{ndigit}d}.{fext}'
                )
                plt.savefig(oname, dpi=dpi)
                plt.clf()
                print(f'   written figure to {oname}')

            if last and nframe + first == last:
                break