def similarity(im0, im1, numiter=1, order=3, constraints=None, filter_pcorr=0, exponent='inf', reports=None): """ Return similarity transformed image im1 and transformation parameters. Transformation parameters are: isotropic scale factor, rotation angle (in degrees), and translation vector. A similarity transformation is an affine transformation with isotropic scale and without shear. Args: im0 (2D numpy array): The first (template) image im1 (2D numpy array): The second (subject) image numiter (int): How many times to iterate when determining scale and rotation order (int): Order of approximation (when doing transformations). 1 = linear, 3 = cubic etc. filter_pcorr (int): Radius of a spectrum filter for translation detection exponent (float or 'inf'): The exponent value used during processing. Refer to the docs for a thorough explanation. Generally, pass "inf" when feeling conservative. Otherwise, experiment, values below 5 are not even supposed to work. constraints (dict or None): Specify preference of seeked values. Pass None (default) for no constraints, otherwise pass a dict with keys ``angle``, ``scale``, ``tx`` and/or ``ty`` (i.e. you can pass all, some of them or none of them, all is fine). The value of a key is supposed to be a mutable 2-tuple (e.g. a list), where the first value is related to the constraint center and the second one to softness of the constraint (the higher is the number, the more soft a constraint is). More specifically, constraints may be regarded as weights in form of a shifted Gaussian curve. However, for precise meaning of keys and values, see the documentation section :ref:`constraints`. Names of dictionary keys map to names of command-line arguments. Returns: dict: Contains following keys: ``scale``, ``angle``, ``tvec`` (Y, X), ``success`` and ``timg`` (the transformed subject image) .. note:: There are limitations * Scale change must be less than 2. * No subpixel precision (but you can use *resampling* to get around this). """ bgval = utils.get_borderval(im1, 5) res = _similarity(im0, im1, numiter, order, constraints, filter_pcorr, exponent, bgval, reports) im2 = transform_img_dict(im1, res, bgval, order) return im2
def _preprocess_extend_single(im, extend, low, high, cut, rcoef, bigshape): im = utils.extend_by(im, extend) im = utils.imfilter(im, low, high, cut) if rcoef != 1: im = resample(im, rcoef) # Make the shape of images the same bg = np.zeros(bigshape) + utils.get_borderval(im, 5) im = utils.embed_to(bg, im) return im
def _preprocess_extend_single(im, extend, low, high, cut, rcoef, bigshape): im = utils.extend_by(im, extend) im = utils.imfilter(im, low, high, cut) if rcoef != 1: im = resample(im, rcoef) # Make the shape of images the same bg = np.zeros(bigshape, dtype=im.dtype) + utils.get_borderval(im, 5) im = utils.embed_to(bg, im) return im
def _preprocess_extend(ims, extend, low, high, cut, rcoef): ims = [utils.extend_by(img, extend) for img in ims] bigshape = np.array([img.shape for img in ims]).max(0) ims = filter_images(ims, low, high, cut) if rcoef != 1: ims = [resample(img, rcoef) for img in ims] bigshape *= rcoef # Make the shape of images the same bgs = [np.zeros(bigshape) + utils.get_borderval(img, 5) for img in ims] ims = [utils.embed_to(bg, img) for bg, img in zip(bgs, ims)] return ims
def transform_img(img, scale=1.0, angle=0.0, tvec=(0, 0), bgval=None, order=1): """ Return translation vector to register images. Args: img (2D or 3D numpy array): What will be transformed. If a 3D array is passed, it is treated in a manner in which RGB images are supposed to be handled - i.e. assume that coordinates are (Y, X, channels). scale (float): The scale factor (scale > 1.0 means zooming in) angle (float): Degrees of rotation (clock-wise) tvec (2-tuple): Pixel translation vector, Y and X component. bgval (float): Shade of the background (filling during transformations) If None is passed, :func:`imreg_dft.utils.get_borderval` with radius of 5 is used to get it. order (int): Order of approximation (when doing transformations). 1 = linear, 3 = cubic etc. Linear works surprisingly well. Returns: The transformed img, may have another i.e. (bigger) shape than the source. """ if img.ndim == 3: # A bloody painful special case of RGB images ret = np.empty_like(img) for idx in range(img.shape[2]): sli = (slice(None), slice(None), idx) ret[sli] = transform_img(img[sli], scale, angle, tvec, bgval, order) return ret if bgval is None: bgval = utils.get_borderval(img, 5) bigshape = np.array(img.shape) * 1.2 bg = np.zeros(bigshape, img.dtype) + bgval dest0 = utils.embed_to(bg, img.copy()) if scale != 1.0: dest0 = ndii.zoom(dest0, scale, order=order, cval=bgval) if angle != 0.0: dest0 = ndii.rotate(dest0, angle, order=order, cval=bgval) if tvec[0] != 0 or tvec[1] != 0: dest0 = ndii.shift(dest0, tvec, order=order, cval=bgval) bg = np.zeros_like(img) + bgval dest = utils.embed_to(bg, dest0) return dest
def transform_img(img, scale=1.0, angle=0.0, tvec=(0, 0), bgval=None, order=1): """ Return translation vector to register images. Args: img (2D or 3D numpy array): What will be transformed. If a 3D array is passed, it is treated in a manner in which RGB images are supposed to be handled - i.e. assume that coordinates are (Y, X, channels). scale (float): The scale factor (scale > 1.0 means zooming in) angle (float): Degrees of rotation (clock-wise) tvec (2-tuple): Pixel translation vector, Y and X component. bgval (float): Shade of the background (filling during transformations) If None is passed, :func:`imreg_dft.utils.get_borderval` with radius of 5 is used to get it. order (int): Order of approximation (when doing transformations). 1 = linear, 3 = cubic etc. Linear works surprisingly well. Returns: np.ndarray: The transformed img, may have another i.e. (bigger) shape than the source. """ if img.ndim == 3: # A bloody painful special case of RGB images ret = np.empty_like(img) for idx in range(img.shape[2]): sli = (slice(None), slice(None), idx) ret[sli] = transform_img(img[sli], scale, angle, tvec, bgval, order) return ret if bgval is None: bgval = utils.get_borderval(img) bigshape = np.round(np.array(img.shape) * 1.2).astype(int) bg = np.zeros(bigshape, img.dtype) + bgval dest0 = utils.embed_to(bg, img.copy()) if scale != 1.0: dest0 = ndii.zoom(dest0, scale, order=order, cval=bgval) if angle != 0.0: dest0 = ndii.rotate(dest0, angle, order=order, cval=bgval) if tvec[0] != 0 or tvec[1] != 0: dest0 = ndii.shift(dest0, tvec, order=order, cval=bgval) bg = np.zeros_like(img) + bgval dest = utils.embed_to(bg, dest0) return dest
def process_images(ims, opts, tosa=None): # lazy import so no imports before run() is really called import numpy as np from imreg_dft import utils from imreg_dft import imreg ims = [utils.extend_by(img, opts["extend"]) for img in ims] bigshape = np.array([img.shape for img in ims]).max(0) ims = filter_images(ims, opts["low"], opts["high"]) rcoef = opts["resample"] if rcoef != 1: ims = [resample(img, rcoef) for img in ims] bigshape *= rcoef # Make the shape of images the same ims = [ utils.embed_to(np.zeros(bigshape) + utils.get_borderval(img, 5), img) for img in ims ] resdict = imreg.similarity(ims[0], ims[1], opts["iters"], opts["order"], opts["constraints"], opts["filter_pcorr"], opts["exponent"]) im2 = resdict.pop("timg") # Seems that the reampling simply scales the translation resdict["tvec"] /= rcoef ty, tx = resdict["tvec"] resdict["tx"] = tx resdict["ty"] = ty resdict["imgs"] = ims tform = resdict if tosa is not None: tosa[:] = ird.transform_img_dict(tosa, tform) if rcoef != 1: ims = [resample(img, 1.0 / rcoef) for img in ims] im2 = resample(im2, 1.0 / rcoef) resdict["Dt"] /= rcoef resdict["unextended"] = [ utils.unextend_by(img, opts["extend"]) for img in ims + [im2] ] return resdict
def process_images(ims, opts, tosa=None): # lazy import so no imports before run() is really called import numpy as np from imreg_dft import utils from imreg_dft import imreg ims = [utils.extend_by(img, opts["extend"]) for img in ims] bigshape = np.array([img.shape for img in ims]).max(0) ims = filter_images(ims, opts["low"], opts["high"]) rcoef = opts["resample"] if rcoef != 1: ims = [resample(img, rcoef) for img in ims] bigshape *= rcoef # Make the shape of images the same ims = [utils.embed_to(np.zeros(bigshape) + utils.get_borderval(img, 5), img) for img in ims] resdict = imreg.similarity( ims[0], ims[1], opts["iters"], opts["order"], opts["constraints"], opts["filter_pcorr"], opts["exponent"]) im2 = resdict.pop("timg") # Seems that the reampling simply scales the translation resdict["tvec"] /= rcoef ty, tx = resdict["tvec"] resdict["tx"] = tx resdict["ty"] = ty resdict["imgs"] = ims tform = resdict if tosa is not None: tosa[:] = ird.transform_img_dict(tosa, tform) if rcoef != 1: ims = [resample(img, 1.0 / rcoef) for img in ims] im2 = resample(im2, 1.0 / rcoef) resdict["Dt"] /= rcoef resdict["unextended"] = [utils.unextend_by(img, opts["extend"]) for img in ims + [im2]] return resdict
def _logpolar(image, shape, log_base, bgval=None): """ Return log-polar transformed image and log base. Takes into account anisotropicity of the freq spectrum of rectangular images Args: image: The image to be transformed shape: Shape of the transformed image log_base: Parameter of the transformation, convoluted with :func:`_get_log_base` Returns: The transformed image """ if bgval is None: bgval = utils.get_borderval(image, 5) imshape = np.array(image.shape) center = imshape[0] / 2.0, imshape[1] / 2.0 # 0 .. pi = only half of the spectrum is used theta = utils._get_angles(shape) radius_x = utils._get_scales(shape, log_base) radius_y = radius_x.copy() ellipse_coef = imshape[0] / float(imshape[1]) # We have to acknowledge that the frequency spectrum can be deformed # if the image aspect ratio is not 1.0 # The image is x-thin, so we acknowledge that the frequency spectra # scale in x is shrunk. radius_x /= ellipse_coef y = radius_y * np.sin(theta) + center[0] x = radius_x * np.cos(theta) + center[1] output = np.empty_like(y) ndii.map_coordinates(image, [y, x], output=output, order=3, mode="constant", cval=bgval) """ import pylab as pyl pyl.figure(); pyl.imshow(output); pyl.show() """ return output
def settle_tiles(imgs, tiledim, opts, reports=None): global _SHIFTS coef = 0.41 img0 = imgs[0] tiles, poss = zip(* ird.utils.decompose(img0, tiledim, coef)) nrows, ncols = utils.starts2dshape(poss) _fill_globals(tiles, poss, imgs[1], opts) for ii, pos in enumerate(poss): process_tile(ii, reports) tile_coord = (ii // ncols, ii % ncols) """ if ncores == 0: # no multiprocessing (to see errors) _seg_init(mesh, dims, counter) data = map(_get_prep, allranges) else: pool = mp.Pool( processes=ncores, initializer=_seg_init, initargs=(mesh, dims, counter), ) res = pool.map_async(_get_prep, allranges) pool.close() while not res.ready(): reporter.update(counter.value) time.sleep(sleeptime) assert res.successful(), \ "Some exceptions have likely occured" data = res.get() """ tosa_offset = np.array(img0.shape)[:2] - np.array(tiledim)[:2] + 0.5 _SHIFTS -= tosa_offset / 2.0 # Get the cluster of the tiles that have similar results and that look # most promising along with the index of the best tile cluster, amax = utils.get_best_cluster(_SHIFTS, _SUCCS, 5) # Make the quantities estimation even more precise by taking # the average of all good tiles shift, angle, scale, score = utils.get_values( cluster, _SHIFTS, _SUCCS, _ANGLES, _SCALES) if reports is not None and reports.show("tile_info"): shape = (nrows, ncols) slices = utils.getSlices(img0.shape, tiledim, coef) reports.set_global("tiles-whole", img0) reports.set_global("tiles-shape", shape) reports.set_global("tiles-cluster", cluster) reports.set_global("tiles_successes", _SUCCS) reports.set_global("tiles_decomp", slices) resdict = _assemble_resdict(amax) resdict["scale"] = scale resdict["angle"] = angle resdict["tvec"] = shift resdict["ty"], resdict["tx"] = resdict["tvec"] bgval = utils.get_borderval(imgs[1], 5) ims = _preprocess_extend(imgs, opts["extend"], opts["low"], opts["high"], opts["cut"], opts["resample"]) im2 = ird.transform_img_dict(ims[1], resdict, bgval, opts["order"]) # TODO: This is kinda dirty resdict["unextended"] = _postprocess_unextend(ims, im2, opts["extend"]) resdict["Dangle"], resdict["Dscale"], resdict["Dt"] = _DIFFS return resdict
def settle_tiles(imgs, tiledim, opts, reports=None): global _SHIFTS coef = 0.41 coef = 0.81 img0 = imgs[0] tiles, poss = zip(* ird.utils.decompose(img0, tiledim, coef)) nrows, ncols = utils.starts2dshape(poss) _fill_globals(tiles, poss, imgs[1], opts) for ii, pos in enumerate(poss): process_tile(ii, reports) tile_coord = (ii // ncols, ii % ncols) if reports is not None and reports.show("tile_info"): shape = (nrows, ncols) slices = utils.getSlices(img0.shape, tiledim, coef) reports.set_global("tiles-whole", img0) reports.set_global("tiles-shape", shape) reports.set_global("tiles-successes", _SUCCS) reports.set_global("tiles-decomp", slices) """ if ncores == 0: # no multiprocessing (to see errors) _seg_init(mesh, dims, counter) data = map(_get_prep, allranges) else: pool = mp.Pool( processes=ncores, initializer=_seg_init, initargs=(mesh, dims, counter), ) res = pool.map_async(_get_prep, allranges) pool.close() while not res.ready(): reporter.update(counter.value) time.sleep(sleeptime) assert res.successful(), \ "Some exceptions have likely occured" data = res.get() """ tosa_offset = np.array(img0.shape)[:2] - np.array(tiledim)[:2] + 0.5 _SHIFTS -= tosa_offset / 2.0 # Get the cluster of the tiles that have similar results and that look # most promising along with the index of the best tile cluster, amax = utils.get_best_cluster(_SHIFTS, _SUCCS, 5) # Make the quantities estimation even more precise by taking # the average of all good tiles shift, angle, scale, score = utils.get_values( cluster, _SHIFTS, _SUCCS, _ANGLES, _SCALES) resdict = _assemble_resdict(amax) resdict["scale"] = scale resdict["angle"] = angle resdict["tvec"] = shift resdict["ty"], resdict["tx"] = resdict["tvec"] bgval = utils.get_borderval(imgs[1], 5) ims = _preprocess_extend(imgs, opts["extend"], opts["low"], opts["high"], opts["cut"], opts["resample"]) im2 = ird.transform_img_dict(ims[1], resdict, bgval, opts["order"]) # TODO: This is kinda dirty resdict["unextended"] = _postprocess_unextend(ims, im2, opts["extend"]) resdict["Dangle"], resdict["Dscale"], resdict["Dt"] = _DIFFS return resdict
def _similarity(im0, im1, numiter=1, order=3, constraints=None, filter_pcorr=0, exponent='inf', bgval=None, reports=None): """ This function takes some input and returns mutual rotation, scale and translation. It does these things during the process: * Handles correct constraints handling (defaults etc.). * Performs angle-scale determination iteratively. This involves keeping constraints in sync. * Performs translation determination. * Calculates precision. Returns: Dictionary with results. """ if bgval is None: bgval = utils.get_borderval(im1, 5) shape = im0.shape if shape != im1.shape: raise ValueError("Images must have same shapes.") elif im0.ndim != 2: raise ValueError("Images must be 2-dimensional.") # We are going to iterate and precise scale and angle estimates scale = 1.0 angle = 0.0 im2 = im1 constraints_default = dict(angle=[0, None], scale=[1, None]) if constraints is None: constraints = constraints_default # We guard against case when caller passes only one constraint key. # Now, the provided ones just replace defaults. constraints_default.update(constraints) constraints = constraints_default # During iterations, we have to work with constraints too. # So we make the copy in order to leave the original intact constraints_dynamic = constraints.copy() constraints_dynamic["scale"] = list(constraints["scale"]) constraints_dynamic["angle"] = list(constraints["angle"]) if reports is not None and reports.show("transformed"): reports["after_tform"] = [im2.copy()] for ii in range(numiter): newscale, newangle = _get_ang_scale([im0, im2], bgval, exponent, constraints_dynamic, reports) scale *= newscale angle += newangle constraints_dynamic["scale"][0] /= newscale constraints_dynamic["angle"][0] -= newangle im2 = transform_img(im1, scale, angle, bgval=bgval, order=order) if reports is not None and reports.show("transformed"): reports["after_tform"].append(im2.copy()) # Here we look how is the turn-180 target, stdev = constraints.get("angle", (0, None)) odds = _get_odds(angle, target, stdev) # now we can use pcorr to guess the translation res = translation(im0, im2, filter_pcorr, odds, constraints, reports) # The log-polar transform may have got the angle wrong by 180 degrees. # The phase correlation can help us to correct that angle += res["angle"] res["angle"] = utils.wrap_angle(angle, 360) # don't know what it does, but it alters the scale a little bit # scale = (im1.shape[1] - 1) / (int(im1.shape[1] / scale) - 1) Dangle, Dscale = _get_precision(shape, scale) res["scale"] = scale res["Dscale"] = Dscale res["Dangle"] = Dangle # 0.25 because we go subpixel now res["Dt"] = 0.25 return res
def transform_img(img, scale=1.0, angle=0.0, tvec=(0, 0), mode="constant", bgval=None, order=1): """ Return translation vector to register images. Args: img (2D or 3D numpy array): What will be transformed. If a 3D array is passed, it is treated in a manner in which RGB images are supposed to be handled - i.e. assume that coordinates are (Y, X, channels). Complex images are handled in a way that treats separately the real and imaginary parts. scale (float): The scale factor (scale > 1.0 means zooming in) angle (float): Degrees of rotation (clock-wise) tvec (2-tuple): Pixel translation vector, Y and X component. mode (string): The transformation mode (refer to e.g. :func:`scipy.ndimage.shift` and its kwarg ``mode``). bgval (float): Shade of the background (filling during transformations) If None is passed, :func:`imreg_dft.utils.get_borderval` with radius of 5 is used to get it. order (int): Order of approximation (when doing transformations). 1 = linear, 3 = cubic etc. Linear works surprisingly well. Returns: np.ndarray: The transformed img, may have another i.e. (bigger) shape than the source. """ if img.ndim == 3: # A bloody painful special case of RGB images ret = np.empty_like(img) for idx in range(img.shape[2]): sli = (slice(None), slice(None), idx) ret[sli] = transform_img(img[sli], scale, angle, tvec, mode, bgval, order) return ret elif np.iscomplexobj(img): decomposed = np.empty(img.shape + (2, ), float) decomposed[:, :, 0] = img.real decomposed[:, :, 1] = img.imag # The bgval makes little sense now, as we decompose the image res = transform_img(decomposed, scale, angle, tvec, mode, None, order) ret = res[:, :, 0] + 1j * res[:, :, 1] return ret if bgval is None: bgval = utils.get_borderval(img) bigshape = np.round(np.array(img.shape) * 1.2).astype(int) bg = np.zeros(bigshape, img.dtype) + bgval dest0 = utils.embed_to(bg, img.copy()) # TODO: We have problems with complex numbers # that are not supported by zoom(), rotate() or shift() if scale != 1.0: dest0 = ndii.zoom(dest0, scale, order=order, mode=mode, cval=bgval) if angle != 0.0: dest0 = ndii.rotate(dest0, angle, order=order, mode=mode, cval=bgval) if tvec[0] != 0 or tvec[1] != 0: dest0 = ndii.shift(dest0, tvec, order=order, mode=mode, cval=bgval) bg = np.zeros_like(img) + bgval dest = utils.embed_to(bg, dest0) return dest
def transform_img(img, scale=1.0, angle=0.0, tvec=(0, 0), mode="constant", bgval=None, order=1): """ Return translation vector to register images. Args: img (2D or 3D numpy array): What will be transformed. If a 3D array is passed, it is treated in a manner in which RGB images are supposed to be handled - i.e. assume that coordinates are (Y, X, channels). Complex images are handled in a way that treats separately the real and imaginary parts. scale (float): The scale factor (scale > 1.0 means zooming in) angle (float): Degrees of rotation (clock-wise) tvec (2-tuple): Pixel translation vector, Y and X component. mode (string): The transformation mode (refer to e.g. :func:`scipy.ndimage.shift` and its kwarg ``mode``). bgval (float): Shade of the background (filling during transformations) If None is passed, :func:`imreg_dft.utils.get_borderval` with radius of 5 is used to get it. order (int): Order of approximation (when doing transformations). 1 = linear, 3 = cubic etc. Linear works surprisingly well. Returns: np.ndarray: The transformed img, may have another i.e. (bigger) shape than the source. """ if img.ndim == 3: # A bloody painful special case of RGB images ret = np.empty_like(img) for idx in range(img.shape[2]): sli = (slice(None), slice(None), idx) ret[sli] = transform_img(img[sli], scale, angle, tvec, mode, bgval, order) return ret elif np.iscomplexobj(img): decomposed = np.empty(img.shape + (2,), float) decomposed[:, :, 0] = img.real decomposed[:, :, 1] = img.imag # The bgval makes little sense now, as we decompose the image res = transform_img(decomposed, scale, angle, tvec, mode, None, order) ret = res[:, :, 0] + 1j * res[:, :, 1] return ret if bgval is None: bgval = utils.get_borderval(img) bigshape = np.round(np.array(img.shape) * 1.2).astype(int) bg = np.zeros(bigshape, img.dtype) + bgval dest0 = utils.embed_to(bg, img.copy()) # TODO: We have problems with complex numbers # that are not supported by zoom(), rotate() or shift() if scale != 1.0: dest0 = ndii.zoom(dest0, scale, order=order, mode=mode, cval=bgval) if angle != 0.0: dest0 = ndii.rotate(dest0, angle, order=order, mode=mode, cval=bgval) if tvec[0] != 0 or tvec[1] != 0: dest0 = ndii.shift(dest0, tvec, order=order, mode=mode, cval=bgval) bg = np.zeros_like(img) + bgval dest = utils.embed_to(bg, dest0) return dest
def similarity(im0, im1, numiter=1, order=3, constraints=None, filter_pcorr=0, exponent='inf', reports=None): """ Return similarity transformed image im1 and transformation parameters. Transformation parameters are: isotropic scale factor, rotation angle (in degrees), and translation vector. A similarity transformation is an affine transformation with isotropic scale and without shear. Args: im0 (2D numpy array): The first (template) image im1 (2D numpy array): The second (subject) image numiter (int): How many times to iterate when determining scale and rotation order (int): Order of approximation (when doing transformations). 1 = linear, 3 = cubic etc. filter_pcorr (int): Radius of a spectrum filter for translation detection exponent (float or 'inf'): The exponent value used during processing. Refer to the docs for a thorough explanation. Generally, pass "inf" when feeling conservative. Otherwise, experiment, values below 5 are not even supposed to work. constraints (dict or None): Specify preference of seeked values. Pass None (default) for no constraints, otherwise pass a dict with keys ``angle``, ``scale``, ``tx`` and/or ``ty`` (i.e. you can pass all, some of them or none of them, all is fine). The value of a key is supposed to be a mutable 2-tuple (e.g. a list), where the first value is related to the constraint center and the second one to softness of the constraint (the higher is the number, the more soft a constraint is). More specifically, constraints may be regarded as weights in form of a shifted Gaussian curve. However, for precise meaning of keys and values, see the documentation section :ref:`constraints`. Names of dictionary keys map to names of command-line arguments. Returns: dict: Contains following keys: ``scale``, ``angle``, ``tvec`` (Y, X), ``success`` and ``timg`` (the transformed subject image) .. note:: There are limitations * Scale change must be less than 2. * No subpixel precision (but you can use *resampling* to get around this). """ bgval = utils.get_borderval(im1, 5) res = _similarity(im0, im1, numiter, order, constraints, filter_pcorr, exponent, bgval, reports) im2 = transform_img_dict(im1, res, bgval, order) # Order of mask should be always 1 - higher values produce strange results. imask = transform_img_dict(np.ones_like(im1), res, 0, 1) # This removes some weird artifacts imask[imask > 0.8] = 1.0 # Framing here = just blending the im2 with its BG according to the mask im3 = utils.frame_img(im2, imask, 10) res["timg"] = im3 return res
def similarity(im0, im1, numiter=1, order=3, constraints=None, filter_pcorr=0, exponent='inf'): """ Return similarity transformed image im1 and transformation parameters. Transformation parameters are: isotropic scale factor, rotation angle (in degrees), and translation vector. A similarity transformation is an affine transformation with isotropic scale and without shear. Args: im0 (2D numpy array): The first (template) image im1 (2D numpy array): The second (subject) image numiter (int): How many times to iterate when determining scale and rotation order (int): Order of approximation (when doing transformations). 1 = linear, 3 = cubic etc. filter_pcorr (int): Radius of a spectrum filter for translation detection exponent (float or 'inf'): The exponent value used during processing. Refer to the docs for a thorough explanation. Generally, pass "inf" when feeling conservative. Otherwise, experiment, values below 5 are not even supposed to work. Returns: dict: Contains following keys: ``scale``, ``angle``, ``tvec`` (Y, X), ``success`` and ``timg`` (the transformed subject image) .. note:: There are limitations * Scale change must be less than 2. * No subpixel precision (but you can use *resampling* to get around this). """ shape = im0.shape if shape != im1.shape: raise ValueError("Images must have same shapes.") elif im0.ndim != 2: raise ValueError("Images must be 2-dimensional.") # We are going to iterate and precise scale and angle estimates scale = 1.0 angle = 0.0 im2 = im1 if constraints is None: constraints = dict(angle=[0, None], scale=[1, None]) # During iterations, we have to work with constraints too. # So we make the copy in order to leave the original intact constraints_dynamic = constraints.copy() constraints_dynamic["scale"] = list(constraints["scale"]) constraints_dynamic["angle"] = list(constraints["angle"]) bgval = utils.get_borderval(im1, 5) for ii in range(numiter): newscale, newangle = _get_ang_scale([im0, im2], bgval, exponent, constraints_dynamic) scale *= newscale angle += newangle constraints_dynamic["scale"][0] /= newscale constraints_dynamic["angle"][0] -= newangle im2 = transform_img(im1, scale, angle, bgval=bgval, order=order) # Here we look how is the turn-180 target, stdev = constraints.get("angle", (0, None)) odds = _get_odds(angle, target, stdev) # now we can use pcorr to guess the translation tvec, succ, angle2 = _translation(im0, im2, filter_pcorr, odds, constraints) # The log-polar transform may have got the angle wrong by 180 degrees. # The phase correlation can help us to correct that angle += angle2 angle = utils.wrap_angle(angle, 360) # don't know what it does, but it alters the scale a little bit # scale = (im1.shape[1] - 1) / (int(im1.shape[1] / scale) - 1) Dangle, Dscale = _get_precision(shape, scale) res = dict(scale=scale, angle=angle, tvec=tvec, Dscale=Dscale, Dangle=Dangle, Dt=0.5, success=succ) im2 = transform_img_dict(im1, res, bgval, order) # Order of mask should be always 1 - higher values produce strange results. imask = transform_img_dict(np.ones_like(im1), res, 0, 1) # This removes some weird artifacts imask[imask > 0.8] = 1.0 # Framing here = just blending the im2 with its BG according to the mask im2 = utils.frame_img(im2, imask, 10) res["timg"] = im2 return res
def similarity(im0, im1, numiter=1, order=3, constraints=None, filter_pcorr=0, exponent='inf'): """ Return similarity transformed image im1 and transformation parameters. Transformation parameters are: isotropic scale factor, rotation angle (in degrees), and translation vector. A similarity transformation is an affine transformation with isotropic scale and without shear. Args: im0 (2D numpy array): The first (template) image im1 (2D numpy array): The second (subject) image numiter (int): How many times to iterate when determining scale and rotation order (int): Order of approximation (when doing transformations). 1 = linear, 3 = cubic etc. filter_pcorr (int): Radius of a spectrum filter for translation detection exponent (float or 'inf'): The exponent value used during processing. Refer to the docs for a thorough explanation. Generally, pass "inf" when feeling conservative. Otherwise, experiment, values below 5 are not even supposed to work. Returns: dict: Contains following keys: ``scale``, ``angle``, ``tvec`` (Y, X), ``success`` and ``timg`` (the transformed subject image) .. note:: There are limitations * Scale change must be less than 2. * No subpixel precision (but you can use *resampling* to get around this). """ shape = im0.shape if shape != im1.shape: raise ValueError("Images must have same shapes.") elif im0.ndim != 2: raise ValueError("Images must be 2-dimensional.") # We are going to iterate and precise scale and angle estimates scale = 1.0 angle = 0.0 im2 = im1 if constraints is None: constraints = dict(angle=[0, None], scale=[1, None]) # During iterations, we have to work with constraints too. # So we make the copy in order to leave the original intact constraints_dynamic = constraints.copy() constraints_dynamic["scale"] = list(constraints["scale"]) constraints_dynamic["angle"] = list(constraints["angle"]) bgval = utils.get_borderval(im1, 5) for ii in range(numiter): newscale, newangle = _get_ang_scale([im0, im2], bgval, exponent, constraints_dynamic) scale *= newscale angle += newangle constraints_dynamic["scale"][0] /= newscale constraints_dynamic["angle"][0] -= newangle im2 = transform_img(im1, scale, angle, bgval=bgval, order=order) # Here we look how is the turn-180 target, stdev = constraints.get("angle", (0, None)) odds = _get_odds(angle, target, stdev) # now we can use pcorr to guess the translation tvec, succ, angle2 = _translation(im0, im2, filter_pcorr, odds, constraints) # The log-polar transform may have got the angle wrong by 180 degrees. # The phase correlation can help us to correct that angle += angle2 angle = utils.wrap_angle(angle, 360) # don't know what it does, but it alters the scale a little bit # scale = (im1.shape[1] - 1) / (int(im1.shape[1] / scale) - 1) Dangle, Dscale = _get_precision(shape, scale) res = dict( scale=scale, angle=angle, tvec=tvec, Dscale=Dscale, Dangle=Dangle, Dt=0.5, success=succ ) im2 = transform_img_dict(im1, res, bgval, order) # Order of mask should be always 1 - higher values produce strange results. imask = transform_img_dict(np.ones_like(im1), res, 0, 1) # This removes some weird artifacts imask[imask > 0.8] = 1.0 # Framing here = just blending the im2 with its BG according to the mask im2 = utils.frame_img(im2, imask, 10) res["timg"] = im2 return res