예제 #1
0
def test_iter_linear_fit_special_cases(ideal_large_data, nclip, sigma,
                                       clip_accum, weights, noise):
    uv, xy, _, _, shift, rmat, _, _, fitgeom = ideal_large_data
    if weights:
        wxy, wuv = 0.1 + 0.9 * np.random.random((2, xy.shape[0]))
    else:
        wxy = None
        wuv = None

    if noise:
        xy = xy + np.random.normal(0, 0.01, xy.shape)
        atol = 0.01
    else:
        atol = _ATOL

    fit = linearfit.iter_linear_fit(xy,
                                    uv,
                                    wxy,
                                    wuv,
                                    fitgeom=fitgeom,
                                    nclip=nclip,
                                    center=(0, 0),
                                    sigma=1,
                                    clip_accum=clip_accum)

    assert np.allclose(fit['shift'], shift, rtol=0, atol=atol)
    assert np.allclose(fit['matrix'], rmat, rtol=0, atol=atol)
예제 #2
0
def test_iter_linear_fit_clip_style(ideal_large_data, weight_data, clip_accum,
                                    noise):
    """ Test clipping behavior. Test that weights exclude "bad" data. """
    uv, xy, angle, scale, shift, rmat, proper, skew, fitgeom = ideal_large_data
    wxy, wuv, idx_xy, idx_uv, bd_xy, bd_uv = weight_data

    noise_sigma = 0.01
    npts = xy.shape[0]

    # add noise to data
    if noise:
        xy = xy + np.random.normal(0, noise_sigma, (npts, 2))
        atol = 10 * noise_sigma
        nclip = 3
    else:
        atol = _ATOL
        nclip = 0

    if wxy is not None:
        xy[idx_xy] += bd_xy

    if wuv is not None:
        uv = uv.copy()
        uv[idx_uv] += bd_uv

    fit = linearfit.iter_linear_fit(xy,
                                    uv,
                                    wxy=wxy,
                                    wuv=wuv,
                                    fitgeom=fitgeom,
                                    sigma=2,
                                    clip_accum=clip_accum,
                                    nclip=nclip)

    shift_with_center = np.dot(rmat, fit['center']) - fit['center'] + shift

    assert np.allclose(fit['shift'], shift_with_center, rtol=0, atol=atol)
    assert np.allclose(fit['matrix'], rmat, rtol=0, atol=atol)
    assert np.allclose(fit['rmse'], 0, rtol=0, atol=atol)
    assert np.allclose(fit['mae'], 0, rtol=0, atol=atol)
    assert np.allclose(fit['std'], 0, rtol=0, atol=atol)
    assert fit['proper'] == proper
    if nclip:
        assert fit['eff_nclip'] > 0
        assert fit['fitmask'].sum(dtype=np.int) < npts
    else:
        assert fit['eff_nclip'] == 0
        assert (fit['fitmask'].sum(dtype=np.int) == npts -
                np.union1d(idx_xy[0], idx_uv[0]).size)
예제 #3
0
def test_iter_linear_fit_1point(weights):
    xy = np.array([[1.0, 2.0]])
    shifts = 20 * (np.random.random(2) - 0.5)
    if weights:
        wxy, wuv = 0.1 + 0.9 * np.random.random((2, xy.shape[0]))
    else:
        wxy, wuv = None, None

    fit = linearfit.iter_linear_fit(xy,
                                    xy + shifts,
                                    wxy=wxy,
                                    wuv=wuv,
                                    fitgeom='shift',
                                    nclip=0)

    assert np.allclose(fit['shift'], -shifts, rtol=0, atol=_ATOL)
    assert np.allclose(fit['matrix'], np.identity(2), rtol=0, atol=_ATOL)
예제 #4
0
def test_iter_linear_fit_fitgeom_clip_all_data(ideal_large_data):
    # Test that clipping is interrupted if number of sources after clipping
    # is below minobj for a given fit:
    xy, uv, _, _, _, _, _, _, fitgeom = ideal_large_data
    ndata = xy.shape[0]
    uv = uv + np.random.normal(0, 0.01, (ndata, 2))
    wxy, wuv = 0.1 + 0.9 * np.random.random((2, ndata))

    fit = linearfit.iter_linear_fit(xy,
                                    uv,
                                    wxy,
                                    wuv,
                                    fitgeom=fitgeom,
                                    sigma=1e-50,
                                    nclip=100)

    assert np.count_nonzero(fit['fitmask']) == len(xy)
    assert fit['eff_nclip'] == 0
예제 #5
0
def test_iter_linear_fit_invalid_fitgeom(ideal_small_data):
    uv, xy, _, _ = ideal_small_data

    with pytest.raises(ValueError):
        linearfit.iter_linear_fit(xy, uv, fitgeom='invalid')
예제 #6
0
def test_iter_linear_fit_invalid_sigma_nclip(ideal_small_data, nclip, sigma):
    uv, xy, _, _ = ideal_small_data

    with pytest.raises(ValueError):
        linearfit.iter_linear_fit(xy, uv, nclip=nclip, sigma=sigma)
예제 #7
0
def test_iter_linear_fit_invalid_shapes(uv, xy, wuv, wxy):
    # incorrect coordinate array dimensionality:
    with pytest.raises(ValueError):
        linearfit.iter_linear_fit(xy, uv, wxy=wxy, wuv=wuv)
예제 #8
0
def find_linear_fit(img_cutouts,
                    drz_cutouts,
                    wcslin=None,
                    fitgeom='general',
                    nclip=3,
                    sigma=3.0,
                    use_weights=True,
                    cc_type='NCC'):
    """
    Perform linear fit to diplacements (found using cross-correlation) between
    ``img_cutouts`` and "blot" of ``drz_cutouts`` onto ``img_cutouts``.

    Parameters
    ----------
    img_cutouts : Cutout
        Cutouts whose WCS should be aligned.

    drz_cutouts : Cutout
        Cutouts that serve as "reference" to which ``img_cutouts`` will be
        aligned.

    wcslin : astropy.wcs.WCS, None, optional
        A `~astropy.wcs.WCS` object that does not have non-linear distortions.
        This WCS defines a tangen plane in which image alignemnt will be
        performed. When not provided or set to `None`,
        internally ``wcslin`` will be set to ``drz_cutouts[0].wcs``.

    fitgeom : {'shift', 'rscale', 'general'}, optional
        The fitting geometry to be used in fitting cutout displacements.
        This parameter is used in fitting the offsets, rotations
        and/or scale changes from the matched object lists. The 'general'
        fit geometry allows for independent scale and rotation for
        each axis.

    nclip : int, optional
        Number (a non-negative integer) of clipping iterations in fit.

    sigma : float, optional
        Clipping limit in sigma units.

    use_weights : bool, optional
        Indicates whether to perform a weighted fit when all input
        ``drz_cutouts.src_weight`` are not `None`.

    cc_type : {'CC', 'NCC', 'ZNCC'}, optional
        Cross-correlation algorithm to be used. ``'CC'`` indicates the
        "standard" cross-correlation algorithm. ``'NCC'`` refers to the
        normalized cross-correlation and ``'ZNCC'`` refers to the
        zero-normalized cross-correlation, see, e.g.,
        `Terminology in image processing \
        <https://en.wikipedia.org/wiki/Cross-correlation#\
        Terminology_in_image_processing>`_.

    Returns
    -------
    fit : dict
        A dictionary of various fit parameters computed during the fit. Use
        ``fit.keys()`` to find which parameters are being returned.

    interlaced_cc : numpy.ndarray
        Interlaced (super-sampled) cross-correlation image. This is provided
        as a diagnostic tool for debugging purposes.

    nonshifted_blts : Cutout
        A list of cutouts of blotted ``drz_cutouts`` without applying any
        sub-pixel shifts. This is provided as a diagnostic tool for
        debugging purposes.

    """
    # check that number of drizzled and FLT cutouts match:
    if not isinstance(img_cutouts, collections.Iterable):
        img_cutouts = [img_cutouts]

    if not isinstance(drz_cutouts, collections.Iterable):
        drz_cutouts = [drz_cutouts]

    if len(img_cutouts) != len(drz_cutouts):
        raise ValueError("The number of image cutouts must match the number "
                         "of drizzled cutouts.")

    # choose a tangent plane WCS if not provided:
    if wcslin is None:
        # get wcs from the first drzizzled cutout:
        wcslin = drz_cutouts[0].wcs
    wcslin = wcslin.deepcopy()

    # check if reference WCS is distorted:
    if any(
            getattr(wcslin, distortion) is not None
            for distortion in _ASTROPY_WCS_DISTORTIONS):
        raise ValueError("Reference WCS must not have non-linear distortions.")

    npts = len(img_cutouts)
    xyim = np.empty((npts, 2), dtype=np.float)
    xyref = np.empty_like(xyim)
    img_dxy = np.empty_like(xyim)
    ref_dxy = np.empty_like(xyim)

    interlaced_cc = []
    nonshifted_blts = []

    # create shifted FLT cutouts:
    for k, (imct, dzct) in enumerate(zip(img_cutouts, drz_cutouts)):
        # store intitial image cutout displacements:
        dx0 = imct.dx
        dy0 = imct.dy

        dzct.data[dzct.mask] = 0

        # blot to initial image cutout:
        blt00 = blot_cutout(dzct, imct)

        # blot to a shifted image cutout by 1/2 along X:
        imct.dx -= 0.5
        blt10 = blot_cutout(dzct, imct)

        # blot to a shifted image cutout by 1/2 along X and Y:
        imct.dy -= 0.5
        blt11 = blot_cutout(dzct, imct)

        # blot to a shifted image cutout by 1/2 along Y:
        imct.dx = dx0
        blt01 = blot_cutout(dzct, imct)

        # restore original image cutout's displacement:
        imct.dy = dy0

        # find cross-correlation shift in image's coordinate system:
        dx, dy, icc, _ = cc.find_displacement(imct.data,
                                              blt00.data,
                                              blt10.data,
                                              blt01.data,
                                              blt11.data,
                                              cc_type=cc_type,
                                              full_output=True)

        interlaced_cc.append(icc)
        nonshifted_blts.append(blt00)
        img_dxy[k] = [dx, dy]

        # convert displacements to reference linear WCS's image coordinates:
        x1, y1 = imct.cutout_src_pos
        xyim[k] = np.array(wcslin.wcs_world2pix(*imct.pix2world(x1, y1), 1))

        x2 = x1 + dx
        y2 = y1 + dy
        xyref[k] = np.array(wcslin.wcs_world2pix(*imct.pix2world(x2, y2), 1))

        ref_dxy[k] = xyim[k] - xyref[k]

    # create a list of weights (if available). We do this only for the
    # cutouts from drizzled image because image cutouts have identical weights.
    if use_weights:
        weights = [ct.src_weight for ct in drz_cutouts]
        if all(w is None for w in weights):
            weights = None
        elif any(w is None for w in weights):
            raise ValueError("Not all cutouts have weights set. All cutouts "
                             "must either have non-negative weights or be "
                             "None.")
        elif any(w < 0 for w in weights):
            raise ValueError("Weights must be non-negative.")
        else:
            weights = np.asarray(weights)

    else:
        weights = None

    # find linear transformation:
    fit = linearfit.iter_linear_fit(xyim,
                                    xyref,
                                    wxy=None,
                                    wuv=weights,
                                    fitgeom=fitgeom,
                                    center=np.array(wcslin.wcs.crpix),
                                    nclip=nclip,
                                    sigma=sigma)

    fit['subpixal_img_dxy'] = img_dxy
    fit['subpixal_ref_dxy'] = ref_dxy

    # Compute fit RMSE in the *image* coordinate system:
    if weights is None:
        fit['irmse'] = np.sqrt(2 * np.mean(img_dxy[fit['fitmask']]**2))

    else:
        npts = len(weights)
        wt = np.sum(weights)
        if npts == 0 or wt == 0.0:
            fit['irmse'] = float('nan')
        else:
            w = weights / wt
            fit['irmse'] = float(
                np.sqrt(
                    np.sum(
                        np.dot(w[fit['fitmask']],
                               img_dxy[fit['fitmask']]**2))))

    return fit, interlaced_cc, nonshifted_blts