def _get_odds(angle, target, stdev): """ Determine whether we are more likely to choose the angle, or angle + 180° Args: angle (float, degrees): The base angle. target (float, degrees): The angle we think is the right one. Typically, we take this from constraints. stdev (float, degrees): The relevance of the target value. Also typically taken from constraints. Return: float: The greater the odds are, the higher is the preferrence of the angle + 180 over the original angle. Odds of -1 are the same as inifinity. """ ret = 1 if stdev is not None: diffs = [ abs(utils.wrap_angle(ang, 360)) for ang in (target - angle, target - angle + 180) ] odds0, odds1 = 0, 0 if stdev > 0: odds0, odds1 = [np.exp(-diff**2 / stdev**2) for diff in diffs] if odds0 == 0 and odds1 > 0: # -1 is treated as infinity in _translation ret = -1 elif stdev == 0 or (odds0 == 0 and odds1 == 0): ret = -1 if diffs[0] < diffs[1]: ret = 0 else: ret = odds1 / odds0 return ret
def _get_odds(angle, target, stdev): """ Args: Return: float: The greater the odds are, the higher is the preferrence of the angle + 180 over the original angle. Odds of -1 are the same as inifinity. """ ret = 1 if stdev is not None: diffs = [abs(utils.wrap_angle(ang, 360)) for ang in (target - angle, target - angle + 180)] odds0, odds1 = 0, 0 if stdev > 0: odds0, odds1 = [np.exp(- diff ** 2 / stdev ** 2) for diff in diffs] if odds0 == 0 and odds1 > 0: # -1 is treated as infinity in _translation ret = -1 elif stdev == 0 or (odds0 == 0 and odds1 == 0): ret = -1 if diffs[0] < diffs[1]: ret = 0 else: ret = odds1 / odds0 return ret
def _get_odds(angle, target, stdev): """ Determine whether we are more likely to choose the angle, or angle + 180° Args: angle (float, degrees): The base angle. target (float, degrees): The angle we think is the right one. Typically, we take this from constraints. stdev (float, degrees): The relevance of the target value. Also typically taken from constraints. Return: float: The greater the odds are, the higher is the preferrence of the angle + 180 over the original angle. Odds of -1 are the same as inifinity. """ ret = 1 if stdev is not None: diffs = [abs(utils.wrap_angle(ang, 360)) for ang in (target - angle, target - angle + 180)] odds0, odds1 = 0, 0 if stdev > 0: odds0, odds1 = [np.exp(- diff ** 2 / stdev ** 2) for diff in diffs] if odds0 == 0 and odds1 > 0: # -1 is treated as infinity in _translation ret = -1 elif stdev == 0 or (odds0 == 0 and odds1 == 0): ret = -1 if diffs[0] < diffs[1]: ret = 0 else: ret = odds1 / odds0 return ret
def _get_ang_scale(ims, bgval, exponent='inf', constraints=None, reports=None): """ Given two images, return their scale and angle difference. Args: ims (2-tuple-like of 2D ndarrays): The images bgval: We also pad here in the :func:`map_coordinates` exponent (float or 'inf'): The exponent stuff, see :func:`similarity` constraints (dict, optional) reports (optional) Returns: tuple: Scale, angle. Describes the relationship of the subject image to the first one. """ assert len(ims) == 2, \ "Only two images are supported as input" shape = ims[0].shape ims_apod = [utils._apodize(im) for im in ims] dfts = [ fft.fftshift( fft.fft2(im, threads=4, overwrite_input=True, auto_align_input=True, auto_contiguous=True, planner_effort='FFTW_ESTIMATE')) for im in ims_apod ] filt = _logpolar_filter(shape) dfts = [dft * filt for dft in dfts] # High-pass filtering used to be here, but we have moved it to a higher # level interface pcorr_shape = _get_pcorr_shape(shape) log_base = _get_log_base(shape, pcorr_shape[1]) stuffs = [_logpolar(np.abs(dft), pcorr_shape, log_base) for dft in dfts] (arg_ang, arg_rad), success = _phase_correlation(stuffs[0], stuffs[1], utils.argmax_angscale, log_base, exponent, constraints, reports) angle = -np.pi * arg_ang / float(pcorr_shape[0]) angle = np.rad2deg(angle) angle = utils.wrap_angle(angle, 360) scale = log_base**arg_rad angle = -angle scale = 1.0 / scale if not 0.5 < scale < 2: raise ValueError( "Images are not compatible. Scale change %g too big to be true." % scale) return scale, angle
def _get_odds(angle, target, stdev): """ Args: Return: float: The greater the odds are, the higher is the preferrence of the angle + 180 over the original angle. Odds of -1 are the same as inifinity. """ ret = 1 if stdev is not None: diffs = [ abs(utils.wrap_angle(ang, 360)) for ang in (target - angle, target - angle + 180) ] odds0, odds1 = 0, 0 if stdev > 0: odds0, odds1 = [np.exp(-diff**2 / stdev**2) for diff in diffs] if odds0 == 0 and odds1 > 0: # -1 is treated as infinity in _translation ret = -1 elif stdev == 0 or (odds0 == 0 and odds1 == 0): ret = -1 if diffs[0] < diffs[1]: ret = 0 else: ret = odds1 / odds0 return ret
def _get_ang_scale(ims, bgval, exponent='inf', constraints=None): """ Given two images, return their scale and angle difference. Args: ims (2-tuple-like of 2D ndarrays): The images bgval: We also pad here in the :func:`map_coordinates` exponent (float or 'inf'): The exponent stuff, see :func:`similarity` Returns: tuple: Scale, angle. Describes the relationship of the subject image to the first one. """ assert len(ims) == 2, \ "Only two images are supported as input" shape = ims[0].shape adfts = [fft.fftshift(abs(fft.fft2(im))) for im in ims] adfts = [_logpolar_filter(adft) for adft in adfts] # High-pass filtering used to be here, but we have moved it to a higher # level interface pcorr_shape = _get_pcorr_shape(shape) log_base = _get_log_base(shape, pcorr_shape[1]) stuffs = [_logpolar(adft, pcorr_shape, log_base, 0.0) for adft in adfts] if 0: import pylab as pyl pyl.figure() pyl.imshow(ims[0]) pyl.figure() pyl.imshow(ims[1]) pyl.show() (arg_ang, arg_rad), success = _phase_correlation(stuffs[0], stuffs[1], utils.argmax_angscale, log_base, exponent, constraints) angle = -np.pi * arg_ang / float(pcorr_shape[0]) angle = np.rad2deg(angle) angle = utils.wrap_angle(angle, 360) scale = log_base**arg_rad if not 0.5 < scale < 2: raise ValueError( "Images are not compatible. Scale change %g too big to be true." % scale) return 1.0 / scale, -angle
def _get_ang_scale(ims, bgval, exponent='inf', constraints=None): """ Given two images, return their scale and angle difference. Args: ims (2-tuple-like of 2D ndarrays): The images bgval: We also pad here in the :func:`map_coordinates` exponent (float or 'inf'): The exponent stuff, see :func:`similarity` Returns: tuple: Scale, angle. Describes the relationship of the subject image to the first one. """ assert len(ims) == 2, \ "Only two images are supported as input" shape = ims[0].shape adfts = [fft.fftshift(abs(fft.fft2(im))) for im in ims] adfts = [_logpolar_filter(adft) for adft in adfts] # High-pass filtering used to be here, but we have moved it to a higher # level interface pcorr_shape = _get_pcorr_shape(shape) log_base = _get_log_base(shape, pcorr_shape[1]) stuffs = [_logpolar(adft, pcorr_shape, log_base, 0.0) for adft in adfts] if 0: import pylab as pyl pyl.figure(); pyl.imshow(ims[0]); pyl.figure(); pyl.imshow(ims[1]); pyl.show() (arg_ang, arg_rad), success = _phase_correlation( stuffs[0], stuffs[1], utils.argmax_angscale, log_base, exponent, constraints) angle = -np.pi * arg_ang / float(pcorr_shape[0]) angle = np.rad2deg(angle) angle = utils.wrap_angle(angle, 360) scale = log_base ** arg_rad if not 0.5 < scale < 2: raise ValueError( "Images are not compatible. Scale change %g too big to be true." % scale) return 1.0 / scale, - angle
def _get_ang_scale(ims, bgval, exponent='inf', constraints=None, reports=None): """ Given two images, return their scale and angle difference. Args: ims (2-tuple-like of 2D ndarrays): The images bgval: We also pad here in the :func:`map_coordinates` exponent (float or 'inf'): The exponent stuff, see :func:`similarity` constraints (dict, optional) reports (optional) Returns: tuple: Scale, angle. Describes the relationship of the subject image to the first one. """ assert len(ims) == 2, \ "Only two images are supported as input" shape = ims[0].shape ims_apod = [utils._apodize(im) for im in ims] dfts = [fft.fftshift(fft.fft2(im)) for im in ims_apod] filt = _logpolar_filter(shape) dfts = [dft * filt for dft in dfts] # High-pass filtering used to be here, but we have moved it to a higher # level interface pcorr_shape = _get_pcorr_shape(shape) log_base = _get_log_base(shape, pcorr_shape[1]) stuffs = [_logpolar(np.abs(dft), pcorr_shape, log_base) for dft in dfts] (arg_ang, arg_rad), success = _phase_correlation(stuffs[0], stuffs[1], utils.argmax_angscale, log_base, exponent, constraints, reports) angle = -np.pi * arg_ang / float(pcorr_shape[0]) angle = np.rad2deg(angle) angle = utils.wrap_angle(angle, 360) scale = log_base**arg_rad angle = -angle scale = 1.0 / scale if reports is not None: reports["shape"] = filt.shape reports["base"] = log_base if reports.show("spectra"): reports["dfts_filt"] = dfts if reports.show("inputs"): reports["ims_filt"] = [ fft.ifft2(np.fft.ifftshift(dft)) for dft in dfts ] if reports.show("logpolar"): reports["logpolars"] = stuffs if reports.show("scale_angle"): reports["amas-result-raw"] = (arg_ang, arg_rad) reports["amas-result"] = (scale, angle) reports["amas-success"] = success extent_el = pcorr_shape[1] / 2.0 reports["amas-extent"] = (log_base**(-extent_el), log_base**extent_el, -90, 90) if not 0.5 < scale < 2: raise ValueError( "Images are not compatible. Scale change %g too big to be true." % scale) return scale, angle
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 _get_ang_scale(ims, bgval, exponent='inf', constraints=None, reports=None): """ Given two images, return their scale and angle difference. Args: ims (2-tuple-like of 2D ndarrays): The images bgval: We also pad here in the :func:`map_coordinates` exponent (float or 'inf'): The exponent stuff, see :func:`similarity` constraints (dict, optional) reports (optional) Returns: tuple: Scale, angle. Describes the relationship of the subject image to the first one. """ assert len(ims) == 2, \ "Only two images are supported as input" shape = ims[0].shape ims_apod = [utils._apodize(im) for im in ims] dfts = [fft.fftshift(fft.fft2(im)) for im in ims_apod] filt = _logpolar_filter(shape) dfts = [dft * filt for dft in dfts] # High-pass filtering used to be here, but we have moved it to a higher # level interface pcorr_shape = _get_pcorr_shape(shape) log_base = _get_log_base(shape, pcorr_shape[1]) stuffs = [_logpolar(np.abs(dft), pcorr_shape, log_base) for dft in dfts] (arg_ang, arg_rad), success = _phase_correlation( stuffs[0], stuffs[1], utils.argmax_angscale, log_base, exponent, constraints, reports) angle = -np.pi * arg_ang / float(pcorr_shape[0]) angle = np.rad2deg(angle) angle = utils.wrap_angle(angle, 360) scale = log_base ** arg_rad angle = - angle scale = 1.0 / scale if reports is not None: reports["shape"] = filt.shape reports["base"] = log_base if reports.show("spectra"): reports["dfts_filt"] = dfts if reports.show("inputs"): reports["ims_filt"] = [fft.ifft2(np.fft.ifftshift(dft)) for dft in dfts] if reports.show("logpolar"): reports["logpolars"] = stuffs if reports.show("scale_angle"): reports["amas-result-raw"] = (arg_ang, arg_rad) reports["amas-result"] = (scale, angle) reports["amas-success"] = success extent_el = pcorr_shape[1] / 2.0 reports["amas-extent"] = ( log_base ** (-extent_el), log_base ** extent_el, -90, 90 ) if not 0.5 < scale < 2: raise ValueError( "Images are not compatible. Scale change %g too big to be true." % scale) return scale, angle
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