# Source code for drizzlepac.tweakreg

    """ `TweakReg` - A replacement for IRAF-based `tweakshifts`

    :Authors: Warren Hack, Mihai Cara

    :License: `<http://www.stsci.edu/resources/software_hardware/pyraf/LICENSE>`_

    """
    from __future__ import absolute_import, division, print_function
    import os
    import sys
    import numpy as np
    from copy import copy

    from stsci.tools import parseinput, teal
    from stsci.tools import logutil, textutil
    from stsci.tools.cfgpars import DuplicateKeyError
    from stwcs import updatewcs

    from . import util

    # __version__ and __vdate__ are defined here, prior to the importing
    # of the modules below, so that those modules can use the values
    # from these variable definitions, allowing the values to be designated
    # in one location only.
    #
    # This is specifically NOT intended to match the package-wide version information.
    __version__ = '1.4.3'
    __vdate__ = '01-Jul-2016'

    from . import tweakutils
    from . import imgclasses
    from . import catalogs
    from . import imagefindpars
    from . import refimagefindpars

    __taskname__ = 'tweakreg' # unless someone comes up with anything better

    PSET_SECTION        = '_SOURCE FINDING PARS_'
    PSET_SECTION_REFIMG = '_REF IMAGE SOURCE FINDING PARS_'
    # !! Use pre and post underscores to hide this added section in TEAL, so that
    # TEAL allows its data to stay alongside the expected data during a call to
    # TweakReg().  All of this needs to be revisited.

    log = logutil.create_logger(__name__)

    def _managePsets(configobj, section_name, task_name, iparsobj=None, input_dict=None):
        """ Read in parameter values from PSET-like configobj tasks defined for
            source-finding algorithms, and any other PSET-like tasks under this task,
            and merge those values into the input configobj dictionary.
        """
        # Merge all configobj instances into a single object
        configobj[section_name] = {}

        # Load the default full set of configuration parameters for the PSET:
        iparsobj_cfg = teal.load(task_name)

        # Identify optional parameters in input_dicts that are from this
        # PSET and add it to iparsobj:
        if input_dict is not None:
            for key in list(input_dict.keys()):
                if key in iparsobj_cfg:
                    if iparsobj is not None and key in iparsobj:
                        raise DuplicateKeyError("Duplicate parameter '{:s}' "
                            "provided for task {:s}".format(key, task_name))
                    iparsobj_cfg[key] = input_dict[key]
                    del input_dict[key]

        if iparsobj is not None:
            iparsobj_cfg.update(iparsobj)

        del iparsobj_cfg['_task_name_']

        # merge these parameters into full set
        configobj[section_name].merge(iparsobj_cfg)

        # clean up configobj a little to make it easier for later...
    #   if '_RULES_' in configobj:
    #       del configobj['_RULES_']

    def edit_imagefindpars():
        """ Allows the user to edit the imagefindpars configObj in a TEAL GUI
            """
        teal.teal(imagefindpars.__taskname__, returnAs=None,
                  autoClose=True, loadOnly=False, canExecute=False)

    def edit_refimagefindpars():
        """ Allows the user to edit the refimagefindpars configObj in a TEAL GUI
            """
        teal.teal(refimagefindpars.__taskname__, returnAs=None,
                  autoClose=True, loadOnly=False, canExecute=False)

    @util.with_logging
    def run(configobj):
        """ Primary Python interface for image registration code
            This task replaces 'tweakshifts'
        """
        print('TweakReg Version %s(%s) started at: %s \n'%(
                        __version__,__vdate__,util._ptime()[0]))
        util.print_pkg_versions()

        # make sure 'updatewcs' is set to False when running from GUI or if missing
        # from configObj:
        if 'updatewcs' not in configobj:
            configobj['updatewcs'] = False

        # Check to see whether or not the imagefindpars parameters have
        # already been loaded, as done through the python interface.
        # Repeat for refimagefindpars
        if PSET_SECTION not in configobj:
            # Manage PSETs for source finding algorithms
            _managePsets(configobj, PSET_SECTION, imagefindpars.__taskname__)
        #print configobj[PSET_SECTION]
        if PSET_SECTION_REFIMG not in configobj:
            # Manage PSETs for source finding algorithms in reference image
            _managePsets(configobj, PSET_SECTION_REFIMG,
                         refimagefindpars.__taskname__)

        # print out user set input parameter values for running this task
        log.info("USER INPUT PARAMETERS common to all Processing Steps:")
        util.printParams(configobj, log=log)

        # start interpretation of input parameters
        input = configobj['input']
        # Start by interpreting the inputs
        use_catfile = True
        expand_refcat = configobj['expand_refcat']
        enforce_user_order = configobj['enforce_user_order']

        filenames,catnames = tweakutils.parse_input(
            input, sort_wildcards=not enforce_user_order
        )

        catdict = {}
        for indx,f in enumerate(filenames):
            if catnames is not None and len(catnames) > 0:
                catdict[f] = catnames[indx]
            else:
                catdict[f] = None

        if not filenames:
            print('No filenames matching input %r were found.' % input)
            raise IOError

        # Verify that files are writable (based on file permissions) so that
        #    they can be updated if either 'updatewcs' or 'updatehdr' have
        #    been turned on (2 cases which require updating the input files)
        if configobj['updatewcs'] or configobj['UPDATE HEADER']['updatehdr']:
            filenames = util.verifyFilePermissions(filenames)
            if filenames is None or len(filenames) == 0:
                raise IOError

        if configobj['UPDATE HEADER']['updatehdr']:
            wname = configobj['UPDATE HEADER']['wcsname']
            # verify that a unique WCSNAME has been specified by the user
            if not configobj['UPDATE HEADER']['reusename']:
                for fname in filenames:
                    uniq = util.verifyUniqueWcsname(fname,wname)
                    if not uniq:
                        errstr = 'WCSNAME "%s" already present in "%s".  '%(wname,fname)+\
                        'A unique value for the "wcsname" parameter needs to be ' + \
                        'specified. \n\nQuitting!'
                        print(textutil.textbox(errstr,width=60))
                        raise IOError

        if configobj['updatewcs']:
            print('\nRestoring WCS solutions to original state using updatewcs...\n')
            updatewcs.updatewcs(filenames)

        if catnames in [None,'',' ','INDEF'] or len(catnames) == 0:
            catfile_par = configobj['COORDINATE FILE DESCRIPTION']['catfile']
            # check to see whether the user specified input catalogs through other parameters
            if catfile_par not in [None,'',' ','INDEF']:
                # read in catalog file list provided by user
                catnames,catdict = tweakutils.parse_atfile_cat('@'+catfile_par)
            else:
                use_catfile = False

            if 'exclusions' in configobj and \
                configobj['exclusions'] not in [None,'',' ','INDEF']:
                if os.path.exists(configobj['exclusions']):
                    excl_files, excl_dict = tweakutils.parse_atfile_cat(
                        '@'+configobj['exclusions'])

                    # make sure the dictionary is well formed and that keys are base
                    # file names and that exclusion files have been expanded:
                    exclusion_files = []
                    exclusion_dict = {}
                    rootpath = os.path.abspath(
                        os.path.split(configobj['exclusions'])[0]
                    )

                    for f in excl_dict.keys():
                        print(f)
                        bf = os.path.basename(f)
                        exclusion_files.append(bf)
                        reglist = excl_dict[f]

                        if reglist is None:
                            exclusion_dict[bf] = None
                            continue

                        new_reglist = []
                        for regfile in reglist:
                            if regfile in [ None, 'None', '', ' ', 'INDEF' ]:
                                new_reglist.append(None)
                            else:
                                abs_regfile = os.path.normpath(
                                    os.path.join(rootpath, regfile)
                                )
                                new_reglist.append(abs_regfile)

                        exclusion_dict[bf] = new_reglist

                else:
                    raise IOError('Could not find specified exclusions file "{:s}"'
                          .format(configobj['exclusions']))

            else:
                exclusion_files = [None]*len(filenames)
                exclusion_dict = {}

                for f in filenames:
                    exclusion_dict[os.path.basename(f)] = None

        # Verify that we have the same number of catalog files as input images
        if catnames is not None and (len(catnames) > 0):
            rcat = configobj['REFERENCE CATALOG DESCRIPTION']['refcat']
            missed_files = []

            for f in filenames:
                if f not in catdict:
                    missed_files.append(f)
                if len(missed_files) > 0:
                    print('The input catalogs does not contain entries for the following images:')
                    print(missed_files)
                    raise IOError
        else:
            # setup array of None values as input to catalog parameter for Image class
            catnames = [None]*len(filenames)
            use_catfile = False

        # convert input images and any provided catalog file names into Image objects
        input_images = []
        # copy out only those parameters needed for Image class
        catfile_kwargs = tweakutils.get_configobj_root(configobj)
        # define default value for 'xyunits' assuming sources to be derived from image directly
        catfile_kwargs['xyunits'] = 'pixels' # initialized here, required by Image class
        del catfile_kwargs['exclusions']

        if use_catfile:
            # reset parameters based on parameter settings in this section
            catfile_kwargs.update(configobj['COORDINATE FILE DESCRIPTION'])
            for sort_par in imgclasses.sortKeys:
                catfile_kwargs['sort_'+sort_par] = catfile_kwargs[sort_par]
        # Update parameter set with 'SOURCE FINDING PARS' now
        catfile_kwargs.update(configobj[PSET_SECTION])
        uphdr_par = configobj['UPDATE HEADER']
        hdrlet_par = configobj['HEADERLET CREATION']
        objmatch_par = configobj['OBJECT MATCHING PARAMETERS']
        catfit_pars = configobj['CATALOG FITTING PARAMETERS']

        catfit_pars['minobj'] = objmatch_par['minobj']
        objmatch_par['residplot'] = catfit_pars['residplot']

        hdrlet_par.update(uphdr_par) # default hdrlet name
        catfile_kwargs['updatehdr'] = uphdr_par['updatehdr']

        shiftpars = configobj['OPTIONAL SHIFTFILE OUTPUT']

        # verify a valid hdrname was provided, if headerlet was set to True
        imgclasses.verify_hdrname(**hdrlet_par)

        print('\nFinding shifts for: ')
        for f in filenames:
            print('    {}'.format(f))

        log.info("USER INPUT PARAMETERS for finding sources for each input image:")
        util.printParams(catfile_kwargs, log=log)

        try:
            minsources = max(1, catfit_pars['minobj'])
            omitted_images = []
            all_input_images = []
            for imgnum in range(len(filenames)):
                # Create Image instances for all input images
                try:
                    regexcl = exclusion_dict[os.path.basename(filenames[imgnum])]
                except KeyError:
                    regexcl = None
                    pass

                img = imgclasses.Image(filenames[imgnum],
                                       input_catalogs=catdict[filenames[imgnum]],
                                       exclusions=regexcl,
                                       **catfile_kwargs)

                all_input_images.append(img)
                if img.num_sources < minsources:
                    warn_str = "Image '{}' will not be aligned " \
                               "since it contains fewer than {} sources." \
                               .format(img.name, minsources)
                    print('\nWARNING: {}\n'.format(warn_str))
                    log.warning(warn_str)
                    omitted_images.append(img)
                    continue
                input_images.append(img)

        except KeyboardInterrupt:
            for img in input_images:
                img.close()
            print('Quitting as a result of user request (Ctrl-C)...')
            return
        # create set of parameters to pass to RefImage class
        kwargs = tweakutils.get_configobj_root(configobj)
        # Determine a reference image or catalog and
        #    return the full list of RA/Dec positions
        # Determine what WCS needs to be used for reference tangent plane
        refcat_par = configobj['REFERENCE CATALOG DESCRIPTION']
        if refcat_par['refcat'] not in [None,'',' ','INDEF']: # User specified a catalog to use
            # Update kwargs with reference catalog parameters
            kwargs.update(refcat_par)

        # input_images list can be modified below.
        # So, make a copy of the original:
        input_images_orig_copy = copy(input_images)
        do_match_refimg = False

        # otherwise, extract the catalog from the first input image source list
        if configobj['refimage'] not in [None, '',' ','INDEF']: # User specified an image to use
            # A hack to allow different source finding parameters for
            # the reference image:
            ref_sourcefind_pars = \
                tweakutils.get_configobj_root(configobj[PSET_SECTION_REFIMG])
            ref_catfile_kwargs = catfile_kwargs.copy()
            ref_catfile_kwargs.update(ref_sourcefind_pars)
            ref_catfile_kwargs['updatehdr'] = False

            log.info("USER INPUT PARAMETERS for finding sources for "
                     "the reference image:")
            util.printParams(ref_catfile_kwargs, log=log)

            #refimg = imgclasses.Image(configobj['refimage'],**catfile_kwargs)
            # Check to see whether the user specified a separate catalog
            #    of reference source positions and replace default source list with it
            if refcat_par['refcat'] not in [None,'',' ','INDEF']: # User specified a catalog to use
                ref_source = refcat_par['refcat']
                cat_src = ref_source
                xycat = None
                cat_src_type = 'catalog'
            else:
                try:
                    regexcl = exclusion_dict[configobj['refimage']]
                except KeyError:
                    regexcl = None
                    pass

                refimg = imgclasses.Image(configobj['refimage'],
                                          exclusions=regexcl,
                                          **ref_catfile_kwargs)
                ref_source = refimg.all_radec
                cat_src = None
                xycat = refimg.xy_catalog
                cat_src_type = 'image'

            try:
                if 'use_sharp_round' in ref_catfile_kwargs:
                    kwargs['use_sharp_round'] = ref_catfile_kwargs['use_sharp_round']
                refimage = imgclasses.RefImage(configobj['refimage'],
                                ref_source, xycatalog=xycat,
                                cat_origin=cat_src, **kwargs)
                refwcs_fname = refimage.wcs.filename
                if cat_src is not None:
                    refimage.name = cat_src

            except KeyboardInterrupt:
                refimage.close()
                for img in input_images:
                    img.close()
                print('Quitting as a result of user request (Ctrl-C)...')
                return

            if len(input_images) < 1:
                warn_str = "Fewer than two images are available for alignment. " \
                           "Quitting..."
                print('\nWARNING: {}\n'.format(warn_str))
                log.warning(warn_str)
                for img in input_images:
                    img.close()
                return

            image = _max_overlap_image(refimage, input_images, expand_refcat,
                                       enforce_user_order)

        elif refcat_par['refcat'] not in [None,'',' ','INDEF']:
            # a reference catalog is provided but not the reference image/wcs
            if len(input_images) < 1:
                warn_str = "No images available for alignment. Quitting..."
                print('\nWARNING: {}\n'.format(warn_str))
                log.warning(warn_str)
                for img in input_images:
                    img.close()
                return

            if len(input_images) == 1:
                image = input_images.pop(0)
            else:
                image, image2 = _max_overlap_pair(input_images, expand_refcat,
                                                  enforce_user_order)
                input_images.insert(0, image2)

            # Workaround the defect described in ticket:
            # http://redink.stsci.edu/trac/ssb/stsci_python/ticket/1151
            refwcs = []
            for i in all_input_images:
                refwcs.extend(i.get_wcs())
            kwargs['ref_wcs_name'] = image.get_wcs()[0].filename

            # A hack to allow different source finding parameters for
            # the reference image:
            ref_sourcefind_pars = \
                tweakutils.get_configobj_root(configobj[PSET_SECTION_REFIMG])
            ref_catfile_kwargs = catfile_kwargs.copy()
            ref_catfile_kwargs.update(ref_sourcefind_pars)
            ref_catfile_kwargs['updatehdr'] = False

            log.info("USER INPUT PARAMETERS for finding sources for "
                     "the reference image (not used):")
            util.printParams(ref_catfile_kwargs, log=log)

            ref_source = refcat_par['refcat']
            cat_src = ref_source
            xycat = None

            try:
                if 'use_sharp_round' in ref_catfile_kwargs:
                    kwargs['use_sharp_round'] = ref_catfile_kwargs['use_sharp_round']
                kwargs['find_bounding_polygon'] = True
                refimage = imgclasses.RefImage(refwcs,
                                               ref_source, xycatalog=xycat,
                                               cat_origin=cat_src, **kwargs)
                refwcs_fname = refimage.wcs.filename
                refimage.name = cat_src
                cat_src_type = 'catalog'
            except KeyboardInterrupt:
                refimage.close()
                for img in input_images:
                    img.close()
                print('Quitting as a result of user request (Ctrl-C)...')
                return

        else:
            if len(input_images) < 2:
                warn_str = "Fewer than two images available for alignment. " \
                    "Quitting..."
                print('\nWARNING: {}\n'.format(warn_str))
                log.warning(warn_str)
                for img in input_images:
                    img.close()
                return

            kwargs['use_sharp_round'] = catfile_kwargs['use_sharp_round']

            cat_src = None

            refimg, image = _max_overlap_pair(input_images, expand_refcat,
                                              enforce_user_order)

            refwcs = []
            #refwcs.extend(refimg.get_wcs())
            #refwcs.extend(image.get_wcs())
            #for i in input_images:
                #refwcs.extend(i.get_wcs())
            # Workaround the defect described in ticket:
            # http://redink.stsci.edu/trac/ssb/stsci_python/ticket/1151
            for i in all_input_images:
                refwcs.extend(i.get_wcs())
            kwargs['ref_wcs_name'] = refimg.get_wcs()[0].filename

            try:
                ref_source = refimg.all_radec
                refimage = imgclasses.RefImage(refwcs, ref_source,
                                            xycatalog=refimg.xy_catalog, **kwargs)
                refwcs_fname = refimg.name
                cat_src_type = 'image'
            except KeyboardInterrupt:
                refimage.close()
                for img in input_images:
                    img.close()
                print('Quitting as a result of user request (Ctrl-C)...')
                return

            omitted_images.insert(0, refimg) # refimage *must* be first
            do_match_refimg = True

        print('\n{0}\nAligning all input images to WCS defined by {1}\n{0}\n'
              .format('='*20, refwcs_fname))

        if refimage.outxy is not None:
            if cat_src is None:
                cat_src = refimage.name

            try:
                log.info("USER INPUT PARAMETERS for matching sources:")
                util.printParams(objmatch_par, log=log)

                log.info("USER INPUT PARAMETERS for fitting source lists:")
                util.printParams(configobj['CATALOG FITTING PARAMETERS'], log=log)

                if hdrlet_par['headerlet']:
                    log.info("USER INPUT PARAMETERS for creating headerlets:")
                    util.printParams(hdrlet_par, log=log)

                if shiftpars['shiftfile']:
                    log.info("USER INPUT PARAMETERS for creating a shiftfile:")
                    util.printParams(shiftpars, log=log)

                # Now, apply reference WCS to each image's sky positions as well as the
                # reference catalog sky positions,
                # then perform the fit between the reference catalog positions and
                #    each image's positions
                quit_immediately = False
                xycat_lines = ''
                xycat_filename = None

                for img in input_images_orig_copy:
                    if xycat_filename is None:
                        xycat_filename = img.rootname+'_xy_catfile.list'
                    # Keep a record of all the generated input_xy catalogs
                    xycat_lines += img.get_xy_catnames()

                retry_flags = len(input_images)*[0]
                objmatch_par['cat_src_type'] = cat_src_type
                while image is not None:
                    print ('\n'+'='*20)
                    print ('Performing fit for: {}\n'.format(image.name))
                    image.match(refimage, quiet_identity=False, **objmatch_par)
                    assert(len(retry_flags) == len(input_images))

                    if not image.goodmatch:
                        # we will try to match it again once reference catalog
                        # has expanded with new sources:
                        #if expand_refcat:
                        input_images.append(image)
                        retry_flags.append(1)
                        if len(retry_flags) > 0 and retry_flags[0] == 0:
                            retry_flags.pop(0)
                            image = input_images.pop(0)
                            # try to match next image in the list
                            continue
                        else:
                            # no new sources have been added to the reference
                            # catalog and we have already tried to match
                            # images to the existing reference catalog

                            #input_images.append(image) # <- add it back for later reporting
                            #retry_flags.append(1)
                            break

                    image.performFit(**catfit_pars)
                    if image.quit_immediately:
                        quit_immediately = True
                        image.close()
                        break

                    # add unmatched sources to the reference catalog
                    # (to expand it):
                    if expand_refcat:
                        refimage.append_not_matched_sources(image)

                    image.updateHeader(wcsname=uphdr_par['wcsname'],
                                        reusename=uphdr_par['reusename'])
                    if hdrlet_par['headerlet']:
                        image.writeHeaderlet(**hdrlet_par)
                    if configobj['clean']:
                        image.clean()
                    image.close()

                    if refimage.dirty and len(input_images) > 0:
                        # The reference catalog has been updated with new sources.
                        # Clear retry flags and get next image:
                        image = _max_overlap_image(
                            refimage, input_images, expand_refcat,
                            enforce_user_order
                        )
                        retry_flags = len(input_images)*[0]
                        refimage.clear_dirty_flag()
                    elif len(input_images) > 0 and retry_flags[0] == 0:
                        retry_flags.pop(0)
                        image = input_images.pop(0)
                    else:
                        break

                assert(len(retry_flags) == len(input_images))

                if not quit_immediately:
                    # process images that have not been matched in order to
                    # update their headers:
                    si = 0
                    if do_match_refimg:
                        image = omitted_images[0]
                        image.match(refimage, quiet_identity=True, **objmatch_par)
                        si = 1

                    # process omitted (from start) images separately:
                    for image in omitted_images[si:]:
                        image.match(refimage, quiet_identity=False, **objmatch_par)

                    # add to the list of omitted images, images that could not
                    # be matched:
                    omitted_images.extend(input_images)

                    if len(input_images) > 0:
                        print("\nUnable to match the following images:")
                        print("-------------------------------------")
                        for image in input_images:
                            print(image.name)
                        print("")

                    # update headers:
                    for image in omitted_images:
                        image.performFit(**catfit_pars)
                        if image.quit_immediately:
                            quit_immediately = True
                            image.close()
                            break
                        image.updateHeader(wcsname=uphdr_par['wcsname'],
                                        reusename=uphdr_par['reusename'])
                        if hdrlet_par['headerlet']:
                            image.writeHeaderlet(**hdrlet_par)
                        if configobj['clean']:
                            image.clean()
                        image.close()

                    if configobj['writecat'] and not configobj['clean']:
                        # Write out catalog file recording input XY catalogs used
                        # This file will be suitable for use as input to 'tweakreg'
                        # as the 'catfile' parameter
                        if os.path.exists(xycat_filename):
                            os.remove(xycat_filename)
                        f = open(xycat_filename, mode='w')
                        f.writelines(xycat_lines)
                        f.close()

                        if expand_refcat:
                            base_reg_name = os.path.splitext(
                                os.path.basename(cat_src))[0]
                            refimage.write_skycatalog(
                                'cumulative_sky_refcat_{:s}.coo' \
                                .format(base_reg_name),
                                show_flux=True, show_id=True
                            )

                    # write out shiftfile (if specified)
                    if shiftpars['shiftfile']:
                        tweakutils.write_shiftfile(input_images_orig_copy,
                                                   shiftpars['outshifts'],
                                                   outwcs=shiftpars['outwcs'])

            except KeyboardInterrupt:
                refimage.close()
                for img in input_images_orig_copy:
                    img.close()
                    del img
                print ('Quitting as a result of user request (Ctrl-C)...')
                return
        else:
            print ('No valid sources in reference frame. Quitting...')
            return

    def _overlap_matrix(images):
        nimg = len(images)
        m = np.zeros((nimg,nimg), dtype=np.float)
        for i in range(nimg):
            for j in range(i+1,nimg):
                p = images[i].skyline.intersection(images[j].skyline)
                area = np.fabs(p.area())
                m[j,i] = area
                m[i,j] = area
        return m

    def _max_overlap_pair(images, expand_refcat, enforce_user_order):
        assert(len(images) > 1)
        if len(images) == 2 or not expand_refcat or enforce_user_order:
            # for the special case when only two images are provided
            # return (refimage, image) in the same order as provided in 'images'.
            # Also, when ref. catalog is static - revert to old tweakreg behavior
            im1 = images.pop(0) # reference image
            im2 = images.pop(0)
            return (im1, im2)

        m = _overlap_matrix(images)
        imgs = [f.name for f in images]
        n = m.shape[0]
        index = m.argmax()
        i = index / n
        j = index % n
        si = np.sum(m[i])
        sj = np.sum(m[:,j])

        if si < sj:
            c = j
            j = i
            i = c

        if i < j:
            j -= 1

        im1 = images.pop(i) # reference image
        im2 = images.pop(j)

        # Sort the remaining of the input list of images by overlap area
        # with the reference image (in decreasing order):
        row = m[i]
        row = np.delete(row, i)
        row = np.delete(row, j)
        sorting_indices = np.argsort(row)[::-1]
        images_arr = np.asarray(images)[sorting_indices]
        while len(images) > 0:
            del images[0]
        for k in range(images_arr.shape[0]):
            images.append(images_arr[k])

        return (im1, im2)

    def _max_overlap_image(refimage, images, expand_refcat, enforce_user_order):
        nimg = len(images)
        assert(nimg > 0)
        if not expand_refcat or enforce_user_order:
            # revert to old tweakreg behavior
            return images.pop(0)

        area = np.zeros(nimg, dtype=np.float)
        for i in range(nimg):
            area[i] = np.fabs(
                refimage.skyline.intersection(images[i].skyline).area()
            )

        # Sort the remaining of the input list of images by overlap area
        # with the reference image (in decreasing order):
        sorting_indices = np.argsort(area)[::-1]
        images_arr = np.asarray(images)[sorting_indices]
        while len(images) > 0:
            del images[0]
        for k in range(images_arr.shape[0]):
            images.append(images_arr[k])

        return images.pop(0)

    #
    # Primary interface for running this task from Python
    #
    def TweakReg(files=None, editpars=False, configobj=None, imagefindcfg=None,
                 refimagefindcfg=None, **input_dict):
        """
        """
        # support input of filenames from command-line without a parameter name
        # then copy this into input_dict for merging with TEAL ConfigObj parameters
        if files is None and configobj is None:
            raise TypeError('TweakReg() needs either "files" or "configobj" arg')

        if files and not util.is_blank(files):
            if input_dict is None:
                input_dict = {}
            input_dict['input'] = files

        # Get default or user-specified configobj for primary task
        if isinstance(configobj, str):
            if not os.path.exists(configobj):
                print('Cannot find .cfg file: '+configobj)
                return
            configobj = teal.load(configobj, strict=False)

        if configobj is None:
            configobj = teal.load(__taskname__)

        if 'updatewcs' in input_dict: # user trying to explicitly turn on updatewcs
            configobj['updatewcs'] = input_dict['updatewcs']
            del input_dict['updatewcs']

        # Merge PSET configobj with full task configobj
        _managePsets(configobj, PSET_SECTION, imagefindpars.__taskname__,
                     iparsobj=imagefindcfg, input_dict=input_dict)
        _managePsets(configobj, PSET_SECTION_REFIMG,
                     refimagefindpars.__taskname__, iparsobj=refimagefindcfg)
        # !! NOTE - the above line needs to be done so that getDefaultConfigObj()
        # can merge in input_dict, however the TEAL GUI is not going to understand
        # the extra section, (or use it), so work needs to be done here - some
        # more thinking about how to accomplish what we want to with PSETS.
        # !! For now, warn them that imagefindpars args will be ignored in GUI.
        if editpars and input_dict:
            idkeys = input_dict.keys()
            for i in idkeys:
                if i in configobj[PSET_SECTION]:
                    print('WARNING: ignoring imagefindpars setting "'+i+
                         '='+str(input_dict[i])+'", for now please enter directly into TEAL.')
                    input_dict.pop(i)
            del configobj[PSET_SECTION] # force run() to pull it again after GUI use
            del configobj[PSET_SECTION_REFIMG] # force run() to pull it again after GUI use

        # If called from interactive user-interface, configObj will not be
        # defined yet, so get defaults using EPAR/TEAL.
        #
        # Also insure that the input_dict (user-specified values) are folded in
        # with a fully populated configObj instance.
        try:
            configObj = util.getDefaultConfigObj(__taskname__, configobj,
                                                input_dict,
                                                loadOnly=(not editpars))
        except ValueError:
            print("Problem with input parameters. Quitting...")
            return

        if configObj is None:
            return
        # If 'editpars' was set to True, util.getDefaultConfigObj() will have
        # already called 'run()'.
        if not editpars:
            # Pass full set of parameters on to the task
            run(configObj)

    def help(file=None):
        """
        Print out syntax help for running astrodrizzle

        Parameters
        ----------
        file : str (Default = None)
            If given, write out help to the filename specified by this parameter
            Any previously existing file with this name will be deleted before
            writing out the help.

        """
        helpstr = getHelpAsString(docstring=True, show_ver = True)
        if file is None:
            print(helpstr)
        else:
            if os.path.exists(file): os.remove(file)
            f = open(file, mode = 'w')
            f.write(helpstr)
            f.close()

    def getHelpAsString(docstring = False, show_ver = True):
        """
        return useful help from a file in the script directory called
        __taskname__.help

        """
        install_dir = os.path.dirname(__file__)
        taskname = util.base_taskname(__taskname__, '')
        htmlfile = os.path.join(install_dir, 'htmlhelp', taskname + '.html')
        helpfile = os.path.join(install_dir, taskname + '.help')

        if docstring or (not docstring and not os.path.exists(htmlfile)):
            if show_ver:
                helpString = os.linesep + \
                    ' '.join([__taskname__, 'Version', __version__,
                    ' updated on ', __vdate__]) + 2*os.linesep
            else:
                helpString = ''
            if os.path.exists(helpfile):
                helpString += teal.getHelpFileAsString(taskname, __file__)
            else:
                if __doc__ is not None:
                    helpString += __doc__ + os.linesep
        else:
            helpString = 'file://' + htmlfile

        return helpString

    TweakReg.__doc__ = getHelpAsString(docstring = True, show_ver = False)