def run(configobj):
    """ Primary Python interface for image registration code
        This task replaces 'tweakshifts'
    """
    print('TweakReg Version %s(%s) started at: %s \n'%(
                    __version__,__version_date__,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__)

    log.debug('')
    log.debug("==== TweakReg was invoked with the following parameters: ====")
    log.debug('')
    util.print_cfg(configobj, log.debug)

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

    # start interpretation of input parameters
    input_files = 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_files, 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_files)
        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):
        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('')
    print('Finding shifts for: ')
    for f in filenames:
        print('    {}'.format(f))
    print('')

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

    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('')
        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('')
        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}\nPerforming alignment in the projection plane defined by the "
          "WCS\nderived from '{1}'\n{0}\n".format('='*63, 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('')
            log.info("USER INPUT PARAMETERS for fitting source lists:")
            util.printParams(configobj['CATALOG FITTING PARAMETERS'], log=log)

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

            if shiftpars['shiftfile']:
                log.info('')
                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