"""Wrappers around the astromatic.net software library."""
import os
import shutil
import glob
import subprocess
import multiprocessing
try:
    from astropy.io import fits as pyfits
except ImportError:
    import pyfits
from imagelog import ImageLog
from dbtools import reach
[docs]class Astromatic(object):
    """Abstract base class for the Astromatic software wrappers
    (SExtractor, SCAMP, Swarp).
    """
    def __init__(self, configs=None, workDir=".", defaultsPath=None):
        self.configs = configs
        self.workDir = workDir
        self.defaultsPath = defaultsPath
        
        # Initialize the working directory
        if os.path.exists(self.workDir) is not True:
            print "making %s" % self.workDir
            os.makedirs(self.workDir)
    
[docs]    def add_default_param_to_configs(self, defaultsCommand,
            name="defaults.txt"):
        """Adds the default parameters filepath to the configs dictionary.
        Writes a new defaults file is one is not found/available.
        """
        # Write new defaults file if necessary
        if self.defaultsPath is None:
            self.write_defaults_file(defaultsCommand)
        elif os.path.exists(self.defaultsPath) is False:
            self.write_defaults_file(defaultsCommand)
        
        self.add_to_configs("c", self.defaultsPath)
     
[docs]    def write_defaults_file(self, defaultsCommand):
        """Writes the Swarp internal defaults and returns its path."""
        self.defaultsPath = os.path.join(self.workDir, "defaults.txt")
        print self.defaultsPath
        command = "%s > %s" % (defaultsCommand, self.defaultsPath)
        p = subprocess.Popen(command, shell=True)
        p.wait()
        
        return self.defaultsPath
     
[docs]    def add_to_configs(self, key, value):
        """Add a key, with a value, to the configs dictionary. Use this to
        ensure the configs are initialized.
        """
        if self.configs is None:
            self.configs = {key: value}
        else:
            self.configs[key] = value
     
[docs]    def set_null_config(self, key, null="\"\""):
        """Sets the value of the key configuration to the null value, which
        is a null string ("") by default. If null is None, the the key is
        deleted altogether.
        """
        if self.configs is None:
            if null is not None:
                self.configs = {key: null}
            else:
                self.configs = {}
        else:
            if null is not None:
                self.configs[key] = null
            else:
                if key in self.configs:
                    del self.configs[key]
     
[docs]    def make_config_command(self):
        """Joins the command line configuration arguments together, returning
        a string.
        """
        if self.configs is not None:
            configCmd = ""
            for key, value in self.configs.iteritems():
                configCmd += " -%s %s" % (key, value)
            return configCmd
        else:
            return None
     
[docs]    def make_command(self):
        """Place holder subclass to generate the command line string for
        running the Terapix program.
        """
        pass
     
[docs]    def run(self, virtualHost=None):
        """Runs the command process.
        The optional virtualHost is a tuple with format:
            (user@host, rootPath)
        """
        # Make the terapix program command
        command = self.make_command()
        
        # Append the ssh call if we run on a virtual machine
        if virtualHost is not None:
            command = " ".join(["ssh",
                virtualHost[0],
                "'cd %s;%s'" % (virtualHost[1], command)])
        
        print command
        p = subprocess.Popen(command, shell=True)
        p.wait()
  
[docs]class Swarp(Astromatic):
    """This class wraps the functionality of Astromatic's Swarp mosaic
    software.
    """
    def __init__(self, imagePaths, mosaicName, scampHeadPaths=None,
            weightPaths=None, defaultsPath=None, configs=None,
            workDir="mosaic", uniqueExt=""):
        self.imagePaths = imagePaths
        self.uniqueExt = uniqueExt
        mosaicBasename = os.path.splitext(mosaicName)[0]
        self.mosaicPath = os.path.join(workDir, mosaicBasename + ".fits")
        self.mosaicWeightPath = os.path.join(workDir,
                mosaicBasename + "_weight.fits")
        
        if weightPaths is not None:
            self.useWeights = True
        else:
            self.useWeights = False
        
        self.swarpInputs = {}
        self.imageNames = [os.path.basename(os.path.splitext(imagePaths[i])[0])
                for i in xrange(len(self.imagePaths))]
        for i, key in enumerate(self.imageNames):
            print key
            if scampHeadPaths is None:
                headPath = None
            else:
                headPath = scampHeadPaths[i]
            
            if self.useWeights:
                weightPath = weightPaths[i]
            else:
                weightPath = None
            
            imagePath = imagePaths[i]
            self.swarpInputs[key] = {'head': headPath, 'path': imagePath,
                'weight': weightPath}
        
        super(Swarp, self).__init__(configs=configs, workDir=workDir,
            defaultsPath=defaultsPath)
    
    @classmethod
[docs]    def from_db(cls, imageLog, imageKeys, pathKey, mosaicName,
            scampHeadPathKey=None, weightPathKey=None, defaultsPath=None,
            configs=None, workDir="mosaic", uniqueExt=""):
        """Construct a Swarp run from database records."""
        dataKeys = [pathKey]
        if scampHeadPathKey is not None:
            dataKeys.append(scampHeadPathKey)
        if weightPathKey is not None:
            dataKeys.append(weightPathKey)
        
        recs = imageLog.find_dict({}, images=imageKeys, fields=dataKeys)
        imagePaths = [reach(recs[k], pathKey) for k in imageKeys]
        if scampHeadPathKey is not None:
            scampHeadPaths = [reach(recs[k], scampHeadPathKey)
                    for k in imageKeys]
        else:
            scampHeadPaths = None
        if weightPathKey is not None:
            weightPaths = [reach(recs[k], weightPathKey) for k in imageKeys]
        else:
            weightPaths = None
        
        return cls(imagePaths, mosaicName, scampHeadPaths=scampHeadPaths,
            weightPaths=weightPaths, defaultsPath=defaultsPath,
            configs=configs, workDir=workDir, uniqueExt="")
     
[docs]    def set_target_fits(self, targetFITSPath):
        """Use the header of `targetFITSPath` to define output pixel space.
        
        This method links `targetFITSPath` to *mosaicName*".head", which
        Swarp automatically recognizes.
        """
        targetFITSPath = os.path.abspath(targetFITSPath)
        destPath = os.path.splitext(self.mosaicPath)[0] + ".head"
        if os.path.lexists(destPath): os.remove(destPath)
        cmd = "ln -s %s %s" % (targetFITSPath, destPath)
        subprocess.call(cmd, shell=True)
     
    def _write_target_header(self, headerText):
        """docstring for _write_target_header"""
        path = os.path.splitext(self.mosaicPath)[0] + ".head"
        if os.path.exists(path):
            os.remove(path)
        f = open(path, 'w')
        f.write(headerText)
        f.close()
    
[docs]    def make_command(self):
        """Produces the command to run Swarp."""
        # Add default parameters to the configs file. Writes a new defaults
        # if one is not found
        self.add_default_param_to_configs("swarp -d")
        
        # Write the input FITS file list to disk
        inputFITSPaths = []
        inputWeightPaths = []
        for key, db in self.swarpInputs.iteritems():
            inputFITSPaths.append(db['path'])
            inputWeightPaths.append(db['weight'])
        
        listPath = self.write_input_file_list(inputFITSPaths,
                name="inputlist" + self.uniqueExt)
        
        if self.useWeights:
            weightListPath = self.write_input_file_list(inputWeightPaths,
                    name="weightlist" + self.uniqueExt)
            self.add_to_configs("WEIGHT_IMAGE", "@" + weightListPath)
        else:
            # ensure there's no weight
            self.add_to_configs("WEIGHT_TYPE", "NONE")
            self.set_null_config("WEIGHT_IMAGE", null=None)
        # Setup external headers
        for key, db in self.swarpInputs.iteritems():
            imagePath = db['path']
            origHeaderPath = db['head']
            newHeaderPath = os.path.splitext(imagePath)[0] + ".head"
            if (origHeaderPath is not None) \
                    
and (origHeaderPath != newHeaderPath):
                if os.path.exists(newHeaderPath):
                    os.remove(newHeaderPath)  # clean out old copies at dest.
                shutil.copy(origHeaderPath, newHeaderPath)
        # Make resampling directory if it does not exist
        if "RESAMPLE_DIR" in self.configs:
            resamp_dir = self.configs['RESAMPLE_DIR']
            if not os.path.exists(resamp_dir):
                os.makedirs(resamp_dir)
        
        # Form command with the inputlist
        command = "swarp @%s" % listPath
        
        # Append the output file configuration
        self.add_to_configs("IMAGEOUT_NAME", self.mosaicPath)
        self.add_to_configs("WEIGHTOUT_NAME", self.mosaicWeightPath)
        
        # Append all other configurations
        configCmd = self.make_config_command()
        if configCmd is not None:
            command = " ".join((command, configCmd))
        
        return command
     
[docs]    def mosaic_paths(self):
        """:return: tuple of (mosaic path, mosaic weight path)."""
        return self.mosaicPath, self.mosaicWeightPath
     
[docs]    def copy_mosaic_wcs(self, headerPath):
        """Copies the WCS keys in the header of the mosaic image (as produced)
        by swarp.run() to an ascii header file at *headerPath*.
        
        This is useful for creating a .head file for use with a second run of
        Swarp with another data set (like a different colour) so that the
        pixel-sky projection is the same in both.
        
        TODO DEPRECATED Refactored as a function in Terapix.py. Keep this
        method around as a wrapper to Terapix.copyHeaderWCS().
        """
        mosaicFITS = pyfits.open(self.mosaicPath)
        mosaicHeader = mosaicFITS[0].header
        mosaicCardList = mosaicHeader.ascardlist()
        
        wcsKeys = ('EQUINOX', 'RADECSYS', 'CTYPE1', 'CUNIT1', 'CRVAL1',
                'CRPIX1', 'CD1_1', 'CD1_2', 'CTYPE2', 'CUNIT2', 'CRVAL2',
                'CRPIX2', 'CD2_1', 'CD2_2')
        
        # List of WCS header cards, as strings
        wcsCardList = []
        
        for key in wcsKeys:
            # try to find this key in the mosaic fits image
            try:
                # Trim the last character so that when we add a newline, it
                # is located at char#80
                cardStr = str(mosaicCardList[key])[:-1]
            except:
                continue  # evidently that key does not exist the mosaic FITS
            wcsCardList.append(cardStr)
        
        # Write the WCS cards, one per line, to headerPath
        headerText = "\n".join(wcsCardList)
        headerText = "".join((headerText, "\n"))  # append last newline
        if os.path.exists(headerPath):
            os.remove(headerPath)
        f = open(headerPath, 'w')
        f.write(headerText)
        f.close()
        
        mosaicFITS.close()
  
[docs]class Scamp(Astromatic):
    """Python wrapper class for Terapix's SCAMP application for astrometric
    and photometric registration of mosaics.
    
    .. note:: the useFileRefs=True feature seems to be broken b/c SCAMP doesn't
    recognize the files that it downloaded itself. For now, always use False
    for this option.
    """
    
    # Listing of all check plots available to scamp 1.4.2
    ALLPLOTS = ["SKY_ALL", "FGROUPS", "DISTORTION", "ASTR_INTERROR1D",
        "ASTR_INTERROR2D", "ASTR_REFERROR1D", "ASTR_REFERROR2D",
        "ASTR_PIXERROR1D", "ASTR_SUBPIXERROR1D", "ASTR_CHI2",
        "ASTR_REFSYSMAP", "ASTR_REFPROPER", "ASTR_COLSHIFT1D",
        "PHOT_ERROR", "PHOT_ERRORVSMAG", "PHOT_ZPCORR", "PHOT_ZPCORR3D",
        "SHEAR_VS_AIRMASS"]
    
    def __init__(self, seCatPaths, imageLog=None, imageKeys=None,
            defaultsPath=None, configs=None, checks=None, workDir="scamp",
            useFileRefs=False):
        self.catalogPaths = seCatPaths
        self.useFileRefs = useFileRefs
        self.imageLog = imageLog
        self.imageKeys = imageKeys
        self.headDB = {}
        for key, catPath in zip(self.imageKeys, self.catalogPaths):
            # Also, add path of .head file, to be produced by scamp, to a
            # database dictionary. The path is the FITS_LDAC's path, but
            # with a new extension.
            #
            # FIXME. how should this be done if no image keys are used?
            headPath = ".".join((os.path.splitext(catPath)[0], "head"))
            self.headDB[key] = headPath
        
        super(Scamp, self).__init__(configs=configs, workDir=workDir,
            defaultsPath=defaultsPath)
        
        # Initialize the check plots
        if checks is not None:
            self.set_check_plots(checks)
        else:
            self.checkList = None
    
    @classmethod
[docs]    def from_db(cls, imageLog, imageKeys, seCatKey, defaultsPath=None,
            configs=None, checks=None, workDir='scamp', useFileRefs=False):
        """Construct a SCAMP run from an image log database."""
        records = imageLog.get_images(imageKeys, [seCatKey])
        seCatPaths = [records[imageKey][seCatKey] for imageKey in imageKeys]
        
        return cls(seCatPaths, imageLog=imageLog, imageKeys=imageKeys,
            defaultsPath=defaultsPath, configs=configs, checks=checks,
            workDir=workDir, useFileRefs=False)
     
[docs]    def set_check_plots(self, checks):
        """Initializes the scamp checkplot types, and associated file names.
        
        By default, the file names are lower-cased versions of the check type,
        and the files are placed in the workDir.
        """
        self.checkList = checks
        self.checkPaths = []
        for checkType in self.checkList:
            self.checkPaths.append(os.path.join(self.workDir,
                checkType.lower()))
     
[docs]    def refcatalog_paths(self):
        """Returns the paths to existing reference catalogs."""
        catSource = self.configs["ASTREF_CATALOG"]
        catWildcard = "*".join((catSource, ".cat"))
        paths = glob.glob(os.path.join(self.workDir, catWildcard))
        return paths
     
[docs]    def make_command(self):
        """Writes the command to run scamp, and returns as a string."""
        
        # Make/add the default parametes file to the configs
        self.add_default_param_to_configs("scamp -d")
        
        # Write the input FITS file list to disk
        listPath = self.write_input_file_list(self.catalogPaths)
        
        # Form command with inputfile
        command = "scamp @%s" % listPath
        
        # If we're using pre-downloaded reference files, set this now
        if self.useFileRefs is True:
            # FIXME this is weird, refcatalog_paths() relied on ASTREF_CATALOG
            # being the name of the catalog source, and that useFileRefs is
            # the only thing that tells us to use a FILE source. Do something
            # more robust? e.g. the next two lines cannot be interchanged
            # in their ordering.
            refPaths = self.refcatalog_paths()
            self.add_to_configs('ASTREF_CATALOG', 'FILE')
            self.add_to_configs('ASTREFCAT_NAME', ",".join(refPaths))
        
        # Append checkplots
        if self.checkList is not None:
            checkPathArgs = ",".join(self.checkPaths)
            checkTypeArgs = ",".join(self.checkList)
            self.add_to_configs('CHECKPLOT_TYPE', checkTypeArgs)
            self.add_to_configs('CHECKPLOT_NAME', checkPathArgs)
        else:
            self.add_to_configs("CHECKPLOT_TYPE", "NONE")
        
        # Append all other configurations
        configCmd = self.make_config_command()
        if configCmd is not None:
            command = " ".join((command, configCmd))
        
        return command
     
 
def _workSE(args):
    """Worker function for batch source extraction."""
    imageKey, imagePath, weightPath, weightType, psfPath, configs, \
        checkImages, catPostfix, workDir, defaultsPath = args
    
    catalogName = "_".join((str(imageKey), catPostfix))
    se = SourceExtractor(imagePath, catalogName, weightPath=weightPath,
        weightType=weightType, psfPath=psfPath, configs=configs,
        workDir=workDir,
        defaultsPath=defaultsPath)
    if checkImages is not None:
        se.set_check_images(checkImages, workDir)
    se.run()
    return imageKey, se
class BatchPSFex(object):
    def __init__(self, imageLog, groupedImageKeys, catalogPathKey, psfKey,
            configs=None, checkImages=None, checkPlots=None,
            defaultsPath=None, xmlKey=None, workDir="psfex", nThreads=None):
        """Runs multiple PSFex instances over independent groups of image keys.
        
        :param imageLog: a ImageLog-compatible database instance.
        :param groupedImageKeys: a dictionary of groupName: sequence of image
            keys
        :param catalogPathKey: record field where the SE (Source Extractor)
            catalogs are found for each image
        :param configs: (optional) dictionary of PSFex settings. Keys and
            values are standard PSFex command line arguments.
        :param defaultsPath: path to where a PSFex configuration file can be
            found
        :param xmlKey: key to install XML. If `None`, then PSFex will
            not produce XML output.
        :param workDir: directory where PSFex outputs are saved.
        :param nThreads: number of processes to run if `debug` is `False`.
        """
        self.imageLog = imageLog
        self.groupedImageKeys = groupedImageKeys
        self.catalogPathKey = catalogPathKey
        self.psfKey = psfKey
        self.xmlKey = xmlKey
        self.configs = configs
        self.checkImages = checkImages
        self.checkPlots = checkPlots
        self.defaultsPath = defaultsPath
        self.workDir = workDir
        if nThreads is not None:
            self.nThreads = nThreads
        else:
            self.nThreads = multiprocessing.cpu_count()
    
    def run(self, debug=False):
        """Executes the multiprocessing PSFex run."""
        dbArgs = {"url": self.imageLog.url,
                  "port": self.imageLog.port,
                  "dbname": self.imageLog.dbname,
                  "cname": self.imageLog.cname}
        
        args = []
        for groupName, imageKeys in self.groupedImageKeys.iteritems():
            args.append((groupName, imageKeys, self.catalogPathKey,
                self.psfKey, self.configs, self.checkImages, self.checkPlots,
                self.defaultsPath, self.xmlKey, self.workDir, dbArgs))
        
        if debug:
            map(_run_batch_psfex, args)
        else:
            pool = multiprocessing.Pool(processes=self.nThreads)
            pool.map(_run_batch_psfex, args)
def _run_batch_psfex(args):
    """Worker function for executing PSFex from BatchPSFex.
    
    The path to the PSF is stored under `psfKey`."""
    groupName, imageKeys, catalogPathKey, psfKey, configs, checkImages, \
        checkPlots, defaultsPath, xmlKey, workDir, dbArgs = args
    dbArgs = dict(dbArgs)  # in case we run in debug mode
    dbname = dbArgs.pop('dbname')
    cname = dbArgs.pop('cname')
    imageLog = ImageLog(dbname, cname, **dbArgs)
    
    print "Running imageKeys:", imageKeys
    psfex = PSFex.from_db(imageLog, imageKeys, catalogPathKey, configs=configs,
        xmlKey=xmlKey, defaultsPath=None, workDir=workDir)
    if checkImages is not None:
        psfex.set_check_images(checkImages, "psfex",
                os.path.join(workDir, "checks"))
    if checkPlots is not None:
        psfex.set_check_plots(checkPlots, "psfex",
                os.path.join(workDir, "plots"), plotType="PSC")
    psfex.run()
    psfex.save_psf_paths(psfKey)
    if xmlKey is not None:
        psfex.save_xml_paths()
[docs]class PSFex(Astromatic):
    """Wrapper on the PSFex PSF-modelling software."""
    def __init__(self, catalogPaths, imageLog=None, imageKeys=None,
            defaultsPath=None, configs=None, xmlKey=None,
            workDir='psfex', groupName=None):
        super(PSFex, self).__init__()
        if type(catalogPaths) is str:
            self.catalogPaths = [catalogPaths]
        else:
            self.catalogPaths = catalogPaths
        self.imageLog = imageLog
        self.imageKeys = imageKeys
        self.configs = configs
        self.xmlKey = xmlKey
        self.workDir = workDir
        # allows the input list to be named different in batch mode
        self.groupName = groupName
        if self.groupName is None:
            self.groupName = self.imageKeys[0]
        
        self.checkDir = None
        self.checkList = None
        self.checkPaths = None
        
        self.plotDir = None
        self.plotList = None
        self.plotPaths = None
    
    @classmethod
[docs]    def from_db(cls, imageLog, imageKeys, catalogPathKey,
            configs=None, xmlKey=None, defaultsPath=None, workDir='psfex'):
        """Creates a PSFex run from an image log database.
        
        :param imageLog: a ImageLog-compatible database instance.
        :param imageKeys: image keys in the imageLog where SE catalogs will be
            gathered.
        :param catalogPathKey: record field where the SE (Source Extractor)
            catalogs are found for each image
        :param xmlKey: key to install XML. If `None`, then PSFex will
            not produce XML output.
        :param configs: (optional) dictionary of PSFex settings. Keys and
            values are standard PSFex command line arguments.
        :param defaultsPath: path to where a PSFex configuration file can be
            found
        :param workDir: directory where PSFex outputs are saved.
        """
        docs = imageLog.find({"_id": {"$in": imageKeys}},
                fields=[catalogPathKey])
        imageKeys = []
        catalogPaths = []
        for d in docs:
            imageKeys.append(d['_id'])
            catalogPaths.append(d[catalogPathKey])
        return cls(catalogPaths, imageLog=imageLog, imageKeys=imageKeys,
            defaultsPath=defaultsPath, configs=configs, xmlKey=xmlKey,
            workDir=workDir)
     
[docs]    def set_check_images(self, checkList, prefix, checkDir):
        """Sets which check images should be made, and where they should be
        saved. By default, all checkimages retain the base file name, plus
        and appropriate extension.
        
        :param checkList: sequence of check image types. May include:
        * CHI
        * PROTOTYPES
        * SAMPLES
        * RESIDUALS
        * SNAPSHOTS
        * MOFFAT
        * -MOFFAT
        * -SYMMETRICAL
        * BASIS
        :param prefix: filename prefix for all check images.
        :param checkDir: directory where the check images should be saved.
        """
        self.checkDir = checkDir
        if os.path.exists(checkDir) is False: os.makedirs(checkDir)
        self.checkList = checkList
        
        print prefix
        print self.checkDir
        checkPath = {"CHI": "chi", "PROTOTYPES": "proto", "SAMPLES": "samp",
            "RESIDUALS": "resi", "SNAPSHOTS": "snap", "MOFFAT": "moffat",
            "-MOFFAT": "-moffat", "-SYMMETRICAL": "-symm", "BASIS": "basis"}
        if self.checkList is not None:
            self.checkPaths = [os.path.join(self.checkDir,
                "%s_%s.fits" % (prefix, checkPath[c])) for c in self.checkList]
        else:
            self.checkPaths = None
        print self.checkPaths
     
[docs]    def set_check_plots(self, plotList, prefix, plotDir, plotType="PDF"):
        """Sets which check figures (plplot) should be made, and where they
        should be saved.
        
        :param checkList: sequence of check figure types. May include:
        * FWHM - map of PSF FWHM over field
        * ELLIPTICITY - map of PSF ellipticity over field
        * COUNTS - map of spatial density of point sources
        * COUNT_FRACTION - map of fraction of points accepted for
            PSF evaluation
        * CHI2 - map of average chi^2/d.o.f over field
        * MOFFAT_RESIDUALS - map of moffat residuals
        * ASYMMETRY - map of asymmetry indices
        """
        self.plotDir = plotDir
        if os.path.exists(plotDir) is False: os.makedirs(plotDir)
        self.plotList = plotList
        plotPath = {"FWHM": "fwhm", "ELLIPTICITY": "ellip", "COUNTS": 'counts',
            "COUNT_FRACTION": 'cfrac', "CHI2": "chi",
            "MOFFAT_RESIDUALS": 'resid', "ASYMMETRY": 'asym'}
        if self.plotList is not None:
            self.plotPaths = [os.path.join(self.plotDir,
                "%s_%s.fits" % (prefix, plotPath[c])) for c in self.plotList]
        else:
            self.plotPaths = None
        self.plotType = plotType
     
[docs]    def make_command(self):
        """Makes the source extractor command (for CL execution).
        Returns a string.
        """
        # Automatically make XML if we can install it
        self.xmlPath = os.path.join(self.workDir, self.groupName + ".xml")
        if self.xmlKey is None:
            self.configs['WRITE_XML'] = "N"
        else:
            self.configs['WRITE_XML'] = "Y"
            self.configs['XML_NAME'] = self.xmlPath
        # If more than 10 catalogs are being processed, a file list is written
        if len(self.catalogPaths) > 10:
            if self.groupName is not None:
                inputName = "%s_input_catalogs.txt" % self.groupName
            else:
                inputName = "input_catalogs.txt"
            inputListPath = os.path.join(self.workDir, inputName)
            if os.path.exists(inputListPath): os.remove(inputListPath)
            f = open(inputListPath, 'w')
            f.write("\n".join(self.catalogPaths) + "\n")
            f.close()
            command = "psfex @%s" % inputListPath
        elif len(self.catalogPaths) == 1:
            command = "psfex %s" % self.catalogPaths[0]
        else:
            command = "psfex %s" % ",".join(self.catalogPaths)
        
        # Add default parameters to the configs file. Writes a new defaults
        # if one is not found
        self.add_default_param_to_configs("psfex -d")
        
        # Add all check image info to the configs dictionary
        if self.checkList is not None:
            checkImageArgs = ",".join(self.checkList)
            checkPathArgs = ",".join(self.checkPaths)
            self.add_to_configs('CHECKIMAGE_TYPE', checkImageArgs)
            self.add_to_configs('CHECKIMAGE_NAME', checkPathArgs)
        
        # Add all check plot info to the configs dictionary
        if self.plotList is not None:
            plotArgs = ",".join(self.plotList)
            plotPathArgs = ",".join(self.plotPaths)
            self.add_to_configs("CHECKPLOT_TYPE", plotArgs)
            self.add_to_configs("CHECKPLOT_NAME", plotPathArgs)
            self.add_to_configs("CHECKPLOT_DEV", self.plotType)
        
        # Append all other configurations
        configCmd = self.make_config_command()
        if configCmd is not None:
            command = " ".join((command, configCmd))
        
        return command
     
[docs]    def check_path(self, checkType):
        """Returns the path to the specified type of check iamage."""
        i = self.checkList.index(checkType)
        if i >= 0:
            path = self.checkPaths[i]
            return path
        else:
            return None
     
[docs]    def psf_paths(self):
        """Get the paths to each image, in the same order as the input
        catalog paths.
        """
        psfPaths = [os.path.join(self.workDir,
            os.path.splitext(os.path.basename(path))[0] + ".psf")
            for path in self.catalogPaths]
        return psfPaths
     
[docs]    def save_psf_paths(self, psfKey):
        """Files the psf file paths into the image log under `psfKey` for
        all images.
        """
        for imageKey, catPath in zip(self.imageKeys, self.catalogPaths):
            psfPath = os.path.join(self.workDir,
                    os.path.splitext(os.path.basename(catPath))[0] + ".psf")
            self.imageLog.set(imageKey, psfKey, psfPath)
 
[docs]    def save_xml_paths(self):
        """Files the xml filepaths into image log under `xmlKey` for all
        images."""
        if self.xmlKey is None: return
        for imageKey in self.imageKeys:
            self.imageLog.set(imageKey, self.xmlKey, self.xmlPath)