Esempio n. 1
0
def verifyUniqueWcsname(fname, wcsname, include_primary=True):
    """
    Report whether or not the specified WCSNAME already exists in the file
    """
    numsci, extname = count_sci_extensions(fname)
    wnames = altwcs.wcsnames(fname, ext=(extname, 1), include_primary=include_primary)
    return wcsname.upper() not in [v.upper() for v in wnames.values()]
Esempio n. 2
0
def verifyUniqueWcsname(fname, wcsname):
    """
    Report whether or not the specified WCSNAME already exists in the file
    """
    uniq = True
    numsci, extname = count_sci_extensions(fname)
    wnames = altwcs.wcsnames(fname, ext=(extname, 1))

    if wcsname in wnames.values():
        uniq = False

    return uniq
Esempio n. 3
0
def verifyUniqueWcsname(fname,wcsname):
    """
    Report whether or not the specified WCSNAME already exists in the file
    """
    uniq = True
    numsci,extname = count_sci_extensions(fname)
    wnames = altwcs.wcsnames(fname,ext=(extname,1))

    if wcsname in wnames.values():
        uniq = False

    return uniq
Esempio n. 4
0
def create_unique_wcsname(fimg, extnum, wcsname):
    """
    This function evaluates whether the specified wcsname value has
    already been used in this image.  If so, it automatically modifies
    the name with a simple version ID using wcsname_NNN format.

    Parameters
    ----------
    fimg : obj
        PyFITS object of image with WCS information to be updated

    extnum : int
        Index of extension with WCS information to be updated

    wcsname : str
        Value of WCSNAME specified by user for labelling the new WCS

    Returns
    -------
    uniqname : str
        Unique WCSNAME value

    """
    wnames = list(altwcs.wcsnames(fimg, ext=extnum).values())
    if wcsname not in wnames:
        uniqname = wcsname
    else:
        # setup pattern to match
        rpatt = re.compile(wcsname + '_\d')
        index = 0
        for wname in wnames:
            rmatch = rpatt.match(wname)
            if rmatch:
                # get index
                n = int(wname[wname.rfind('_') + 1:])
                if n > index: index = 1
        index += 1  # for use with new name
        uniqname = "%s_%d" % (wcsname, index)
    return uniqname
Esempio n. 5
0
def _wcs_key_name(wcs_key, wcs_name, fi, ext, default_key, param_name):
    wnames = altwcs.wcsnames(fi.image.hdu, ext=ext)
    if wcs_key is None:
        if wcs_name is None:
            wcs_key = sorted(wnames)[default_key]
            wcs_name = wnames[wcs_key]
            # report our guess:
            print(f"Using WCS with WCSNAME '{wcs_name}' (WCS key '{wcs_key}') "
                  f"for '{param_name}'")
        else:
            # find associated wcs_key
            wcs_name_u = wcs_name.upper()
            for k, name in wnames.items():
                if name.upper() == wcs_name_u:
                    wcs_key = k
                    break
            else:
                fi.release_all_images()
                raise KeyError(f"WCS with wcsname '{wcs_name}' not found.")
    else:
        wcs_name = wnames[wcs_key]

    return wcs_key, wcs_name
Esempio n. 6
0
def update_wcscorr(dest, source=None, extname='SCI', wcs_id=None, active=True):
    """
    Update WCSCORR table with a new row or rows for this extension header. It
    copies the current set of WCS keywords as a new row of the table based on
    keyed WCSs as per Paper I Multiple WCS standard).

    Parameters
    ----------
    dest : HDUList
        The HDU list whose WCSCORR table should be appended to (the WCSCORR HDU
        must already exist)
    source : HDUList, optional
        The HDU list containing the extension from which to extract the WCS
        keywords to add to the WCSCORR table.  If None, the dest is also used
        as the source.
    extname : str, optional
        The extension name from which to take new WCS keywords.  If there are
        multiple extensions with that name, rows are added for each extension
        version.
    wcs_id : str, optional
        The name of the WCS to add, as in the WCSNAMEa keyword.  If
        unspecified, all the WCSs in the specified extensions are added.
    active: bool, optional
        When True, indicates that the update should reflect an update of the
        active WCS information, not just appending the WCS to the file as a
        headerlet
    """
    if not isinstance(dest, fits.HDUList):
        dest = fits.open(dest,mode='update')
    fname = dest.filename()

    if source is None:
        source = dest

    if extname == 'PRIMARY':
        return

    numext = fileutil.countExtn(source, extname)
    if numext == 0:
        raise ValueError('No %s extensions found in the source HDU list.'
                        % extname)
    # Initialize the WCSCORR table extension in dest if not already present
    init_wcscorr(dest)
    try:
        dest.index_of('WCSCORR')
    except KeyError:
        return

    # check to see whether or not this is an up-to-date table
    # replace with newly initialized table with current format
    old_table = dest['WCSCORR']
    wcscorr_cols = ['WCS_ID','EXTVER', 'SIPNAME',
                    'HDRNAME', 'NPOLNAME', 'D2IMNAME']

    for colname in wcscorr_cols:
        if colname not in old_table.data.columns.names:
            print("WARNING:    Replacing outdated WCSCORR table...")
            outdated_table = old_table.copy()
            del dest['WCSCORR']
            init_wcscorr(dest)
            old_table = dest['WCSCORR']
            break

    # Current implementation assumes the same WCS keywords are in each
    # extension version; if this should not be assumed then this can be
    # modified...
    wcs_keys = altwcs.wcskeys(source[(extname, 1)].header)
    wcs_keys = [kk for kk in wcs_keys if kk]
    if ' ' not in wcs_keys: wcs_keys.append(' ') # Insure that primary WCS gets used
    # apply logic for only updating WCSCORR table with specified keywords
    # corresponding to the WCS with WCSNAME=wcs_id
    if wcs_id is not None:
        wnames = altwcs.wcsnames(source[(extname, 1)].header)
        wkeys = []
        for letter in wnames:
            if wnames[letter] == wcs_id:
                wkeys.append(letter)
        if len(wkeys) > 1 and ' ' in wkeys:
            wkeys.remove(' ')
        wcs_keys = wkeys
    wcshdr = stwcs.wcsutil.HSTWCS(source, ext=(extname, 1)).wcs2header()
    wcs_keywords = list(wcshdr.keys())

    if 'O' in wcs_keys:
        wcs_keys.remove('O') # 'O' is reserved for original OPUS WCS

    # create new table for hdr and populate it with the newly updated values
    new_table = create_wcscorr(descrip=True,numrows=0, padding=len(wcs_keys)*numext)
    prihdr = source[0].header

    # Get headerlet related keywords here
    sipname, idctab = utils.build_sipname(source, fname, "None")
    npolname, npolfile = utils.build_npolname(source, None)
    d2imname, d2imfile = utils.build_d2imname(source, None)
    if 'hdrname' in prihdr:
        hdrname = prihdr['hdrname']
    else:
        hdrname = ''

    idx = -1
    for wcs_key in wcs_keys:
        for extver in range(1, numext + 1):
            extn = (extname, extver)
            if 'SIPWCS' in extname and not active:
                tab_extver = 0 # Since it has not been added to the SCI header yet
            else:
                tab_extver = extver
            hdr = source[extn].header
            if 'WCSNAME'+wcs_key in hdr:
                wcsname = hdr['WCSNAME' + wcs_key]
            else:
                wcsname = utils.build_default_wcsname(hdr['idctab'])

            selection = {'WCS_ID': wcsname, 'EXTVER': tab_extver,
                         'SIPNAME':sipname, 'HDRNAME': hdrname,
                         'NPOLNAME': npolname, 'D2IMNAME':d2imname
                         }

            # Ensure that an entry for this WCS is not already in the dest
            # table; if so just skip it
            rowind = find_wcscorr_row(old_table.data, selection)
            if np.any(rowind):
                continue

            idx += 1

            wcs = stwcs.wcsutil.HSTWCS(source, ext=extn, wcskey=wcs_key)
            wcshdr = wcs.wcs2header()

            # Update selection column values
            for key, val in selection.items():
                if key in new_table.data.names:
                    new_table.data.field(key)[idx] = val

            for key in wcs_keywords:
                if key in new_table.data.names:
                    new_table.data.field(key)[idx] = wcshdr[key + wcs_key]

            for key in DEFAULT_PRI_KEYS:
                if key in new_table.data.names and key in prihdr:
                    new_table.data.field(key)[idx] = prihdr[key]
            # Now look for additional, non-WCS-keyword table column data
            for key in COL_FITSKW_DICT:
                fitkw = COL_FITSKW_DICT[key]
                # Interpret any 'pri.hdrname' or
                # 'sci.crpix1' formatted keyword names
                if '.' in fitkw:
                    srchdr,fitkw = fitkw.split('.')
                    if 'pri' in srchdr.lower(): srchdr = prihdr
                    else: srchdr = source[extn].header
                else:
                    srchdr = source[extn].header

                if fitkw+wcs_key in srchdr:
                    new_table.data.field(key)[idx] = srchdr[fitkw+wcs_key]


    # If idx was never incremented, no rows were added, so there's nothing else
    # to do...
    if idx < 0:
        return

    # Now, we need to merge this into the existing table
    rowind = find_wcscorr_row(old_table.data, {'wcs_id':['','0.0']})
    old_nrows = np.where(rowind)[0][0]
    new_nrows = new_table.data.shape[0]

    # check to see if there is room for the new row
    if (old_nrows + new_nrows) > old_table.data.shape[0]-1:
        pad_rows = 2 * new_nrows
        # if not, create a new table with 'pad_rows' new empty rows
        upd_table = fits.new_table(old_table.columns,header=old_table.header,
                                     nrows=old_table.data.shape[0]+pad_rows)
    else:
        upd_table = old_table
        pad_rows = 0
    # Now, add
    for name in old_table.columns.names:
        if name in new_table.data.names:
            # reset the default values to ones specific to the row definitions
            for i in range(pad_rows):
                upd_table.data.field(name)[old_nrows+i] = old_table.data.field(name)[-1]
            # Now populate with values from new table
            upd_table.data.field(name)[old_nrows:old_nrows + new_nrows] = \
                    new_table.data.field(name)
    upd_table.header['TROWS'] = old_nrows + new_nrows

    # replace old extension with newly updated table extension
    dest['WCSCORR'] = upd_table
Esempio n. 7
0
def update_wcscorr(dest, source=None, extname='SCI', wcs_id=None, active=True):
    """
    Update WCSCORR table with a new row or rows for this extension header. It
    copies the current set of WCS keywords as a new row of the table based on
    keyed WCSs as per Paper I Multiple WCS standard).

    Parameters
    ----------
    dest : HDUList
        The HDU list whose WCSCORR table should be appended to (the WCSCORR HDU
        must already exist)
    source : HDUList, optional
        The HDU list containing the extension from which to extract the WCS
        keywords to add to the WCSCORR table.  If None, the dest is also used
        as the source.
    extname : str, optional
        The extension name from which to take new WCS keywords.  If there are
        multiple extensions with that name, rows are added for each extension
        version.
    wcs_id : str, optional
        The name of the WCS to add, as in the WCSNAMEa keyword.  If
        unspecified, all the WCSs in the specified extensions are added.
    active: bool, optional
        When True, indicates that the update should reflect an update of the
        active WCS information, not just appending the WCS to the file as a
        headerlet
    """
    if not isinstance(dest, fits.HDUList):
        dest = fits.open(dest, mode='update')
    fname = dest.filename()

    if source is None:
        source = dest

    if extname == 'PRIMARY':
        return

    numext = fileutil.countExtn(source, extname)
    if numext == 0:
        raise ValueError('No %s extensions found in the source HDU list.' %
                         extname)
    # Initialize the WCSCORR table extension in dest if not already present
    init_wcscorr(dest)
    try:
        dest.index_of('WCSCORR')
    except KeyError:
        return

    # check to see whether or not this is an up-to-date table
    # replace with newly initialized table with current format
    old_table = dest['WCSCORR']
    wcscorr_cols = [
        'WCS_ID', 'EXTVER', 'SIPNAME', 'HDRNAME', 'NPOLNAME', 'D2IMNAME'
    ]

    for colname in wcscorr_cols:
        if colname not in old_table.data.columns.names:
            print("WARNING:    Replacing outdated WCSCORR table...")
            outdated_table = old_table.copy()
            del dest['WCSCORR']
            init_wcscorr(dest)
            old_table = dest['WCSCORR']
            break

    # Current implementation assumes the same WCS keywords are in each
    # extension version; if this should not be assumed then this can be
    # modified...
    wcs_keys = altwcs.wcskeys(source[(extname, 1)].header)
    wcs_keys = [kk for kk in wcs_keys if kk]
    if ' ' not in wcs_keys:
        wcs_keys.append(' ')  # Insure that primary WCS gets used
    # apply logic for only updating WCSCORR table with specified keywords
    # corresponding to the WCS with WCSNAME=wcs_id
    if wcs_id is not None:
        wnames = altwcs.wcsnames(source[(extname, 1)].header)
        wkeys = []
        for letter in wnames:
            if wnames[letter] == wcs_id:
                wkeys.append(letter)
        if len(wkeys) > 1 and ' ' in wkeys:
            wkeys.remove(' ')
        wcs_keys = wkeys
    wcshdr = stwcs.wcsutil.HSTWCS(source, ext=(extname, 1)).wcs2header()
    wcs_keywords = list(wcshdr.keys())

    if 'O' in wcs_keys:
        wcs_keys.remove('O')  # 'O' is reserved for original OPUS WCS

    # create new table for hdr and populate it with the newly updated values
    new_table = create_wcscorr(descrip=True,
                               numrows=0,
                               padding=len(wcs_keys) * numext)
    prihdr = source[0].header

    # Get headerlet related keywords here
    sipname, idctab = utils.build_sipname(source, fname, "None")
    npolname, npolfile = utils.build_npolname(source, None)
    d2imname, d2imfile = utils.build_d2imname(source, None)
    if 'hdrname' in prihdr:
        hdrname = prihdr['hdrname']
    else:
        hdrname = ''

    idx = -1
    for wcs_key in wcs_keys:
        for extver in range(1, numext + 1):
            extn = (extname, extver)
            if 'SIPWCS' in extname and not active:
                tab_extver = 0  # Since it has not been added to the SCI header yet
            else:
                tab_extver = extver
            hdr = source[extn].header
            if 'WCSNAME' + wcs_key in hdr:
                wcsname = hdr['WCSNAME' + wcs_key]
            else:
                wcsname = utils.build_default_wcsname(hdr['idctab'])

            selection = {
                'WCS_ID': wcsname,
                'EXTVER': tab_extver,
                'SIPNAME': sipname,
                'HDRNAME': hdrname,
                'NPOLNAME': npolname,
                'D2IMNAME': d2imname
            }

            # Ensure that an entry for this WCS is not already in the dest
            # table; if so just skip it
            rowind = find_wcscorr_row(old_table.data, selection)
            if np.any(rowind):
                continue

            idx += 1

            wcs = stwcs.wcsutil.HSTWCS(source, ext=extn, wcskey=wcs_key)
            wcshdr = wcs.wcs2header()

            # Update selection column values
            for key, val in selection.items():
                if key in new_table.data.names:
                    new_table.data.field(key)[idx] = val

            for key in wcs_keywords:
                if key in new_table.data.names:
                    new_table.data.field(key)[idx] = wcshdr[key + wcs_key]

            for key in DEFAULT_PRI_KEYS:
                if key in new_table.data.names and key in prihdr:
                    new_table.data.field(key)[idx] = prihdr[key]
            # Now look for additional, non-WCS-keyword table column data
            for key in COL_FITSKW_DICT:
                fitkw = COL_FITSKW_DICT[key]
                # Interpret any 'pri.hdrname' or
                # 'sci.crpix1' formatted keyword names
                if '.' in fitkw:
                    srchdr, fitkw = fitkw.split('.')
                    if 'pri' in srchdr.lower(): srchdr = prihdr
                    else: srchdr = source[extn].header
                else:
                    srchdr = source[extn].header

                if fitkw + wcs_key in srchdr:
                    new_table.data.field(key)[idx] = srchdr[fitkw + wcs_key]

    # If idx was never incremented, no rows were added, so there's nothing else
    # to do...
    if idx < 0:
        return

    # Now, we need to merge this into the existing table
    rowind = find_wcscorr_row(old_table.data, {'wcs_id': ['', '0.0']})
    old_nrows = np.where(rowind)[0][0]
    new_nrows = new_table.data.shape[0]

    # check to see if there is room for the new row
    if (old_nrows + new_nrows) > old_table.data.shape[0] - 1:
        pad_rows = 2 * new_nrows
        # if not, create a new table with 'pad_rows' new empty rows
        upd_table = fits.new_table(old_table.columns,
                                   header=old_table.header,
                                   nrows=old_table.data.shape[0] + pad_rows)
    else:
        upd_table = old_table
        pad_rows = 0
    # Now, add
    for name in old_table.columns.names:
        if name in new_table.data.names:
            # reset the default values to ones specific to the row definitions
            for i in range(pad_rows):
                upd_table.data.field(name)[old_nrows +
                                           i] = old_table.data.field(name)[-1]
            # Now populate with values from new table
            upd_table.data.field(name)[old_nrows:old_nrows + new_nrows] = \
                    new_table.data.field(name)
    upd_table.header['TROWS'] = old_nrows + new_nrows

    # replace old extension with newly updated table extension
    dest['WCSCORR'] = upd_table
Esempio n. 8
0
def apply_tweak(drz_file,
                orig_wcs_name,
                output_wcs_name=None,
                input_files=None,
                default_extname='SCI',
                **kwargs):
    """
    Apply WCS solution recorded in drizzled file to distorted input images
    (``_flt.fits`` files) used to create the drizzled file.

    It is assumed that if input images given by ``input_files`` are drizzled
    together, they would produce the drizzled image given by ``drz_file`` image
    and with the "original" primary WCS. It is also assumed that drizzled image
    was aligned using ``tweakreg`` either to another image or to an external
    reference catalog. We will refer to the primary WCS in the drizzled image
    _before_ ``tweakreg`` was run as the "original" WCS and the WCS _after_
    ``tweakreg`` was run as "tweaked" WCS.

    By comparing both "original" and "tweaked" WCS, ``apply_wcs`` computes
    the correction that was applied by ``tweakreg`` to the "original" WCS
    and converts this correction in the drizzled image frame into a correction
    in the input image's (``input_files``) frame that will be applied to the
    primary WCS of input images. If updated input images are now resampled
    again, they would produce an image very close to ``drz_file`` but with
    a primary WCS very similar to the "tweaked" WCS instead of the "original"
    WCS.

    Parameters
    ----------
    drz_file : str
        File name of the drizzled image that contains both the "original" and
        "tweaked" WCS. Even though wildcards are allowed in the file name,
        their expansion must resolve to a single image file. By default,
        ``apply_tweak`` looks for the first image-like HDU in the drizzled
        image. To specify a particular extension from which to load WCS,
        append extension specification after the file name, for example:
            - ``'image_drz.fits[sci,1]'`` for first "sci" extension
            - ``'image_drz.fits[1]'`` for the first extension
            - ``'image_drz.fits[0]'`` for the primary HDU

    orig_wcs_name : str
        Name (provided by the ``WCSNAME?`` header keyword where ``?``
        respesents a letter A-Z) of the "original" WCS. This is the WCS of
        the resampled image (obtained by drizzling all input images)  _before_
        this resampled image was aligned ("tweaked") to another image/catalog.

        If ``orig_wcs_name`` is `None`, the the original WCS _must be
        specified_ using ``orig_wcs_key``. When ``orig_wcs_key`` is provided,
        ``orig_wcs_name`` is ignored altogether.

    output_wcs_name : str, None
        Value of ``WCSNAME`` to be used to label the updated solution in the
        input (e.g., ``_flt.fits``) files.  If left blank or ``None``, it will
        default to using either the current (primary) ``WCSNAME`` value from
        the ``drz_file`` or from the alternate WCS given by the
        ``tweaked_wcs_name`` or ``tweaked_wcs_key`` parameters.

    input_files : str, None
        Filenames of distorted images whose primary WCS is to be updated
        with the same transformation as used in the "tweaked" drizzled image.
        Default value of `None` indicates that input image filenames will be
        derived from the ``D*DATA`` keywords written out by the ``AstroDrizzle``.
        If they can not be found, the task will quit.

        ``input_files`` string can contain one of the following:

            * a comma-separated list of valid science image file names
              (see note below) and (optionally) extension specifications,
              e.g.: ``'j1234567q_flt.fits[1], j1234568q_flt.fits[sci,2]'``;

            * an @-file name, e.g., ``'@files_to_match.txt'``.

        .. note::
            **Valid** **science** **image** **file** **names** are:

            * file names of existing FITS, GEIS, or WAIVER FITS files;

            * partial file names containing wildcard characters, e.g.,
              ``'*_flt.fits'``;

            * Association (ASN) tables (must have ``_asn``, or ``_asc``
              suffix), e.g., ``'j12345670_asn.fits'``.

        .. warning::
            @-file names **MAY** **NOT** be followed by an extension
            specification.

        .. warning::
            If an association table or a partial file name with wildcard
            characters is followed by an extension specification, it will be
            considered that this extension specification applies to **each**
            file name in the association table or **each** file name
            obtained after wildcard expansion of the partial file name.

    default_extname : str
        Extension name of extensions in input images whose primary WCS
        should be updated. This value is used only when file names provided in
        ``input_files`` do not contain extension specifications.

    Other Parameters
    ----------------
    tweaked_wcs_name : str
        Name of the "tweaked" WCS. This is the WCS of
        the resampled image (obtained by drizzling all input images)  _after_
        this resampled image was aligned ("tweaked") to another image/catalog.

        When neither ``tweaked_wcs_name`` nor ````tweaked_wcs_key`` are not
        provided, ``apply_tweak`` will take the current primary WCS in the
        drizzled image as a "tweaked" WCS. ``tweaked_wcs_name`` is ignored
        when ``tweaked_wcs_key`` is provided.

    tweaked_wcs_key : {' ', 'A'-'Z'}
        Same as ``tweaked_wcs_name`` except it specifies a WCS by key instead
        of name. When provided, ``tweaked_wcs_name`` is ignored.

    orig_wcs_key : {' ', 'A'-'Z'}
        Same as ``orig_wcs_name`` except it specifies a WCS by key instead
        of name. When provided, ``orig_wcs_name`` is ignored.

    Notes
    -----
    The algorithm used by this function is based on linearization of
    the exact compound operator that converts input image coordinates
    to the coordinates (in the input image) that would result in
    alignment with the new drizzled image WCS.

    .. warning::
        Parameters ``orig_wcs_name`` and ``tweaked_wcs_name`` (or their "key"
        equivalents) allow computation of transformation between *any two
        WCS* in the drizzled image and application of this transformation to the
        primary WCS of the input images. This will produce an
        expected result **only if** the WCS pointed to by ``orig_wcs_name`` was
        obtained by drizzling input images with their current primary WCS.


    EXAMPLES
    --------
    A drizzled image named ``acswfc_mos2_drz.fits`` was created from 4 images
    using ``AstroDrizzle``. The primary WCS of this drizzled image was named
    ``'INITIAL_GUESS'``. This drizzled image was then aligned to some other
    image using ``TweakReg`` and the updated ("tweaked") primary WCS was named
    ``'BEST_WCS'`` while the previous primary WCS - the WCS named
    ``'INITIAL_GUESS'`` - was archived by ``TweakReg`` under WCS key ``'C'``.
    We will refer to this archived WCS as the "original" WCS.
    ``apply_tweak`` can now be used to compute the
    transformation between the original and the tweaked WCS and apply this
    transformation to the WCS of each of the input images that were
    drizzle-combined to produce the resampled image ``acswfc_mos2_drz.fits``.

    The simplest way to accomplish this would be to run ``apply_tweak()`` using
    default parameters:

    >>> from drizzlepac import tweakback
    >>> tweakback.apply_tweak('acswfc_mos2_drz.fits', orig_wcs_name='INITIAL_GUESS')

    or

    >>> tweakback.apply_tweak('acswfc_mos2_drz.fits', orig_wcs_key='C')

    If the same WCS should be applied to a specific set of images or
    extensions in those images, then we can explicitly specify input files:

    >>> tweakback.apply_tweak(
    ...     'acswfc_mos2_drz.fits',
    ...     input='img_mos2a_flt.fits,img_mos2c_flt.fits[1],img_mos2d_flt.fits[sci,1]'
    ... )

    In the examples above, current primary WCS of the input
    ``'img_mos2?_flt.fits'`` files will be archived and the primary WCS will
    be replaced with a "tweaked" WCS obtained by applying relevant
    transformations to the current primary WCS. Because we did not specify
    ``output_wcs_name``, the name of this tweaked primary WCS in the
    input images will be set to ``'BEST_WCS'``.

    See Also
    --------
    stwcs.wcsutil.altwcs: Alternate WCS implementation

    """
    print(f"\n*** 'apply_tweak' version {__version__:s} started "
          f"at {util._ptime()[0]:s}: ***\n")

    tweaked_wcs_name = kwargs.get('tweaked_wcs_name', None)
    tweaked_wcs_key = kwargs.get('tweaked_wcs_key', None)
    orig_wcs_key = kwargs.get('orig_wcs_key', None)

    tweaked_wcs_key = _process_wcs_key_par('tweaked_wcs_key', kwargs)
    orig_wcs_key = _process_wcs_key_par('orig_wcs_key', kwargs)

    # load drizzled image and extract input file names (if needed) and
    # load specified WCS:

    fis = parse_cs_line(drz_file,
                        default_ext='*',
                        fnamesOnly=False,
                        doNotOpenDQ=True,
                        im_fmode="readonly")
    if len(fis) == 0:
        raise FileNotFoundError(f"Drizzled file '{drz_file}' not found.")
    elif len(fis) > 1:
        for f in fis:
            f.release_all_images()
        raise ValueError("When expanded, 'drz_file' should correspond to a "
                         "single file.")

    fi = fis[0]
    hdul = fi.image.hdu
    if len(fi.fext) == 1:
        drz_sciext = fi.fext[0]

    elif fi.fext:
        fi.release_all_images()
        raise ValueError(
            "Input drizzled image contains multiple image-like extensions. "
            "Please explicitly specify a single extension containing desired "
            "WCS.")

    else:
        fi.release_all_images()
        raise ValueError(
            "Specified extension was not found in the input drizzled image.")

    # check that there are at least two WCS in the drizzled image header:
    wkeys = altwcs.wcskeys(hdul, ext=drz_sciext)
    if len(wkeys) < 2:
        fi.release_all_images()
        raise ValueError(f"'{fi.image}[{ext2str(drz_sciext)}]' must "
                         "contain at least two valid WCS: original and "
                         "updated by tweakreg.")

    # load "tweaked" WCS
    tweaked_wcs_key, tweaked_wcs_name = _wcs_key_name(
        tweaked_wcs_key,
        tweaked_wcs_name,
        fi=fi,
        ext=drz_sciext,
        default_key=0,
        param_name='tweaked_wcs_name')
    tweaked_wcs = wcsutil.HSTWCS(hdul, ext=drz_sciext, wcskey=tweaked_wcs_key)

    # load "original" WCS
    if orig_wcs_key is None and orig_wcs_name is None:
        fi.release_all_images()
        raise ValueError(
            "Either 'orig_wcs_name' or 'orig_wcs_key' must be specified.")

    # default_key=-1 below is useless since we require that either
    # orig_wcs_key or orig_wcs_name be specified. However, in the future,
    # if we allow both to be None, we can use the last WCS key as the default
    # for the "original" WCS (last WCS key in the list).
    orig_wcs_key, orig_wcs_name = _wcs_key_name(orig_wcs_key,
                                                orig_wcs_name,
                                                fi=fi,
                                                ext=drz_sciext,
                                                default_key=-1,
                                                param_name='orig_wcs_name')
    orig_wcs = wcsutil.HSTWCS(hdul, ext=drz_sciext, wcskey=orig_wcs_key)

    # get RMS values reported for new solution
    crderr1 = fi.image.hdu[drz_sciext].header.get('CRDER1' + orig_wcs_key, 0.0)
    crderr2 = fi.image.hdu[drz_sciext].header.get('CRDER2' + orig_wcs_key, 0.0)

    fi.release_all_images()  # done with the resampled image

    # Process the list of input files:
    if not isinstance(default_extname, str):
        raise TypeError("Argument 'default_extname' must be a string")
    default_extname = default_extname.strip()
    if default_extname.upper() == 'PRIMARY':
        ext2get = ('PRIMARY', 1)
    else:
        ext2get = (default_extname, '*')

    if input_files is None:
        # get input (FLT) file names from the drizzled image. This information
        # is recorded in the primary header of the drizzled image.
        input_files = ",".join(hdul[0].header["D???DATA"].values())

    # Build a list of input files and extensions
    fnames_ext = {}
    fis = parse_cs_line(input_files,
                        default_ext=ext2get,
                        fnamesOnly=False,
                        doNotOpenDQ=True,
                        im_fmode="readonly")
    for f in fis:
        f.release_all_images()
        if f.image in fnames_ext:
            fnames_ext[f.image] |= set(f.fext)
        else:
            fnames_ext[f.image] = set(f.fext)

    if output_wcs_name is None:
        output_wcs_name = tweaked_wcs_name
        print(f"\n* Setting 'output_wcs_name' to '{output_wcs_name}'")
        auto_output_name = True
    else:
        auto_output_name = False

    output_wcs_name_u = output_wcs_name.strip().upper()

    # Compute tweakback transformation to each extension of each input file.
    # This is the main part of this function.
    # Before applying new WCS solution, make sure we can use the same
    # output WCS name for the updated WCS in all input images.
    # Also, this gives us opportunity to remove duplicate extensions, if any.

    final_wcs_info = []

    for fname, extlist in fnames_ext.items():
        print(f"\n* Working on input image {fname:s} ...")

        fis = parse_cs_line(f"{fname}",
                            default_ext=ext2get,
                            fnamesOnly=False,
                            doNotOpenDQ=True,
                            im_fmode="readonly")
        if len(fis) != 1:
            fi.release_all_images()
            raise AssertionError(
                "The algorithm should not open more than one file.")
        fi = fis[0]

        if not fi.fext:
            fi.release_all_images()
            print(
                f"  No valid input image extension found. Skipping image {fname}.\n"
            )
            continue

        current_wcs_info = {
            'fname': fname,
            'extlist': [],
            'archived_wcs_name': [],
            'updated_primary_wcs': []
        }
        final_wcs_info.append(current_wcs_info)

        # Process extensions
        hdu_list = []  # to avoid processing duplicate hdus
        try:
            for ext in extlist:
                imhdulist = fi.image.hdu
                hdu = imhdulist[ext]
                if hdu in hdu_list:
                    continue
                hdu_list.append(hdu)

                current_wcs_info['extlist'].append(ext)

                # Find the name under which to archive current WCS:
                all_wcs_names = [
                    v.upper() for v in altwcs.wcsnames(
                        imhdulist, ext, include_primary=False).values()
                ]

                if output_wcs_name_u in all_wcs_names:
                    if auto_output_name:
                        raise ValueError(
                            "Current value of 'output_wcs_name' was set to "
                            f"'{tweaked_wcs_name}' by default. However, this "
                            f"WCS name value was already used in {fname:s}[{ext2str(ext)}]. "
                            "Please re-run 'apply_tweak' again and explicitly "
                            "provide a unique value for the output WCS name.")
                    else:
                        raise ValueError(
                            "Provided value of 'output_wcs_name' - '{tweaked_wcs_name}' - "
                            f"was already used in {fname:s}[{ext2str(ext)}]. "
                            "Please re-run 'apply_tweak' again and explicitly "
                            "provide a unique value for the output WCS name.")

                if 'WCSNAME' in imhdulist[ext].header:
                    pri_wcs_name = imhdulist[ext].header['WCSNAME'].strip()
                else:
                    pri_wcs_name = 'NONAME'

                # add current output WCS name to the list so that archived
                # primary WCS will be archived under a different name:
                all_wcs_names.append(output_wcs_name)

                archived_name = altwcs._auto_increment_wcsname(
                    pri_wcs_name, all_wcs_names)
                current_wcs_info['archived_wcs_name'].append(archived_name)

                # compute updated WCS:
                new_wcs = wcsutil.HSTWCS(imhdulist, ext=ext)

                update_chip_wcs(new_wcs,
                                orig_wcs,
                                tweaked_wcs,
                                xrms=crderr1,
                                yrms=crderr2)
                new_wcs.setOrient()
                current_wcs_info['updated_primary_wcs'].append(new_wcs)

                print(
                    f"  - Computed new WCS solution for {fname:s}[{ext2str(ext)}]:"
                )
                repr_wcs = repr(new_wcs)
                print('\n'.join(
                    ['      ' + l.strip() for l in repr_wcs.split('\n')]))

        finally:
            fi.release_all_images()

    print("\n* Saving updated WCS to image headers:")

    for fwi in final_wcs_info:
        if not fwi['extlist']:
            continue

        fname = fwi['fname']

        fis = parse_cs_line(f"{fname}",
                            default_ext=ext2get,
                            fnamesOnly=False,
                            doNotOpenDQ=True,
                            im_fmode="update")
        fi = fis[0]

        # Process extensions
        try:
            for ext, archived_name, new_wcs in zip(fwi['extlist'],
                                                   fwi['archived_wcs_name'],
                                                   fwi['updated_primary_wcs']):
                imhdulist = fi.image.hdu
                hdu = imhdulist[ext]

                hdu.header['HISTORY'] = (
                    f"apply_tweak version: {__version__} ({date.today().isoformat():s})"
                )

                # Archive current primary WCS:
                awcs_key, awcs_name = altwcs.archive_wcs(
                    imhdulist,
                    ext,
                    wcsname=archived_name,
                    mode=altwcs.ArchiveMode.NO_CONFLICT)
                hdu.header['HISTORY'] = (
                    "apply_tweak: Archived Primary WCS under key "
                    f"'{awcs_key}' on {date.today().isoformat():s}")
                hdu.header['HISTORY'] = (
                    f"apply_tweak: WCSNAME{awcs_key}='{awcs_name}'")

                # Update primary WCS of this extension:
                wcs_hdr = new_wcs.wcs2header(idc2hdr=new_wcs.idcscale
                                             is not None,
                                             relax=True)
                wcs_hdr.set('WCSNAME', output_wcs_name, before=0)
                wcs_hdr.set('WCSTYPE',
                            updatehdr.interpret_wcsname_type(output_wcs_name),
                            after=0)
                wcs_hdr.set('ORIENTAT', new_wcs.orientat, after=len(wcs_hdr))
                hdu.header.update(wcs_hdr)
                hdu.header['HISTORY'] = (
                    f"apply_tweak: Applied Primary WCS correction on {date.today().isoformat():s}"
                )

            str_extlist = '; '.join(map(ext2str, fwi['extlist']))
            print(f"  - Updated '{fname:s}', extensions: {str_extlist}")

        finally:
            util.updateNEXTENDKw(imhdulist)
            fi.release_all_images()