Пример #1
0
def imstat():
    """main logic:"""
    optlist = parse_args()
    mu.init_logging(optlist.debug)
    mu.init_warnings()
    ncalls.counter = 0
    # begin processing -- loop over files
    for ffile in optlist.fitsfile:
        try:
            hdulist = fits.open(ffile)
        except IOError as ioerr:
            logging.error("IOError: %s", ioerr)
            exit(1)
        if optlist.info:  # just print the image info per file
            hdulist.info()
            continue
        if not optlist.noheadings:  # print filename
            print("#")
            print("# {}".format(os.path.basename(ffile)))
        # Construct a list of the HDU's to work on
        hduids = iu.get_requested_image_hduids(
            hdulist, optlist.hduname, optlist.hduindex
        )
        if optlist.quicklook:
            quicklook(optlist, hduids, hdulist)
        else:
            stats_proc(optlist, hduids, hdulist)
        ncalls.counter = 0  # reset per file, triggers headers
Пример #2
0
def imxtalk():
    """main logic:"""
    optlist = parse_args()
    mu.init_logging(optlist.debug)
    mu.init_warnings()
    ncalls.counter = 0
    # begin processing -- loop over files
    for ffile in optlist.fitsfile:
        try:
            hdulist = fits.open(ffile)
        except IOError as ioerr:
            logging.error("IOError: %s", ioerr)
            exit(1)
        if optlist.info:  # just print the image info per file
            hdulist.info()
            continue
        # Construct a list of the source HDU's to work on
        srcids = iu.get_requested_image_hduids(hdulist, optlist.srcname,
                                               optlist.srcindex)
        # Construct a list of the response HDU's to work on
        rspids = iu.get_requested_image_hduids(hdulist, optlist.rspname,
                                               optlist.rspindex)
        # do stuff
        logging.debug("calling iu.get_union_of_bad_column_segs(hdulist)")
        bad_segs = iu.get_union_of_bad_column_segs(hdulist)
        logging.debug("bad_segs=%s", bad_segs)
        max_rn = 7.0
        pcnt = 20
        lsst_num = hdulist[0].header.get("LSST_NUM")
        # get_xtalk_coefs(hdulist, srcids, rspids, optlist.threshold)
        for srcid in srcids:
            hdu_s = hdulist[srcid]
            # subtract the bias estimate from the source array
            if lsst_num and re.match(r"^E2V-CCD250", lsst_num):
                stype = "byrowe2v"
            else:
                stype = "byrow"
            ptype = "bycolfilter"
            iu.subtract_bias(stype, ptype, hdu_s, bad_segs)
            logging.info("hdu_s = %s", hdu_s.header["EXTNAME"])
            (datasec_s, soscan_s, poscan_s) = iu.get_data_oscan_slices(hdu_s)
            rn_est = min(np.std(hdu_s.data[poscan_s[0], soscan_s[1]]), max_rn)
            if optlist.threshold:
                thresh = optlist.threshold
            else:
                rn_est = min(np.std(hdu_s.data[poscan_s[0], soscan_s[1]]),
                             max_rn)
                thresh = 500 * rn_est
            # estimate source background level, the threshold will be added to that
            # since we are only interested in source pixels above background by thresh
            thresh += np.percentile(hdu_s.data[datasec_s], pcnt)

            # make the (weights) mask used in response hdu bckgrnd subtraction
            mask_s = np.ones_like(hdu_s.data, dtype=int)
            mask_s[np.nonzero(hdu_s.data > thresh)] = 0
            mask_s[:, bad_segs] = 0  # fold these in

            arr_s = hdu_s.data.flatten("K")
            logging.debug("np.shape(arr_s)= %s", np.shape(arr_s))
            arr_x = arr_s[arr_s > thresh]
            logging.debug("found %d nans in arr_x",
                          np.count_nonzero(np.isnan(arr_x)))
            arr_x = arr_x.reshape(
                -1, 1)  # infer 1st axis, 2nd axis for 1 "feature"
            logging.debug("np.shape(arr_x)= %s", np.shape(arr_x))
            if np.size(arr_x) < 1000:
                logging.warn(
                    "not enough source points to produce a coef: %d < 100",
                    np.size(arr_x),
                )
                continue

            if optlist.plot:
                plt.style.use(optlist.style)
                pu.update_rcparams()
                fig, axes = pu.get_fig_and_axis(
                    len(rspids),
                    optlist.layout,
                    False,
                    optlist.sharex,
                    optlist.sharey,
                    None,
                )
                nprows, npcols = (axes.shape[0], axes.shape[1])
                pu.set_fig_title(optlist.title, ffile, fig)
                sylim_upper = sylim_lower = 0.0

            for rindex, rspid in enumerate(rspids):
                if rspid == srcid:
                    sindex = rindex
                    continue
                hdu_r = hdulist[rspid]
                logging.info("    hdu_r = %s", hdu_r.header["EXTNAME"])
                if np.shape(hdu_s.data) != np.shape(hdu_r.data):
                    logging.warning(
                        "hdu's %s, %s shapes not commensurate: %s != %s, skipping",
                        hdu_s.header["EXTNAME"],
                        hdu_r.header["EXTNAME"],
                        np.shape(hdu_s.data),
                        np.shape(hdu_r.data),
                    )
                    continue
                (datasec_r, soscan_r,
                 poscan_r) = iu.get_data_oscan_slices(hdu_r)
                iu.subtract_bias(stype, ptype, hdu_r, bad_segs)
                # need to subtract background level estimate from hdu_s but it may
                # have lots of structure so need somewhat careful estimate
                # ------------
                # redo this with masking and line by line interp across the mask
                logging.debug("found %d nans in hdu_r",
                              np.count_nonzero(np.isnan(hdu_r.data)))
                iu.subtract_background_for_xtalk(hdu_r, mask_s, datasec_r)
                logging.debug("found %d nans in hdu_r",
                              np.count_nonzero(np.isnan(hdu_r.data)))
                arr_r = hdu_r.data.flatten("K")
                logging.debug("np.shape(arr_r)= %s", np.shape(arr_r))
                arr_y = arr_r[arr_s > thresh]

                logging.debug("found %d nans in arr_y",
                              np.count_nonzero(np.isnan(arr_y)))
                arr_xp = arr_x[~np.isnan(arr_y)]
                arr_yp = arr_y[~np.isnan(arr_y)]

                # reject high sources in response channel
                arr_xp = arr_xp[arr_yp < thresh]
                arr_yp = arr_yp[arr_yp < thresh]

                if optlist.intercept:
                    lr = linear_model.LinearRegression()
                    ransac = linear_model.RANSACRegressor()
                else:
                    lr = linear_model.LinearRegression(fit_intercept=False)
                    ransac = linear_model.RANSACRegressor(
                        linear_model.LinearRegression(fit_intercept=False))

                # lr.fit(arr_xp, arr_yp)
                # ransac.fit(arr_xp, arr_yp)
                # print(f"lr.coef={lr.coef_}")
                # print(f"ransac.estimator.coef={ransac.estimator_.coef_}")
                if np.max(arr_xp) < 0.95 * np.max(arr_x):
                    logging.warning("threshold is too low, raise and re-run")
                nbins = (np.max(arr_xp) - np.min(arr_xp)) / 1000 * rn_est
                logging.debug("np.max(arr_xp) = %.2f", np.max(arr_xp))
                logging.debug("np.min(arr_xp) = %.2f", np.min(arr_xp))
                logging.debug("nbins = %d", nbins)
                s, edges, _ = binned_statistic(arr_xp[:, 0], arr_yp, "median",
                                               nbins)
                cnt, cedges, _ = binned_statistic(arr_xp[:, 0], arr_yp,
                                                  "count", nbins)
                bin_width = edges[1] - edges[0]
                logging.debug("bin_width = %.2f", bin_width)
                binx = edges[1:] - bin_width / 2
                binx = binx[~np.isnan(s)]  # remove the empty bins
                count = cnt[~np.isnan(s)]
                logging.debug(
                    "count: mean: %.2f  median: %.2f  stddev: %.2f min: %.2f  max: %.2f",
                    np.mean(count),
                    np.median(count),
                    np.std(count),
                    np.min(count),
                    np.max(count),
                )
                count = np.sqrt(count) * (np.log10(binx)
                                          )  # extra weight on high bins
                logging.debug("binx[-10:] = %s", binx[-10:])
                logging.debug("count[-10:] = %s", count[-10:])
                s = s[~np.isnan(s)]
                sylim_upper = np.percentile(arr_yp, 99)
                sylim_lower = np.percentile(arr_yp, 1)
                if optlist.raw:  # expand limits
                    sydel = sylim_upper - sylim_lower
                    sylim_upper += 0.4 * sydel
                    sylim_lower -= 0.3 * sydel

                binx = binx.reshape(
                    -1, 1)  # infer 1st axis, 2nd axis for 1 "feature"
                lr.fit(binx, s, sample_weight=count)
                ransac.fit(binx, s, count)
                inlier_mask = ransac.inlier_mask_
                outlier_mask = np.logical_not(inlier_mask)
                lrcoef = lr.coef_[0]
                lrincpt = lr.intercept_
                rscoef = ransac.estimator_.coef_[0]
                rsincpt = ransac.estimator_.intercept_
                print(f"binned lr.coef={lrcoef:>3g}")
                if optlist.intercept:
                    print(f"binned lr.intercept={lrincpt:>.2f}")
                print(f"binned ransac.estimator.coef={rscoef:>3g}")
                if optlist.intercept:
                    print(f"binned ransac.estimator.intercept={rsincpt:>.2f}")

                # plotting
                if optlist.plot:
                    ax = np.ravel(axes)[int(rindex / npcols) * npcols +
                                        rindex % npcols]
                    if optlist.style == "ggplot":
                        ax.scatter([], [])  # skip the first color
                    ax.grid(True)
                    ax.set_xlabel("source signal", size="x-small")
                    ax.set_ylabel("response signal", size="x-small")
                    if optlist.raw:
                        ax.scatter(arr_xp[:, 0], arr_yp, s=1.0, label="raw")
                    si = s[inlier_mask]
                    ci = count[inlier_mask]
                    # ci[-1] = 1.0
                    ax.scatter(
                        binx[inlier_mask, 0],
                        si,
                        # s=np.sqrt(count),
                        s=np.sqrt(ci),
                        color="blue",
                        alpha=0.5,
                        label="inliers",
                    )
                    si = s[outlier_mask]
                    ci = count[outlier_mask]
                    # ci[-1] = 1.0
                    ax.scatter(
                        binx[outlier_mask, 0],
                        si,
                        # s=np.sqrt(count),
                        s=np.sqrt(ci),
                        color="purple",
                        alpha=0.5,
                        label="outliers",
                    )
                    if optlist.predict:  # Predict and plot result of estimated models
                        line_x = np.arange(0.0, binx.max())[:, np.newaxis]
                        line_y = lr.predict(line_x)
                        line_y_ransac = ransac.predict(line_x)
                        lw = 2
                        if optlist.intercept:
                            lbl = f"lr: {lrcoef:>.3g}*x + {lrincpt:>.3g}"
                        else:
                            lbl = f"lr: {lrcoef:>.3g}*x"
                        ax.plot(line_x,
                                line_y,
                                color="navy",
                                linewidth=lw,
                                label=lbl)
                        if optlist.intercept:
                            lbl = f"ransac: {rscoef:>.3g}*x + {rsincpt:>.3g}"
                        else:
                            lbl = f"ransac: {rscoef:>.3g}*x"
                        ax.plot(
                            line_x,
                            line_y_ransac,
                            color="cornflowerblue",
                            linewidth=lw,
                            label=lbl,
                        )

                    if optlist.ylimits:
                        ax.set_ylim(optlist.ylimits[0], optlist.ylimits[1])
                    else:
                        ax.set_ylim(sylim_lower, sylim_upper)
                    ax.xaxis.set_tick_params(labelsize="x-small")
                    ax.xaxis.set_major_formatter(ticker.EngFormatter())
                    ax.yaxis.set_tick_params(labelsize="x-small")
                    ax.set_title(
                        f"SRC:RSP {hdu_s.header['EXTNAME']}:{hdu_r.header['EXTNAME']}",
                        fontsize="xx-small",
                    )
                    handles, labels = ax.get_legend_handles_labels()
                    lgnd = pu.mk_legend("inside", nprows, handles, labels, ax)
                    # big hack
                    print(f"sizes={lgnd.legendHandles[-2]._sizes}")
                    lgnd.legendHandles[-2]._sizes = [6]
                    lgnd.legendHandles[-1]._sizes = [6]

            if optlist.plot:
                for gidx in range(rindex + 1, nprows * npcols):
                    # ax = np.ravel(axes)[int(gidx / npcols) * npcols + gidx % npcols]
                    ax = np.ravel(axes)[gidx]
                    ax.grid(False)
                    ax.set_frame_on(False)
                    ax.get_xaxis().set_visible(False)
                    ax.get_yaxis().set_visible(False)

                if srcid in rspids:
                    ax = np.ravel(axes)[sindex]
                    ax.grid(False)
                    ax.set_frame_on(False)
                    ax.get_xaxis().set_visible(False)
                    ax.get_yaxis().set_visible(False)

                fig.set_tight_layout({
                    "h_pad": 0.50,
                    "w_pad": 1.0,
                    "rect": [0, 0, 1, 0.97]
                })
                plt.show()

        # end of doing stuff
        ncalls.counter = 0  # reset per file, triggers headers

    ncalls()  # track call count, acts like static variable)
Пример #3
0
def imarith():
    """main logic:"""
    optlist = parse_args()
    mu.init_logging(optlist.debug)
    mu.init_warnings()

    # evaluate operands as either a filename, float, floats or error
    verify_args(optlist)
    region = None
    if optlist.region:
        region = iu.parse_region(optlist.region)

    # Open files, throws exception on error
    hdulist1 = fits.open(optlist.operand1, mode="readonly")
    if os.path.isfile(optlist.operand2):
        hdulist2 = fits.open(optlist.operand2, mode="readonly")
    else:
        hdulist2 = None
        operand2 = optlist.operand2.split()  # list of floats as strings

    # create output image with primary header and updates
    hdulisto = iu.create_output_hdulist(hdulist1, sys.argv)

    # loop over HDU id's from Master file, copy non-image HDUs
    # and process the image HDUs accordingly
    hduids = iu.get_requested_image_hduids(hdulist1, optlist.hduname,
                                           optlist.hduindex)
    if hduids is None:
        logging.info("No data HDUs found or requested")
        sys.exit(1)
    for hduid in hduids:  # process these images
        #
        # This needs work to allow more flexible hdulist2 type images
        # as-is, it enforces that names match for corresponding hdu's
        hdu1 = hdulist1[hduid]
        if hdulist2:
            if isinstance(hdulist2[hduid], (fits.ImageHDU, fits.CompImageHDU)):
                hdu2 = hdulist2[hduid]
            else:
                logging.error("HDU %d does not exist in %s", hduid,
                              hdulist2.filename())
        #
        if hdulist2 and np.shape(hdu1.data) != np.shape(hdu2.data):
            logging.error("Images are not comensurate")
            sys.exit(1)

        # prepare the output hdu
        hduo = iu.init_image_hdu(hdu1, hdulisto, region)

        # optionally subtract bias
        if not optlist.sbias and not optlist.pbias:
            pass
        else:
            iu.subtract_bias(optlist.sbias, optlist.pbias, hdu1)
            if hdulist2:
                iu.subtract_bias(optlist.sbias, optlist.pbias, hdu2)
        #
        # do the arithmetic
        if hdulist2:
            hduo.data = ffcalc(hdu1.data, hdu2.data, optlist.op, region)
        else:  # scalar or list of scalars
            if len(operand2) == 1:
                arg2 = float(operand2[0])
            else:
                arg2 = float(operand2.pop(0))
            hduo.data = fscalc(hdulist1[hduid].data, arg2, optlist.op, region)
        # finish up this hdu
        hduo.update_header()
        dtstr = datetime.datetime.utcnow().isoformat(timespec="milliseconds")
        hduo.add_checksum(dtstr)

    for hdu in hdulist1:
        # append to output if it does not contain image data
        if not isinstance(hdu,
                          (fits.ImageHDU, fits.CompImageHDU, fits.PrimaryHDU)):
            hdulisto.append(hdu)

    # write the output file
    hdulisto.info()
    hdulisto.writeto(optlist.result, overwrite=True)
    sys.exit(0)
Пример #4
0
def imfft():
    """main logic:"""
    optlist = parse_args()
    mu.init_logging(optlist.debug)
    mu.init_warnings()

    if optlist.scaling == "density":
        window = "boxcar"
    else:
        window = "flattop"

    # Open files
    fileno = 0
    hduids = []
    # loop over files
    for ffile in optlist.fitsfile:
        try:
            hdulist = fits.open(ffile)
        except IOError as ioerr:
            emsg = "IOError: {}".format(ioerr)
            logging.error(emsg)
            sys.exit(1)
        if optlist.info:  # just print the image info and exit
            hdulist.info()
            continue
        # Construct a list of the HDU's to work on
        hduids = iu.get_requested_image_hduids(hdulist, optlist.hduname,
                                               optlist.hduindex)
        # loop over hdu's
        hducnt = 0
        for hduid in hduids:
            hdr = hdulist[hduid].header
            try:
                dstr = hdr["DATASEC"]
            except KeyError as ke:
                emsg = "KeyError: {}, required".format(ke)
                logging.error(emsg)
                sys.exit(1)
            debugmsg = "DATASEC={}".format(dstr)
            logging.debug(debugmsg)
            res = re.match(r"\[*([0-9]*):([0-9]+),([0-9]+):([0-9]+)\]*", dstr)
            if res:
                datasec = res.groups()
            else:
                emsg = "DATASEC:{} parsing failed".format(dstr)
                logging.error(emsg)
                sys.exit(1)

            # define region to measure
            x1 = int(datasec[0]) - 1
            x2 = int(datasec[1])

            stddev = float(hdr["STDVBIAS"])
            pix = hdulist[hduid].data
            fs = 1.0 / (optlist.rt * 1e-9)
            # measure the size needed
            arr = pix[optlist.row, x1:x2]
            x, p = signal.periodogram(arr, fs, window, scaling=optlist.scaling)
            flen = x.size
            plen = p.size
            if flen != plen:
                emsg = "flen({}) != plen({})".format(flen, plen)
                logging.error(emsg)
                emsg = "DATASEC:{} parsing failed".format(dstr)
                logging.error(emsg)
                sys.exit(1)
            # now do the real calculation
            f = np.empty((optlist.nrows, flen))
            Pxx_den = np.empty((optlist.nrows, plen))
            for rr in range(0, optlist.nrows):
                arr = pix[rr + optlist.row, x1:x2]
                if optlist.clip:
                    amed = np.median(arr)
                    farr = sigma_clip(arr)
                    x, p = signal.periodogram(farr.filled(amed),
                                              fs,
                                              window,
                                              scaling=optlist.scaling)
                else:
                    x, p = signal.periodogram(arr,
                                              fs,
                                              window,
                                              scaling=optlist.scaling)
                f[rr] = x
                Pxx_den[rr] = p

            f_avg = np.average(f, axis=0)
            Pxx_den_avg = np.average(Pxx_den, axis=0)
            # track the range needed for y-axis limits
            if (fileno + hducnt) == 0:
                pmin = Pxx_den_avg.min()
                pmax = Pxx_den_avg.max()
                debugmsg = "pmin0={:>g}".format(pmin)
                logging.debug(debugmsg)
                debugmsg = "pmax0={:>g}".format(pmax)
                logging.debug(debugmsg)
            else:
                if pmin > Pxx_den_avg.min():
                    pmin = Pxx_den_avg.min()
                    debugmsg = "pmin={:>g}".format(pmin)
                    logging.debug(debugmsg)
                if pmax < Pxx_den_avg.max():
                    pmax = Pxx_den_avg.max()
                    debugmsg = "pmax={:>g}".format(pmax)
                    logging.debug(debugmsg)

            plt.semilogy(
                f_avg,
                Pxx_den_avg,
                label="{}:{:>02d}:{:>7.2f}".format(fileno, hduid, stddev),
            )
            hducnt += 1
            # end loop over hdui's
        fileno += 1
        # end loop over files
    #
    plt.ylim([0.8 * pmin, 1.2 * pmax])
    plt.xlabel("freqquency [Hz]")
    if optlist.scaling == "density":
        plt.ylabel("PSD [V**2/Hz]")
    else:
        plt.ylabel("Linear spectrum [V RMS]")
    plt.grid(True)
    plt.legend(fontsize="xx-small", title="File:HDUi RN")
    plt.show()
Пример #5
0
def imcombine():
    """main logic:"""
    optlist = parse_args()
    mu.init_logging(optlist.debug)
    mu.init_warnings()
    verbose = optlist.verbose

    # convert to slice format
    region = None
    if optlist.region:
        region = iu.parse_region(optlist.region)

    # Prepare scaling region in slice format
    scaling = None
    if optlist.scaling:
        scaling = iu.parse_region(optlist.scaling)

    # build file list
    if optlist.fitsfile:  # input files listed on cmd line
        ifiles = optlist.fitsfile
    elif optlist.ifile:  # input files listed in one or more files
        if not ifiles:
            ifiles = []
        for b in mu.file_to_tokens(optlist.ifile):
            ifiles.extend(b)
    ifiles = sorted(list(set(ifiles)))  # remove duplicates
    if verbose:
        print(f"using {len(ifiles)} input files")
    if optlist.debug:
        logging.debug("input files:")
        for ff in ifiles:
            logging.debug("  %s", ff)

    # get a list of verified images as hdulists
    # prepare input files for use (open and verify)
    if optlist.bimage:  # include in verification
        ifiles.append(optlist.bimage)
    iimages = iu.files_to_hdulists(ifiles, True)
    if optlist.bimage:
        bimage = iimages.pop()  # remove & assign last as bias image
    else:
        bimage = None

    # create output image with primary header and updates
    hdulisto = iu.create_output_hdulist(iimages[0], sys.argv)

    # get all requested hduids
    hduids_to_proc = iu.get_requested_hduids(iimages[0], optlist.hduname,
                                             optlist.hduindex)
    if hduids_to_proc is None:
        logging.error("No HDUs found or requested")
        sys.exit(1)
    # get just requested hduids with image data to be combined
    hduids_to_comb = iu.get_requested_image_hduids(iimages[0], optlist.hduname,
                                                   optlist.hduindex)
    if hduids_to_comb is None:
        logging.error("No data HDUs found or requested")
        sys.exit(1)

    # choose the method to combine images
    if optlist.median:
        method = ["median"]
        if len(iimages) < 3:
            logging.warning("image count %d < 3, can only choose mean",
                            len(iimages))
            sys.exit()
    elif optlist.mean:
        method = ["mean"]
    elif optlist.sigmaclipped:
        method = ["sigmaclipped", optlist.sigmaclipped]
        if len(iimages) < 5:
            logging.warning("image count %d < 5, can only choose mean",
                            len(iimages))
            sys.exit()
    elif optlist.rank:
        method = ["rank", optlist.rank]
        if len(iimages) < 5:
            logging.warning("image count %d < 5, can only choose median, mean",
                            len(iimages))
            sys.exit()
    else:
        method = ["median"]  # default

    # prepare the output image hdus using the first image as a template
    for hduid, hdui in enumerate(iimages[0]):
        # hdu image has data to combine
        if hduid in hduids_to_comb:
            logging.debug(f"processing hdu index {hduid}")
            hduo = iu.init_image_hdu(hdui, hdulisto, region)
            # this is the main algorithm/function
            iu.image_combine_hdu(
                iimages,
                hduid,
                method,
                region,
                bimage,
                optlist.sbias,
                optlist.ptype,
                scaling,
                hduo,
            )
            # finish up this hdu
            hduo.update_header()
            dtstr = datetime.datetime.utcnow().isoformat(
                timespec="milliseconds")
            hduo.add_checksum(dtstr)

        # just append if image hdu has no data (eg. tables etc.)
        if hduid in hduids_to_proc:
            if not isinstance(
                    hdui, (fits.ImageHDU, fits.CompImageHDU, fits.PrimaryHDU)):
                # append extensions that contain non-image data
                logging.debug(f"appending hdu index {hduid}")
                hdulisto.append(hdui)

    # write the output file
    hdulisto.writeto(optlist.result[0], overwrite=True)
Пример #6
0
def implot():
    """main logic:"""
    optlist = parse_args()
    mu.init_logging(optlist.debug)
    mu.init_warnings()
    logging.debug("optlist: %s", optlist)

    # update/override some critical parameters
    plt.style.use(optlist.style)
    pu.update_rcparams()
    # uncomment to use latex
    #  plt.rcParams["text.usetex"] = True
    #  plt.rcParams["font.size"] = 12
    #  plt.rc("text.latex", preamble=r"\usepackage{underscore}")

    fig, axes = pu.get_fig_and_axis(
        len(optlist.fitsfile),
        optlist.layout,
        optlist.overlay,
        optlist.sharex,
        optlist.sharey,
        optlist.dpi,
    )

    fsize = fig.get_size_inches()
    logging.debug("width= %5.2f, height= %5.2f", fsize[0], fsize[1])
    logging.debug("len(axes)=%d", len(axes))
    logging.debug("axes.shape= %s", axes.shape)
    nprows, npcols = (axes.shape[0], axes.shape[1])
    logging.debug("nprows= %d, npcols= %d", nprows, npcols)

    pu.set_fig_title(optlist.title, optlist.fitsfile, fig)
    nfiles = len(optlist.fitsfile)

    # findex = 0
    # for ffile in optlist.fitsfile:
    for findex in range(0, nfiles):
        try:
            hdulist = fits.open(optlist.fitsfile[findex],
                                memmap=optlist.nomemmap)
        except IOError as ioerr:
            logging.error("IOError: %s", ioerr)
            sys.exit(1)
        # info option
        if optlist.info:
            hdulist.info()
            continue

        if optlist.overlay:
            ax = axes[0, 0]
        else:
            logging.debug(
                "ax = np.ravel(axes)[%d + %d]",
                int(findex / npcols) * npcols,
                findex % npcols,
            )
            ax = np.ravel(axes)[int(findex / npcols) * npcols +
                                findex % npcols]

        # construct a list of the HDU's to work on
        hduids = iu.get_requested_image_hduids(hdulist, optlist.hduname,
                                               optlist.hduindex)
        if hduids is None:
            logging.error("No valid HDUs found in %s", optlist.hduname
                          or optlist.hduindex)
            sys.exit(1)

        # plot title is truncated filename (w/out path or .fit(s))
        title_str = re.sub(r"^.*/(.*)$", r"\1", optlist.fitsfile[findex])
        title_str = re.sub(r"^(.*)\.fits?(\.fz)*$", r"\1", title_str)
        if npcols < 3:
            title_nchars = 44
            title_fontsize = "x-small"
        else:
            title_nchars = 32
            title_fontsize = "xx-small"
        if len(title_str) > title_nchars:
            title_str = "{}...".format(title_str[:title_nchars])
        else:
            title_str = "{}".format(title_str)
        logging.debug("using title_nchars=%d title_fontsize=%s", title_nchars,
                      title_fontsize)
        if optlist.overlay:
            if nfiles > 1:  # trunc'd filename in legend
                ax.plot([], [], " ", label=title_str)
        elif nfiles == 1 and not optlist.title:
            ax.set_title(title_str, fontsize=title_fontsize)
            if optlist.style == "ggplot":
                ax.plot([], [])  # skip the first color
        else:
            ax.set_title(title_str, fontsize=title_fontsize)
            if optlist.style == "ggplot":
                ax.plot([], [])  # skip the first color

        if optlist.xlimits:
            # for ax in np.ravel(axes):
            xbot, xtop = ax.set_xlim(optlist.xlimits[0], optlist.xlimits[1])
            logging.debug("xbot= %.3g xtop= %.3g", xbot, xtop)
        if optlist.ylimits:
            # for ax in np.ravel(axes):
            ybot, ytop = ax.set_ylim(optlist.ylimits[0], optlist.ylimits[1])
            logging.debug("ybot= %.3g ytop= %.3g", ybot, ytop)

        if optlist.logy:
            logging.debug("set_yscale(symlog)")
            ax.set_yscale("symlog")

        # y label depends on offset type
        if not optlist.offset:
            ax.set_ylabel("signal", size="x-small")
        elif optlist.offset == "mean":
            ax.set_ylabel("signal - mean", size="x-small")
        elif optlist.offset == "median":
            ax.set_ylabel("signal - median", size="x-small")
        elif optlist.offset == "delta":
            ax.set_ylabel("signal - mean + 5*j*stdev, j=0,1,..",
                          size="x-small")
        else:
            logging.error("invalid --offset choice")
            sys.exit(1)
        # x label
        ax.grid(True)
        if optlist.row is not None:
            ax.set_xlabel("column", size="x-small")
            if optlist.ltype == "series":
                ax.set_xlabel("col series", size="x-small")
        elif optlist.col is not None:
            ax.set_xlabel("row", size="x-small")
            if optlist.ltype == "series":
                ax.set_xlabel("row series", size="x-small")
        else:
            logging.error("must have one of --row or --col")
            sys.exit(1)

        # do the plotting
        pu.plot_hdus(vars(optlist), hduids, hdulist, ax)

        #  done with file, close it
        hdulist.close()

        # end of loop over files

    if optlist.info:  # just print the image info and exit
        sys.exit()

    if optlist.title:  # set the suptitle
        fig.set_tight_layout({
            "h_pad": 0.50,
            "w_pad": 1.0,
            "rect": [0, 0, 1, 0.97]
        })
    else:
        fig.set_tight_layout({
            "h_pad": 0.50,
            "w_pad": 1.0,
            "rect": [0, 0, 1, 1]
        })

    # Deal with the legend (ugly)
    # Get list of handles, labels from first plot
    ax = np.ravel(axes)[0]
    handles, labels = ax.get_legend_handles_labels()
    if nfiles == 1 or optlist.overlay:
        ax = np.ravel(axes)[0]
    else:
        ax = np.ravel(axes)[-1]  # put legend in last slot
    pu.mk_legend(optlist.placement, nprows, handles, labels, ax)

    if not optlist.overlay:
        for gidx in range(nfiles, nprows * npcols):
            ax = np.ravel(axes)[int(gidx / npcols) * npcols + gidx % npcols]
            ax.grid(False)
            ax.set_frame_on(False)
            ax.get_xaxis().set_visible(False)
            ax.get_yaxis().set_visible(False)

    if optlist.saveplot:
        fig.savefig(f"{optlist.saveplot}", dpi=600)

    plt.show()