Exemple #1
0
def run_order_odd(method, tol):
    """
    Test cossin distributions including odd orders using Gaussian peaks.

    tol = list of tolerances for order=0, order=1, order=2, ...
    """
    sigma = 5.0  # peak SD
    step = 6 * sigma  # distance between peak centers

    for n in range(0, len(tol)):  # order = n
        size = int((n + 2) * step)
        # coordinates:
        x = np.arange(float(size))
        y = size - np.arange(2 * float(size) + 1)[:, None]
        # radius
        r = np.sqrt(x**2 + y**2)
        # cos, sin
        r[size, 0] = np.inf
        c = y / r
        s = x / r
        r[size, 0] = 0

        # Gaussian peak with one cossin angular term
        def peak(i):
            m = i  # cos power
            k = (n - m) & ~1  # sin power (round down to even)
            return c ** m * s ** k * \
                   np.exp(-(r - (i + 1) * step) ** 2 / (2 * sigma**2))

        # quadrant with all peaks
        Q = peak(0)
        for i in range(1, n + 1):
            Q += peak(i)

        for rmax in ['MIN', 'all']:
            param = '-> rmax = {}, order={}, method = {}'.\
                    format(rmax, n, method)
            res = Distributions((int(size), 0),
                                rmax,
                                n,
                                odd=True,
                                method=method).image(Q)
            cossin = res.cossin()
            # extract values at peak centers
            cs = np.array(
                [cossin[:, int((i + 1) * step)] for i in range(n + 1)])
            assert_allclose(cs, np.identity(n + 1), atol=tol[n], err_msg=param)
            # test that Ibeta, and thus harmonics, work in principle
            res.Ibeta()
Exemple #2
0
def run_order(method, tol):
    """
    Test cossin distributions for even orders using Gaussian peaks.

    tol = list of tolerances for order=0, order=2, order=4, ...
    """
    sigma = 5.0  # peak SD
    step = 6 * sigma  # distance between peak centers

    for n in range(len(tol)):  # order = 2n
        size = int((n + 2) * step)
        # squared coordinates:
        x2 = np.arange(float(size))**2
        y2 = x2[:, None]
        r2 = x2 + y2
        # cos^2, sin^2
        c2 = np.divide(y2, r2, out=np.zeros_like(r2), where=r2 != 0)
        s2 = 1 - c2
        # radius
        r = np.sqrt(r2)

        # Gaussian peak with one cossin angular term.
        def peak(i):
            return c2 ** (n - i) * s2 ** i * \
                   np.exp(-(r - (i + 1) * step) ** 2 / (2 * sigma**2))

        # quadrant with all peaks
        Q = peak(0)
        for i in range(1, n + 1):
            Q += peak(i)

        for rmax in ['MIN', 'all']:
            param = '-> rmax = {}, order={}, method = {}'.\
                    format(rmax, 2 * n, method)
            res = Distributions('ul', rmax, 2 * n, method=method).image(Q)
            cossin = res.cossin()
            # extract values at peak centers
            cs = np.array([cossin[:, int((i + 1) * step)]
                           for i in range(n + 1)])
            assert_allclose(cs, np.identity(n + 1), atol=tol[n],
                            err_msg=param)
            # test that Ibeta, and thus harmonics, work in principle
            res.Ibeta()
Exemple #3
0
def _profiles(IM, origin, rmax, order, odd, weights, verbose):
    """
    Get radial profiles of cos^n theta terms from the input image.
    """
    # the Distributions object is cached to speed up further calculations,
    # plus its cos^n theta matrices are used later to construct the transformed
    # image
    global _prm, _weights, _dst, _ibs, _trf, _tri_prm, _tri

    old_valid = None if _dst is None else _dst.valid

    if verbose:
        print('Extracting radial profiles...')
    prm = [IM.shape, origin, rmax, order, odd]
    if _prm != prm or _weights is not weights:
        _prm = prm
        _weights = weights
        _dst = Distributions(origin=origin,
                             rmax=rmax,
                             order=order,
                             odd=odd,
                             weights=weights,
                             use_sin=False,
                             method='linear')
        if verbose:
            print('(new Distributions object created)')
        # reset image basis
        _ibs = None
    else:
        if verbose:
            print('(reusing cached Distributions object)')

    c = _dst(IM).cos()

    if not np.array_equal(_dst.valid, old_valid):
        # reset transforms
        _trf = None
        _tri_prm = None
        _tri = None

    return c
Exemple #4
0
def run_method_odd(method,
                   rmax,
                   tolP0,
                   tolP1,
                   tolP2,
                   tolI,
                   tolbeta1,
                   tolbeta2,
                   weq=True):
    """
    Test harmonics and Ibeta for various combinations of origins and weights
    for default order=2, but with odd=True.

    method = method name
    rmax = 'MIN' or 'all'
    tol... = (atol, rmstol) for ...
    tolbeta = atol for beta
    weq = compare symbolic and array weights
    """
    # image size
    n = 81  # width
    m = 91  # height
    # origin coordinates
    xc = [0, 30, n // 2, 50, n - 1]
    yc = [0, 25, m // 2, 65, m - 1]
    # peak SD
    sigma = 2.0

    # Gaussian peak.
    def peak(i, r):
        return np.exp(-(r - i * step)**2 / (2 * sigma**2))

    # Test image.
    def image():
        # coordinates:
        x = np.arange(float(n)) - x0
        y = y0 - np.arange(float(m))[:, None]
        # radius:
        r = np.sqrt(x**2 + y**2)
        # cos, |sin|
        r[y0, x0] = np.inf
        c = y / r
        s = np.abs(x) / r
        s[y0, x0] = 1
        r[y0, x0] = 0
        # image: 4 peaks with different anisotropies
        IM = s**2      * peak(1, r) + \
             c**2      * peak(2, r) + \
             (1/2 + c) * peak(3, r) + \
                         peak(4, r)
        return IM, s  # image, sin theta

    # Reference distribution for test image.
    def ref_distr():
        r = np.arange(R + 1)
        P0 = 2/3 * peak(1, r) + \
             1/3 * peak(2, r) + \
             1/2 * peak(3, r) + \
                   peak(4, r)
        P1 = peak(3, r)
        P2 = -2/3 * peak(1, r) + \
              2/3 * peak(2, r)
        I = 4 * np.pi * r**2 * P0
        beta1 = P1 / P0
        beta2 = P2 / P0
        return r, P0, P1, P2, I, beta1, beta2

    for y0, x0 in itertools.product(yc, xc):
        param = ' @ y0 = {}, x0 = {}, rmax = {}, method = {}'.\
                format(y0, x0, rmax, method)

        # determine largest radius extracted from image
        if rmax == 'MIN':
            R = min(max(x0, n - 1 - x0), max(y0, m - 1 - y0))
        elif rmax == 'MAX':
            # exclude situations when the outer ring has insufficient vertical
            # span for reliable even/odd separation
            if y0 in [0, m - 1] and x0 not in [0, n - 1] or \
               x0 == n // 2 and abs(y0 - m // 2) not in [0, m // 2]:
                continue
            R = max(max(x0, n - 1 - x0), max(y0, m - 1 - y0))
        step = (R - 5 * sigma) / 4  # distance between peak centers
        refr, refP0, refP1, refP2, refI, refbeta1, refbeta2 = ref_distr()
        f = 1 / (4 * np.pi * (1 + refr**2))  # rescaling factor for I

        IM, ws = image()
        w1 = np.ones_like(IM)

        IMcopy = IM.copy()
        w1copy = w1.copy()
        wscopy = ws.copy()

        weights = [(False, None, None), (True, None, None), (False, '1', w1),
                   (False, 'sin', ws), (True, '1', w1)]
        P0, P1, P2, r, I, beta1, beta2 = {}, {}, {}, {}, {}, {}, {}
        for use_sin, wname, warray in weights:
            weight_param = param + \
                           ', sin = {}, weights = {}'.format(use_sin, wname)
            key = (use_sin, wname)

            distr = Distributions((y0, x0),
                                  rmax,
                                  odd=True,
                                  use_sin=use_sin,
                                  weights=warray,
                                  method=method)
            res = distr(IM)
            P0[key], P1[key], P2[key] = res.harmonics()
            r[key], I[key], beta1[key], beta2[key] = res.rIbeta()

            def assert_cmp(msg, a, ref, tol):
                atol, rmstol = tol
                assert_allclose(a, ref, atol=atol, err_msg=msg + weight_param)
                rms = np.sqrt(np.mean((a - ref)**2))
                assert rms < rmstol, \
                       '\n' + msg + weight_param + \
                       '\nRMS error = {} > {}'.format(rms, rmstol)

            assert_cmp('-> P0', P0[key], refP0, tolP0)
            assert_cmp('-> P1', P1[key], refP1, tolP1)
            assert_cmp('-> P2', P2[key], refP2, tolP2)

            assert_equal(r[key], refr, err_msg='-> r' + weight_param)
            assert_cmp('-> I', f * I[key], f * refI, tolI)
            # beta values at peak centers
            b1 = [round(beta1[key][int(i * step)], 5) for i in (1, 2, 3, 4)]
            assert_allclose(b1, [0, 0, 2, 0],
                            atol=tolbeta1,
                            err_msg='-> beta1' + weight_param)
            b2 = [round(beta2[key][int(i * step)], 5) for i in (1, 2, 3, 4)]
            assert_allclose(b2, [-1, 2, 0, 0],
                            atol=tolbeta2,
                            err_msg='-> beta2' + weight_param)

            assert_equal(IM, IMcopy, err_msg='-> IM corrupted' + weight_param)
            assert_equal(w1,
                         w1copy,
                         err_msg='-> weights corrupted' + weight_param)
            assert_equal(ws,
                         wscopy,
                         err_msg='-> weights corrupted' + weight_param)

        if not weq:
            continue
        # check that results for symbolic and explicit weights match
        for key1, key2 in [((False, '1'), (False, None)),
                           ((False, 'sin'), (True, None)),
                           ((True, '1'), (False, 'sin'))]:
            pair_param = param + ', sin + weights {} != {}'.format(key1, key2)
            assert_allclose(P0[key1], P0[key2], err_msg='-> P0' + pair_param)
            assert_allclose(P1[key1], P1[key2], err_msg='-> P1' + pair_param)
            assert_allclose(P2[key1], P2[key2], err_msg='-> P2' + pair_param)
            assert_allclose(I[key1], I[key2], err_msg='-> I' + pair_param)
            assert_allclose(beta1[key1],
                            beta1[key2],
                            err_msg='-> beta1' + pair_param)
            assert_allclose(beta2[key1],
                            beta2[key2],
                            err_msg='-> beta2' + pair_param)
Exemple #5
0
def run_method(method, rmax, tolP0, tolP2, tolI, tolbeta, weq=True):
    """
    Test harmonics and Ibeta for various combinations of origins and weights
    for default order=2, odd=False.

    method = method name
    rmax = 'MIN' or 'all'
    tol... = (atol, rmstol) for ...
    tolbeta = atol for beta
    weq = compare symbolic and array weights
    """
    # image size
    n = 81  # width
    m = 71  # height
    # origin coordinates
    xc = [0, 20, n // 2, 60, n - 1]
    yc = [0, 25, m // 2, 45, m - 1]
    # peak SD
    sigma = 2.0

    # Gaussian peak.
    def peak(i, r):
        return np.exp(-(r - i * step)**2 / (2 * sigma**2))

    # Test image.
    def image():
        # squared coordinates:
        x2 = (np.arange(float(n)) - x0)**2
        y2 = (np.arange(float(m))[:, None] - y0)**2
        r2 = x2 + y2
        # radius:
        r = np.sqrt(r2)
        # cos^2, sin^2:
        c2 = np.divide(y2, r2, out=np.zeros_like(r2), where=r2 != 0)
        s2 = 1 - c2
        # image: 3 peaks with different anisotropies
        IM = s2 * peak(1, r) + \
                  peak(2, r) + \
             c2 * peak(3, r)
        return IM, np.sqrt(s2)  # image, sin theta

    # Reference distribution for test image.
    def ref_distr():
        r = np.arange(R + 1)
        P0 = 2/3 * peak(1, r) + \
                   peak(2, r) + \
             1/3 * peak(3, r)
        P2 = -2/3 * peak(1, r) + \
              2/3 * peak(3, r)
        I = 4 * np.pi * r**2 * P0
        beta = P2 / P0
        return r, P0, P2, I, beta

    for y0, x0 in itertools.product(yc, xc):
        param = ' @ y0 = {}, x0 = {}, rmax = {}, method = {}'.\
                format(y0, x0, rmax, method)

        # determine largest radius extracted from image
        if rmax == 'MIN':
            R = min(max(x0, n - 1 - x0), max(y0, m - 1 - y0))
        elif rmax == 'all':
            R = max([
                int(np.sqrt((x - x0)**2 + (y - y0)**2)) for x in (0, n - 1)
                for y in (0, m - 1)
            ])
        step = (R - 5 * sigma) / 3  # distance between peak centers
        refr, refP0, refP2, refI, refbeta = ref_distr()
        f = 1 / (4 * np.pi * (1 + refr**2))  # rescaling factor for I

        IM, ws = image()
        w1 = np.ones_like(IM)

        IMcopy = IM.copy()
        w1copy = w1.copy()
        wscopy = ws.copy()

        weights = [(False, None, None), (True, None, None), (False, '1', w1),
                   (False, 'sin', ws), (True, '1', w1)]
        P0, P2, r, I, beta = {}, {}, {}, {}, {}
        for use_sin, wname, warray in weights:
            weight_param = param + \
                           ', sin = {}, weights = {}'.format(use_sin, wname)
            key = (use_sin, wname)

            distr = Distributions((y0, x0),
                                  rmax,
                                  use_sin=use_sin,
                                  weights=warray,
                                  method=method)
            res = distr(IM)
            P0[key], P2[key] = res.harmonics()
            r[key], I[key], beta[key] = res.rIbeta()

            def assert_cmp(msg, a, ref, tol):
                atol, rmstol = tol
                assert_allclose(a, ref, atol=atol, err_msg=msg + weight_param)
                rms = np.sqrt(np.mean((a - ref)**2))
                assert rms < rmstol, \
                       '\n' + msg + weight_param + \
                       '\nRMS error = {} > {}'.format(rms, rmstol)

            assert_cmp('-> P0', P0[key], refP0, tolP0)
            assert_cmp('-> P2', P2[key], refP2, tolP2)

            assert_equal(r[key], refr, err_msg='-> r' + weight_param)
            assert_cmp('-> I', f * I[key], f * refI, tolI)
            # beta values at peak centers
            b = [round(beta[key][int(i * step)], 5) for i in (1, 2, 3)]
            assert_allclose(b, [-1, 0, 2],
                            atol=tolbeta,
                            err_msg='-> beta' + weight_param)

            assert_equal(IM, IMcopy, err_msg='-> IM corrupted' + weight_param)
            assert_equal(w1,
                         w1copy,
                         err_msg='-> weights corrupted' + weight_param)
            assert_equal(ws,
                         wscopy,
                         err_msg='-> weights corrupted' + weight_param)

        if not weq:
            continue
        # check that results for symbolic and explicit weights match
        for key1, key2 in [((False, '1'), (False, None)),
                           ((False, 'sin'), (True, None)),
                           ((True, '1'), (False, 'sin'))]:
            pair_param = param + ', sin + weights {} != {}'.format(key1, key2)
            assert_allclose(P0[key1], P0[key2], err_msg='-> P0' + pair_param)
            assert_allclose(P2[key1], P2[key2], err_msg='-> P2' + pair_param)
            assert_allclose(I[key1], I[key2], err_msg='-> I' + pair_param)
            assert_allclose(beta[key1],
                            beta[key2],
                            err_msg='-> beta' + pair_param)
Exemple #6
0
def rbasex_transform(IM,
                     origin='center',
                     rmax='MIN',
                     order=2,
                     odd=False,
                     weights=None,
                     direction='inverse',
                     reg=None,
                     out='same',
                     basis_dir=None,
                     verbose=False):
    r"""
    This function takes the input image and outputs its forward or inverse Abel
    transform as an image and its radial distributions.

    The **origin**, **rmax**, **order**, **odd** and **weights** parameters are
    passed to :class:`abel.tools.vmi.Distributions`, so see its documentation
    for their detailed descriptions.

    Parameters
    ----------
    IM : m × n numpy array
        the image to be transformed
    origin : tuple of int or str
        image origin, explicit in the (row, column) format, or as a location
        string (by default, the image center)
    rmax : int or string
        largest radius to include in the transform (by default, the largest
        radius with at least one full quadrant of data)
    order : int
        highest angular order present in the data, ≥ 0 (by default, 2)
    odd : bool
        include odd angular orders (by default is `False`, but is enabled
        automatically if **order** is odd)
    weights : m × n numpy array, optional
        weighting factors for each pixel. The array shape must match the image
        shape. Parts of the image can be excluded from analysis by assigning
        zero weights to their pixels. By default is `None`, which applies equal
        weight to all pixels.
    direction : str: ``'forward'`` or ``'inverse'``
        type of Abel transform to be performed (by default, inverse)
    reg : None or str or tuple (str, float), optional
        regularization to use for inverse Abel transform. ``None`` (default)
        means no regularization, a string selects a non-parameterized
        regularization method, and parameterized methods are selected by a
        tuple (`method`, `strength`). Available methods are:

        ``('L2', strength)``:
            Tikhonov :math:`L_2` regularization with `strength` as the square
            of the Tikhonov factor. This is the same as “Tikhonov
            regularization” used in BASEX, with almost identical effects on the
            radial distributions.
        ``('diff', strength)``:
            Tikhonov regularization with the difference operator (approximation
            of the derivative) multiplied by the square root of `strength` as
            the Tikhonov matrix. This tends to produce less blurring, but more
            negative overshoots than ``'L2'``.
        ``('SVD', strength)``:
            truncated SVD (singular value decomposition) with
            N = `strength` × **rmax** largest singular values removed for each
            angular order. This mimics the approach proposed (but in fact not
            used) in pBasex. `Not recommended` due to generally poor results.
        ``'pos'``:
            non-parameterized method, finds the best (in the least-squares
            sense) solution with non-negative :math:`\cos^n\theta \sin^m\theta`
            terms (see :meth:`~abel.tools.vmi.Distributions.Results.cossin`).
            For **order** = 0, 1, and 2 (with **odd** = `False`) this is
            equivalent to :math:`I(r, \theta) \geqslant 0`; for higher orders
            this assumption is stronger than :math:`I \geqslant 0` and
            corresponds to no interference between different multiphoton
            channels. Not implemented for odd orders > 1.

            Notice that this method is nonlinear, which also means that it is
            considerably slower than the linear methods and the transform
            operator cannot be cached.

        In all cases, `strength` = 0 provides no regularization. For the
        Tikhonov methods, `strength` ~ 100 is a reasonable value for megapixel
        images. For truncated SVD, `strength` must be < 1; `strength` ~ 0.1 is
        a reasonable value; `strength` ~ 0.5 can produce noticeable ringing
        artifacts. See the :ref:`full description <rBasexmathreg>` and examples
        there.
    out : str or None
        shape of the output image:

        ``'same'`` (default):
            same shape and origin as the input
        ``'fold'`` (fastest):
            Q0 (upper right) quadrant (for ``odd=False``) or right half (for
            ``odd=True``) up to **rmax**, but limited to the largest
            input-image quadrant (or half)
        ``'unfold'``:
            like ``'fold'``, but symmetrically “unfolded” to all 4 quadrants
        ``'full'``:
            all pixels with radii up to **rmax**
        ``'full-unique'``:
            the unique part of ``'full'``: Q0 (upper right) quadrant for
            ``odd=False``, right half for ``odd=True``
        ``None``:
            no image (**recon** will be ``None``). Can be useful to avoid
            unnecessary calculations when only the transformed radial
            distributions (**distr**) are needed.
    basis_dir : str, optional
        path to the directory for saving / loading the basis set (useful only
        for the inverse transform without regularization; time savings in other
        cases are small and might be negated by the disk-access overhead).
        If ``None`` (default), the basis set will not be loaded from or saved
        to disk.
    verbose : bool
        print information about processing (for debugging), disabled by default

    Returns
    -------
    recon : 2D numpy array or None
        the transformed image. Is centered and might have different dimensions
        than the input image.
    distr : Distributions.Results object
        the object from which various distributions for the transformed image
        can be retrieved, see :class:`abel.tools.vmi.Distributions.Results`
    """
    if order == 0:
        odd = False  # (to eliminate additional checks)
    elif order % 2:
        odd = True  # enable automatically for odd orders

    # extract radial profiles from input image
    p = _profiles(IM, origin, rmax, order, odd, weights, verbose)
    # (caches Distributions as _dst)

    Rmax = _dst.rmax

    # get appropriate transform matrices
    A = get_bs_cached(Rmax, order, odd, direction, reg, _dst.valid, basis_dir,
                      verbose)

    # transform radial profiles
    if reg == 'pos':
        if verbose:
            print('Solving NNLS equations...')
        N = len(p)
        p = np.hstack(p)
        cs = nnls(A, p)[0]
        cs = np.split(cs, N)
        if odd:
            # (1 ± cos) / 2 → cos^0, cos^1
            c = [cs[0] + cs[1], cs[0] - cs[1]]
        else:
            # cossin → cos transform
            C = np.flip(invpascal(N, 'upper'))
            c = C.dot(cs)
    else:
        if verbose:
            print('Applying radial transforms...')
        c = [An.dot(pn) for An, pn in zip(A, p)]

    # construct output (transformed) distributions
    distr = Distributions.Results(np.arange(Rmax + 1), np.array(c), order, odd,
                                  _dst.valid)

    if out is None:
        return None, distr

    # output size
    if out == 'same':
        height = _dst.shape[0] if odd else _dst.VER + 1
        width = _dst.HOR + 1
        row = _dst.row if odd else 0
    elif out in ['fold', 'unfold']:
        height = _dst.Qheight
        width = _dst.Qwidth
        row = _dst.row if odd else 0
    elif out in ['full', 'full-unique']:
        height = 2 * Rmax + 1 if odd else Rmax + 1
        width = Rmax + 1
        row = Rmax if odd else 0
    else:
        raise ValueError('Wrong output shape "{}"'.format(out))
    # construct output image from transformed radial profiles
    if verbose:
        print('Constructing output image...')
    # bottom right quadrant or right half
    recon = _image(height, width, row, c, verbose)
    if odd:
        if out not in ['fold', 'full-unique']:
            # combine with left half (mirrored without central column)
            recon = np.hstack((recon[:, :0:-1], recon))
    else:  # even only
        recon = recon[::-1]  # flip to Q0
        if out not in ['fold', 'full-unique']:
            # assemble full image
            recon = put_image_quadrants((recon, recon, recon, recon),
                                        (2 * height - 1, 2 * width - 1))
    if out == 'same':
        # crop as needed
        row = 0 if odd else _dst.VER - _dst.row
        col = _dst.HOR - _dst.col
        H, W = IM.shape
        recon = recon[row:row + H, col:col + W]

    return recon, distr
Exemple #7
0
    def __init__(self,
                 n=[301, 501],
                 shape='half',
                 rmax='MIN',
                 order=2,
                 weight=['none', 'sin', 'sin+array'],
                 method='all',
                 repeat=1,
                 t_min=0.1):
        self.n = _ensure_list(n)

        if shape == 'Q':
            origin = 'll'
            self.shape = 'One quadrant'
        elif shape == 'half':
            origin = 'cl'
            self.shape = 'Half image'
        elif shape == 'full':
            origin = 'cc'
            self.shape = 'Full image'
        else:
            raise ValueError('Incorrect shape "{}"'.format(shape))

        self.rmaxs = rmaxs = _ensure_list(rmax)

        self.order = order

        weights = _ensure_list(weight)
        if 'all' in weights:
            weights = ['none', 'sin', 'array', 'sin+array']
        self.weights = weights

        methods = _ensure_list(method)
        if 'all' in methods:
            methods = ['nearest', 'linear', 'remap']
        self.methods = methods

        # create the timing function
        time = Timent(skip=-1, repeat=repeat, duration=t_min).time

        # dictionary for the results
        self.results = {
            m: {r: {w: []
                    for w in weights}
                for r in rmaxs}
            for m in methods
        }

        from abel.tools.vmi import Ibeta, Distributions
        # make sure that everything is loaded
        # (otherwise the 1st timing call is very slow)
        Ibeta(np.array([[0]]))

        # Loop over all image sizes
        for ni in self.n:
            ni = int(ni)

            # create image and weight array
            rows = (ni + 1) // 2 if shape == 'Q' else ni
            cols = (ni + 1) // 2 if shape in ['Q', 'half'] else ni
            IM = np.random.randn(rows, cols)
            warray = np.random.randn(rows, cols)

            # benchmark each combination of parameters
            for method in methods:
                for rmax in rmaxs:
                    for weight in weights:
                        if weight == 'none':
                            w = {'use_sin': False, 'weights': None}
                        elif weight == 'sin':
                            w = {'use_sin': True, 'weights': None}
                        elif weight == 'array':
                            w = {'use_sin': False, 'weights': warray}
                        elif weight == 'sin+array':
                            w = {'use_sin': True, 'weights': warray}
                        else:
                            raise ValueError(
                                'Incorrect weight "{}"'.format(weight))
                        # single-image
                        t1 = time(Ibeta,
                                  IM,
                                  origin,
                                  rmax,
                                  order,
                                  method=method,
                                  **w)
                        # cached
                        distr = Distributions(origin,
                                              rmax,
                                              order,
                                              method=method,
                                              **w)
                        distr(IM)  # trigger precalculations

                        def distrIMIbeta(IM):
                            return distr(IM).Ibeta()

                        tn = time(distrIMIbeta, IM)
                        # save results
                        self.results[method][rmax][weight].append(
                            (t1 * 1000, tn * 1000))