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)
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)
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)
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
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')
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)
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)
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