Exemple #1
0
def subPixelPeak(inp, k=1, ignore_border=1):
    from pytom.voltools import transform
    is2d = len(inp.shape.squeeze()) == 2
    if is2d:
        inp2 = inp.squeeze()[ignore_border:-ignore_border,
                             ignore_border:-ignore_border]
        scale = (k, k, 1)
        out = xp.array(inp.squeeze(), dtype=xp.float32)
        x, y = xp.array(xp.unravel_index(inp2.argmax(),
                                         inp2.shape)) + ignore_border
        out = xp.expand_dims(out, 2)
        translation = [inp.shape[0] // 2 - x, inp.shape[1] // 2 - y, 0]
    else:
        inp2 = inp[ignore_border:-ignore_border, ignore_border:-ignore_border,
                   ignore_border:-ignore_border]
        scale = (k, k, k)
        out = xp.array(inp, dtype=xp.float32)
        x, y, z = xp.array(xp.unravel_index(inp2.argmax(),
                                            inp2.shape)) + ignore_border
        translation = [
            inp.shape[0] // 2 - x, inp.shape[1] // 2 - y, inp.shape[2] // 2 - z
        ]

    zoomed = xp.zeros_like(out, dtype=xp.float32)
    transform(out,
              output=zoomed,
              scale=scale,
              translation=translation,
              device='gpu:0',
              interpolation='filt_bspline')

    shifts = [x2, y2, z2] = xp.unravel_index(zoomed.argmax(), zoomed.shape)

    return [zoomed[x2, y2, z2], shifts[3 - is2d]]
Exemple #2
0
def find_sub_pixel_max_value_voltools(inp, k=.1, ignore_border=50):
    """
    To find the highest point in a 2D array, with subpixel accuracy based on 1D spline interpolation.
    The algorithm is based on a matlab script "tom_peak.m"

    @param inp: A 2D numpy array containing the data points.
    @type inp: numpy array 2D
    @param k: The smoothing factor used in the spline interpolation, must be 1 <= k <= 5.
    @type k: int
    @return: A list of all points of maximal value in the structure of tuples with the x position, the y position and
        the value.
    @returntype: list
    """
    # assert len(ixp.shape) == 2
    # assert isinstance(k, int) and 1 <= k <= 5

    import cupy
    import numpy as xp
    from scipy.interpolate import InterpolatedUnivariateSpline
    from pytom.voltools import transform

    inp2 = inp[ignore_border:-ignore_border, ignore_border:-ignore_border]

    out = cupy.array(inp, dtype=cupy.float32)
    x, y = xp.array(xp.unravel_index(inp2.argmax(),
                                     inp2.shape)) + ignore_border

    out = cupy.expand_dims(out, 2)
    zoomed = cupy.zeros_like(out, dtype=cupy.float32)
    transform(out,
              output=zoomed,
              scale=(k, k, 1),
              translation=[inp.shape[0] // 2 - x, inp.shape[1] // 2 - y, 0],
              device='gpu:0',
              interpolation='filt_bspline')

    z = zoomed.get().squeeze()

    x2, y2 = xp.unravel_index(z.argmax(), z.shape)
    return [
        x + (x2 - z.shape[0] // 2) * k - z.shape[0] // 2,
        y + (y2 - z.shape[1] // 2) * k - z.shape[0] // 2, z, x, y, x2, y2
    ]
Exemple #3
0
def maxIndex(volume, num_threads=1024):
    nblocks = int(xp.ceil(volume.size / num_threads / 2))
    fast_sum = -1000000 * xp.ones((nblocks), dtype=xp.float32)
    max_id = xp.zeros((nblocks), dtype=xp.int32)
    argmax((
        nblocks,
        1,
    ), (num_threads, 1, 1), (volume, fast_sum, max_id, volume.size),
           shared_mem=16 * num_threads)
    mm = min(max_id[fast_sum.argmax()], volume.size - 1)
    indices = xp.unravel_index(mm, volume.shape)
    return indices
def find_sub_pixel_voltools(inp, k=0.1, border=75, max_shift=15):
    import pytom.voltools as vt
    inp = xp.ascontiguousarray(inp)

    # find the position of the initial maximum
    dimx, dimy = inp.squeeze().shape
    initial_max = xp.unravel_index(
        inp[border:-border, border:-border].argmax(),
        (dimx - border * 2, dimy - border * 2))
    ix, iy = [v + border for v in initial_max]

    # Create an array with specific size so the zoom fits in (size = max_shift * 2 /k)
    # Center of array is initial max
    out = int(xp.around(max_shift * 2 / k))
    model = xp.zeros((out, out), dtype=xp.float32)
    model[out // 2 - max_shift:out // 2 + max_shift, out // 2 -
          max_shift:out // 2 + max_shift] = inp[ix - max_shift:ix + max_shift,
                                                iy - max_shift:iy + max_shift]
    zoomedVol = xp.expand_dims(model, 2)

    # Scale image
    vt.transform(zoomedVol,
                 scale=(k, k, 1),
                 interpolation='filt_bspline',
                 device=device,
                 output=zoomedVol)

    # fig, ax = subplots(1, 2, figsize=(10, 5))
    # ax[0].imshow(inp.get().squeeze())
    # ax[1].imshow(zoomedVol.squeeze().get())
    # show()

    # Find new max and update the initial max according to the shift
    transformed_max = xp.unravel_index(zoomedVol.argmax(), zoomedVol.shape)
    interpolX, interpolY = ix + (transformed_max[0] - out // 2) * k, iy + (
        transformed_max[1] - out // 2) * k

    return interpolX, interpolY, zoomedVol.squeeze()
Exemple #5
0
def subPixelMax3D(volume,
                  k=.01,
                  ignore_border=50,
                  interpolation='filt_bspline',
                  plan=None,
                  profile=True,
                  num_threads=1024,
                  zoomed=None,
                  fast_sum=None,
                  max_id=None):
    """
    Function to find the highest point in a 3D array, with subpixel accuracy using cubic spline interpolation.

    @param inp: A 3D numpy/cupy array containing the data points.
    @type inp: numpy/cupy array 3D
    @param k: The interpolation factor used in the spline interpolation, k < 1 is zoomed in, k>1 zoom out.
    @type k: float
    @return: A list of maximal value in the interpolated volume and a list of  x position, the y position and
        the value.
    @returntype: list
    """

    from pytom.voltools import transform
    from pytom.tompy.io import write

    ox, oy, oz = volume.shape
    ib = ignore_border
    cropped_volume = volume[ib:ox - ib, ib:oy - ib,
                            ib:oz - ib].astype(xp.float32)

    if profile:
        stream = xp.cuda.Stream.null
        t_start = stream.record()

    # x,y,z = xp.array(maxIndex(cropped_volume)) + ignore_border
    x, y, z = xp.array(
        xp.unravel_index(cropped_volume.argmax(),
                         cropped_volume.shape)) + ignore_border

    dx, dy, dz = volume.shape
    translation = [dx // 2 - x, dy // 2 - y, dz // 2 - z]

    if profile:
        t_end = stream.record()
        t_end.synchronize()

        time_took = xp.cuda.get_elapsed_time(t_start, t_end)
        print(f'initial find max time: \t{time_took:.3f}ms')
        t_start = stream.record()

    b = border = max(0, int(volume.shape[0] // 2 - 4 / k))
    zx, zy, zz = volume.shape
    out = volume[b:zx - b, b:zy - b, b:zz - b]

    transform(out,
              output=zoomed,
              scale=(k, k, k),
              device=device,
              translation=translation,
              interpolation=interpolation)

    if profile:
        t_end = stream.record()
        t_end.synchronize()

        time_took = xp.cuda.get_elapsed_time(t_start, t_end)
        print(f'transform finished in \t{time_took:.3f}ms')
        t_start = stream.record()

    nblocks = int(xp.ceil(zoomed.size / num_threads / 2))
    argmax((
        nblocks,
        1,
    ), (num_threads, 1, 1), (zoomed, fast_sum, max_id, zoomed.size),
           shared_mem=8 * num_threads)
    x2, y2, z2 = xp.unravel_index(max_id[fast_sum.argmax()], zoomed.shape)

    peakValue = zoomed[x2][y2][z2]
    peakShift = [
        x + (x2 - zoomed.shape[0] // 2) * k - volume.shape[0] // 2,
        y + (y2 - zoomed.shape[1] // 2) * k - volume.shape[1] // 2,
        z + (z2 - zoomed.shape[2] // 2) * k - volume.shape[2] // 2
    ]

    if profile:
        t_end = stream.record()
        t_end.synchronize()

        time_took = xp.cuda.get_elapsed_time(t_start, t_end)
        print(f'argmax finished in \t{time_took:.3f}ms')
        t_start = stream.record()
        print()

    return [peakValue, peakShift]
Exemple #6
0
def find_sub_pixel_max_value_2d(inp,
                                interpolate_factor=100,
                                smoothing=0,
                                dim=10,
                                border_size=5,
                                ignore_border=37):  #ignore_border for 200: 75
    """
    To find the highest point in a given numpy array based on 2d spline interpolation, returns the maximum with subpixel
    precision.

    @param inp: The input data array (numpy 2D)
    @type inp: numpy array 2D
    @param interpolate_factor: The amount of interpolation to be done
    @type interpolate_factor: int
    @param smoothing: The amount of smoothing in the spline interpolation
    @type smoothing: int
    @param dim: The dimensions of the peak cutout, which is interpolated to find the subpixel maximum (initial pixels)
    @type dim: int
    @param border_size: The amount of pixels (initial pixels) to disregard in the peak cutout
    @type border_size: int
    @param ignore_border: The amount of pixels (initial pixels) to disregard in the initial finding of the initial maximum, to force the
       found maximum to be more in the center
    @type ignore_border: int
    @return: The subpixel maximum (x, y, the interpolated peak (excluding the border area))
    @returntype: tuple

     <-------- Vol_Size ------->
    | ignore_border             |
    | |   <------ a ------>     |
    | -> |                 | <- |
    |    | Here the max is |    |
    |    | found           |    |
    |    |    d> <c> <d    |    |
    |    |   |.. max ..|   |    |
    |    |   |... * ...|   |    |
    |    |   |.........|   |    |
    |    |    <-- b -->    |    |
    |    -------------------    |
    |___________________________|

    a: vol_size - 2 * ignore_border     (original pixels)
    b: dim * 2                          (original pixels)
    c: b * interpolate_factor - 2 * d   (interpolated pixels)
    d: border_size * interpolate_factor (interpolated pixels)
    ...: interpolated values
    *: peak found

    """
    # assert len(ixp.shape) == 2
    # assert isinstance(interpolate_factor, int) and interpolate_factor > 0
    # assert isinstance(smoothing, float) and smoothing >= 0
    # assert isinstance(dim, int) and dim > 0
    # assert isinstance(border_size, int) and border_size > 0
    # assert isinstance(ignore_border, int) and ignore_border > 0

    import numpy as xp
    from scipy import interpolate
    import warnings

    border_size = border_size * interpolate_factor

    # Get the position of the initial maximum
    inp_without_border = inp[ignore_border:-ignore_border,
                             ignore_border:-ignore_border]
    initial_max = xp.unravel_index(inp_without_border.argmax(),
                                   inp_without_border.shape)
    # Reset the coordinates to be relative to the original inp(ut)
    initial_max = (initial_max[0] + ignore_border,
                   initial_max[1] + ignore_border)
    # imshow(inp[initial_max[0]-10: initial_max[0]+10, initial_max[1]-10: initial_max[1]+10])
    # show()

    # Get the starting points of the peak cutout
    x_dim = inp.shape[0]
    y_dim = inp.shape[1]
    x_start = max([0, initial_max[0] - dim])
    x_end = min([x_dim, initial_max[0] + dim])
    y_start = max([0, initial_max[1] - dim])
    y_end = min([y_dim, initial_max[1] + dim])

    # Create a grid to save the original points and one to save the interpolated points
    x, y = xp.mgrid[0:x_end - x_start, 0:y_end - y_start]
    xnew, ynew = xp.mgrid[0:x_end - x_start:1 / interpolate_factor,
                          0:y_end - y_start:1 / interpolate_factor]

    #print(x_start, x_end, y_start, y_end)

    # Interpolate the points
    # While catching warnings from the pessimistic algorithm of interpolate,
    # which always thinks it needs too much memory
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        tck = interpolate.bisplrep(x,
                                   y,
                                   inp[x_start:x_end, y_start:y_end],
                                   s=smoothing)
        interpolated_grid = interpolate.bisplev(xnew[:, 0], ynew[0, :], tck)
        cropped_inter_grid = interpolated_grid[border_size:-border_size,
                                               border_size:-border_size]
        result = xp.unravel_index(cropped_inter_grid.argmax(),
                                  cropped_inter_grid.shape)

        rx, ry = result
        ix, iy = interpolated_grid.shape
        # imshow(interpolated_grid)
        # show()
        # Reset the coordinates to point to a place in the original data array
        result = [
            x_start + (border_size + rx) / interpolate_factor,
            y_start + (border_size + ry) / interpolate_factor
        ]

        return result[0], result[1], cropped_inter_grid
Exemple #7
0
def run_single_tilt_angle(subtomogram, ang, offset, vol_size,
                          particle_position, particle_rotation,
                          particle_filename, particle_number, binning, img,
                          create_graphics, fsc_path, dimz, peak_border):
    """
    To run a single tilt angle to allow for parallel computing

    @param ang: the tilt angle
    @type ang: int
    @param subtomogram: the filename of the subtomogram
    @type subtomogram: str
    @param offset: the offset used (x,y,z)
    @type offset: list(int, int, int)
    @param vol_size: the size of the volume to be reconstructed (in pixels)
    @type vol_size: int
    @param particle_position: the position of the particle in vector format,
               as given by particle.pickPosition().toVector()
    @type particle_position: tuple
    @param particle_rotation: the rotation of the particle (Z1/phi, X/the, Z2/psi)
    @type particle_rotation: tuple
    @param particle_filename: the filename of the particle, as given by particle.getfilename()
    @type particle_filename: str
    @param particle_number: the number of the particle, to allow for unique mapping
    @type particle_number: int
    @param binning: the binning factor used
    @type binning: int
    @param img: the filename of the projection to be used
    @type img: str
    @param create_graphics: to flag if images should be created for human inspection of the work done
    @type create_graphics: bool
    @return: the newly found positions of the particle, as a list  in the LOCAL_ALIGNMENT_RESULTS format
    @returntype: list
    """

    from pytom.reconstruction.reconstructionFunctions import alignImageUsingAlignmentResultFile
    from pytom.tompy.transform import rotate3d, rotate_axis
    from pytom.gui.guiFunctions import datatypeAR, loadstar
    import numpy as np
    from math import cos, sin, pi, sqrt
    from pytom.tompy.tools import create_circle
    from pytom.tompy.transform import cut_from_projection
    from pytom.tompy.filter import applyFourierFilterFull, bandpass_circle
    from pytom.tompy.io import read, write
    from pytom.tompy.correlation import meanUnderMask, stdUnderMask
    import os
    import time

    t = time.time()
    # print(particle_filename, ang)

    # Filter using FSC
    fsc_mask = None
    k = 0.01
    subtomogram = read(subtomogram) * read(
        '/data/gijsvds/ctem/05_Subtomogram_Analysis/Alignment/GLocal/mask_200_75_5.mrc'
    )

    import os
    from pytom.tompy.filter import filter_volume_by_profile, profile2FourierVol
    fsc_path = ''
    if os.path.isfile(fsc_path):
        profile = [line.split()[0] for line in open(fsc_path, 'r').readlines()]
        fsc_mask3d = profile2FourierVol(profile, subtomogram.shape)

        subtomogram = applyFourierFilterFull(subtomogram, fsc_mask3d)

    # Cross correlate the templat
    # e and patch, this should give the pixel shift it is after
    from pytom.tompy.correlation import nXcf

    # Get template
    # First rotate the template towards orientation of the particle, then to the tilt angle

    img = read(img)

    rotated1 = rotate3d(subtomogram,
                        phi=particle_rotation[0],
                        the=particle_rotation[1],
                        psi=particle_rotation[2])
    rotated2 = rotate_axis(
        rotated1, -ang,
        'y')  # SWITCHED TO ROTATE AXIS AND ANGLE *-1 THIS IS AN ATTEMPT
    template = rotated2.sum(axis=2)

    # write('pp1_template.mrc', template)

    # img = read(img)
    try:
        patch, xx, yy = cut_patch(img,
                                  ang,
                                  particle_position,
                                  dimz=dimz,
                                  vol_size=vol_size,
                                  binning=binning)
        mask2d = create_circle(patch.shape, radius=75, sigma=5, num_sigma=2)
        patch *= mask2d
        # write('pp1_patch.mrc', patch)

        if os.path.isfile(fsc_path):
            profile = [
                line.split()[0] for line in open(fsc_path, 'r').readlines()
            ]
            fsc_mask2d = profile2FourierVol(profile, patch.shape)
            patch = applyFourierFilterFull(patch, fsc_mask2d)

        template = normalize_image(template, mask2d, mask2d.sum())
        patch = normalize_image(patch, mask2d, mask2d.sum())

        if 1:
            ff = xp.ones_like(patch)
        else:
            ff = bandpass_circle(patch.squeeze(), 6, 25, 3)

        ccf = normalised_cross_correlation(template, patch, ff)

        points2d1 = find_sub_pixel_max_value_2d(ccf.copy(),
                                                ignore_border=peak_border)
        points2d2 = find_sub_pixel_max_value(ccf.copy(),
                                             ignore_border=peak_border)
        points2d = list(points2d2[:2]) + [points2d1[-1]]

        x_diff = points2d[0] - vol_size / 2
        y_diff = points2d[1] - vol_size / 2

        dist = sqrt(x_diff**2 + y_diff**2)

        #rx, ry, ii, oox, ooy, x2, y2 = find_sub_pixel_max_value_voltools(ccf.copy())

        #print(rx,ry, x_diff, y_diff, x2, y2, oox, ooy, ii.shape)
        #print(f'{particle_number:3d} {ang:5.1f}, {dist:5.2f} {x_diff} {y_diff} {ccf.max()}')
        if create_graphics:
            rx, ry, ii, oox, ooy, x2, y2 = find_sub_pixel_max_value_voltools(
                ccf.copy(), k=k)
            print(rx, ry)
            from scipy.ndimage.filters import gaussian_filter
            # Create an image to display and/or test the inner workings of the algorithm

            points = find_sub_pixel_max_value(ccf, ignore_border=peak_border)

            nx, ny, nz = particle_position
            nx += x_diff
            ny += y_diff

            npatch = cut_from_projection(
                img.squeeze(), [xx + x_diff, yy + y_diff],
                [vol_size, vol_size]
            )  # img.sum(axis=2)[int(xx+x_diff-v):int(xx+x_diff+v), int(yy+y_diff-v):int(yy+y_diff+v)]  #
            npatch = normalize_image(npatch, mask2d, mask2d.sum())

            nccf = normalised_cross_correlation(template, npatch.squeeze(), ff)
            npoints = find_sub_pixel_max_value(nccf, ignore_border=peak_border)
            npoints2d = find_sub_pixel_max_value_2d(nccf,
                                                    ignore_border=peak_border)
            #rx, ry = abs(xp.array(npoints2d[:2]) - 100)

            from scipy.ndimage.filters import gaussian_filter
            # Create an image to display and/or test the inner workings of the algorithm

            points = find_sub_pixel_max_value(ccf, ignore_border=peak_border)

            nx, ny, nz = particle_position
            nx += x_diff
            ny += y_diff

            npatch = cut_from_projection(
                img.squeeze(), [xx + x_diff, yy + y_diff],
                [vol_size, vol_size]
            )  # img.sum(axis=2)[int(xx+x_diff-v):int(xx+x_diff+v), int(yy+y_diff-v):int(yy+y_diff+v)]  #
            npatch = normalize_image(npatch, mask2d, mask2d.sum())

            nccf = normalised_cross_correlation(template, npatch.squeeze(), ff)
            npoints = find_sub_pixel_max_value(nccf, ignore_border=peak_border)
            npoints2d = find_sub_pixel_max_value_2d(nccf,
                                                    ignore_border=peak_border)
            #rx, ry = abs(xp.array(npoints2d[:2]) - 100)

            m_style = dict(color='tab:blue',
                           linestyle=':',
                           marker='o',
                           markersize=5,
                           markerfacecoloralt='tab:red')
            m_style_alt = dict(color='tab:red',
                               linestyle=':',
                               marker='o',
                               markersize=5,
                               markerfacecoloralt='tab:orange')

            grid = pp.GridSpec(3,
                               3,
                               wspace=0,
                               hspace=0.35,
                               left=0.05,
                               right=0.95,
                               top=0.90,
                               bottom=0.05)

            ax_0_0 = pp.subplot(grid[0, 0])
            ax_0_1 = pp.subplot(grid[0, 1])
            ax_0_2 = pp.subplot(grid[0, 2])
            ax_1_0 = pp.subplot(grid[1, 0])
            ax_1_1 = pp.subplot(grid[1, 1])
            ax_1_2 = pp.subplot(grid[1, 2])
            ax_2_0 = pp.subplot(grid[2, 0])
            ax_2_1 = pp.subplot(grid[2, 1])
            ax_2_2 = pp.subplot(grid[2, 2])

            ax_0_0.axis('off')
            ax_0_1.axis('off')
            ax_0_2.axis('off')
            ax_1_0.axis('off')
            ax_1_1.axis('off')
            ax_1_2.axis('off')
            ax_2_0.axis('off')
            ax_2_1.axis('off')
            ax_2_2.axis('off')

            axis_title(ax_0_0, "Cutout")
            ax_0_0.imshow(applyFourierFilterFull(patch, xp.fft.fftshift(ff)))
            axis_title(ax_0_1, "Template")
            ax_0_1.imshow(template)
            axis_title(ax_0_2, "Shifted Cutout\n(based on cross correlation)")
            ax_0_2.imshow(npatch.squeeze())

            axis_title(ax_1_0, u"Cross correlation\ncutout × template")
            ax_1_0.imshow(ccf)
            ax_1_0.plot([points[1]], [points[0]], fillstyle='none', **m_style)
            ax_1_0.plot([points2d[1]], [points2d[0]],
                        fillstyle='none',
                        **m_style_alt)
            ax_1_0.plot([vol_size / 2], [vol_size / 2], ",k")

            ax_1_1.text(
                0.5,
                0.8,
                "Red: 2D spline interpolation\nx: {:f}\ny: {:f}\nBlue: 1D spline interpolation\nx: {:f}\ny: {:f}"
                "\nBlack: center".format(x_diff, y_diff,
                                         points[0] - vol_size / 2,
                                         points[1] - vol_size / 2),
                fontsize=8,
                horizontalalignment='center',
                verticalalignment='center',
                transform=ax_1_1.transAxes)

            axis_title(ax_1_2, u"Cross correlation\nshifted cutout × template")
            ax_1_2.imshow(nccf)
            ax_1_2.plot([npoints[0]], [npoints[1]],
                        fillstyle='none',
                        **m_style)
            ax_1_2.plot([npoints2d[0]], [npoints2d[1]],
                        fillstyle='none',
                        **m_style_alt)
            ax_1_2.plot([vol_size / 2], [vol_size / 2], ",k")

            axis_title(ax_2_0, u"Zoom into red peak\nin CC cutout × template")
            d = 10

            points2d = list(xp.unravel_index(ccf.argmax(),
                                             ccf.shape)) + [points2d[-1]]
            peak = ccf[int(points2d[0]) - d:int(points2d[0]) + d,
                       int(points2d[1]) - d:int(points2d[1] + d)]
            ax_2_0.imshow(peak)

            axis_title(
                ax_2_1,
                u"Zoom into red peak\nin CC cutout × template\ninterpolated")
            ax_2_1.imshow(ii)

            ax_2_1.plot([y2 + (y_diff - ry) / k], [x2 + (x_diff - rx) / k],
                        fillstyle='none',
                        **m_style)
            ax_2_1.plot([y2], [x2], fillstyle='none', **m_style_alt)
            axis_title(ax_2_2, u"Cutout\nGaussian filter σ3")
            import scipy
            ax_2_2.imshow(scipy.ndimage.gaussian_filter(patch, 3))

            pp.savefig(
                "../Images/test/polish_particle_{:04d}_tiltimage_{:05.2f}_shift_{:.1f}.png"
                .format(particle_number, ang, dist))
        del img
    except Exception as e:
        print(e)
        x_diff = y_diff = 0

    print(
        f'{particle_number:3d} {ang:4d} {x_diff:5.2f} {y_diff:5.2f} {points2d1[0]-patch.shape[0]//2:.2f} {points2d1[1]-patch.shape[0]//2:.2f} {time.time()-t:5.3f}'
    )
    return particle_number, x_diff, y_diff, ang, 0, 0, particle_filename
Exemple #8
0
def find_coords_max_ccmap(volume):
    return xp.unravel_index(volume.argmax(), volume.shape)