Esempio n. 1
0
def calibrate_mag1_from_image_fn(center_fn, other_fn):
    """Calibrate pixel->stageposition coordinates from a set of images.

    center_fn: `str`
        Reference image at the center of the grid (with the clover in the middle)
    other_fn: `tuple` of `str`
        Set of images to cross correlate to the first reference image

    return:
        instance of Calibration class with conversion methods
    """
    img_cent, h_cent = read_image(center_fn)

    # binsize = h_cent["ImageBinsize"]
    cam_dimensions = h_cent['ImageCameraDimensions']
    bin_x, bin_y = cam_dimensions / np.array(img_cent.shape)
    assert bin_x == bin_y, 'Binsizes do not match {bin_x} != {bin_y}'
    binsize = int(bin_x)

    img_cent, scale = autoscale(img_cent, maxdim=512)

    x_cent, y_cent, _, _, _ = h_cent['StagePosition']

    xy_cent = np.array([x_cent, y_cent])
    print('Center:', center_fn)
    print('Stageposition: x={:.0f} | y={:.0f}'.format(*xy_cent))
    print()

    shifts = []
    stagepos = []

    for i, fn in enumerate(other_fn):
        img, h = read_image(fn)

        img = imgscale(img, scale)

        x_xobs, yobs, _, _, _ = h_cent['StagePosition']
        print('Image:', fn)
        print(f'Stageposition: x={xobs:.0f} | y={yobs:.0f}')

        shift, error, phasediff = phase_cross_correlation(img_cent, img, upsample_factor=10)
        print('Shift:', shift)
        print()

        stagepos.append((xobs, yobs))
        shifts.append(shift)

    # correct for binsize, store as binsize=1
    shifts = np.array(shifts) * binsize / scale
    stagepos = np.array(stagepos) - xy_cent

    c = CalibStage.from_data(shifts, stagepos, reference_position=xy_cent, camera_dimensions=cam_dimensions)
    c.plot()
    c.to_file()

    return c
def calibrate_stage_lowmag_from_image_fn(center_fn, other_fn):
    """Calibrate pixel->stageposition coordinates from a set of images.

    center_fn: `str`
        Reference image at the center of the grid (with the clover in the middle)
    other_fn: `tuple` of `str`
        Set of images to cross correlate to the first reference image

    return:
        instance of Calibration class with conversion methods
    """
    img_cent, h_cent = read_image(center_fn)

    img_cent, scale = autoscale(img_cent, maxdim=512)

    x_cent, y_cent, _, _, _ = h_cent['StagePosition']
    xy_cent = np.array([x_cent, y_cent])
    print('Center:', center_fn)
    print('Stageposition: x={:.0f} | y={:.0f}'.format(*xy_cent))
    print()

    binsize = h_cent['ImageBinsize']

    shifts = []
    stagepos = []

    for fn in other_fn:
        img, h = read_image(fn)

        img = imgscale(img, scale)

        xobs, yobs, _, _, _ = h['StagePosition']
        print('Image:', fn)
        print(f'Stageposition: x={xobs:.0f} | y={yobs:.0f}')
        print()

        shift, error, phasediff = phase_cross_correlation(img_cent,
                                                          img,
                                                          upsample_factor=10)

        stagepos.append((xobs, yobs))
        shifts.append(shift)

    # correct for binsize, store as binsize=1
    shifts = np.array(shifts) * binsize / scale
    stagepos = np.array(stagepos) - xy_cent

    c = CalibStage.from_data(shifts,
                             stagepos,
                             reference_position=xy_cent,
                             header=h_cent)
    c.plot()

    return c
def calibrate_directbeam_from_file(center_fn, other_fn, key='DiffShift'):
    print()
    print('Center:', center_fn)

    img_cent, h_cent = load_img(center_fn)
    readout_cent = np.array(h_cent[key])

    img_cent, scale = autoscale(img_cent, maxdim=512)

    binsize = h_cent['ImageBinsize']

    print('{}: x={} | y={}'.format(key, *readout_cent))

    shifts = []
    readouts = []

    for i, fn in enumerate(other_fn):
        print(fn)
        img, h = load_img(fn)
        img = imgscale(img, scale)

        readout = np.array(h[key])
        print()
        print('Image:', fn)
        print('{}: dx={} | dy={}'.format(key, *readout))

        shift, error, phasediff = phase_cross_correlation(img_cent,
                                                          img,
                                                          upsample_factor=10)

        readouts.append(readout)
        shifts.append(shift)

    # correct for binsize, store in binsize=1
    shifts = np.array(shifts) * binsize / scale
    readouts = np.array(readouts) - np.array(readout_cent)

    c = CalibDirectBeam.from_data(shifts,
                                  readouts,
                                  key,
                                  header=h_cent,
                                  **refine_params[key])
    c.plot(key)

    return c
Esempio n. 4
0
def find_holes_entry():
    from formats import read_image

    for fn in sys.argv[1:]:
        img, h = read_image(fn)

        img_zoomed, scale = autoscale(img, maxdim=512)

        binsize = h['ImageBinsize']
        magnification = h['Magnification']
        d = 150

        area = calculate_hole_area(d, magnification, img_scale=scale, binsize=binsize)
        holes = find_holes(img_zoomed, area=area, plot=True)

        print()
        for hole in holes:
            x, y = hole.centroid
            px = py = calibration['lowmag']['pixelsize'][magnification] / 1000  # nm -> um
            area = hole.area * px * py / scale**2
            d = 2 * (area / np.pi)**0.5
            print(f'x: {x*scale:.2f}, y: {y*scale:.2f}, d: {d:.2f} um')
Esempio n. 5
0
    def __init__(self, cam='simulate'):
        threading.Thread.__init__(self)

        if isinstance(cam, str):
            self.cam = Camera(name=cam, as_stream=False)
        else:
            self.cam = cam

        self.lock = threading.Lock()

        self.default_exposure = self.cam.default_exposure
        self.default_binsize = self.cam.default_binsize
        self.dimensions = self.cam.dimensions
        self.name = self.cam.name

        self.frametime = self.default_exposure

        self.streamable = self.cam.streamable

        self.display_dim = 512

        self.frame, scale = autoscale(np.ones(self.dimensions), maxdim=self.display_dim)
Esempio n. 6
0
def calibrate_brightness_from_image_fn(fns):
    """Calibrate pixel->brightness (size of beam) from a set of images.

    fns: `str`
        Set of images to determine size of beam from

    return:
        instance of Calibration class with conversion methods
    """

    values = []

    for fn in fns:
        print()
        print('Image:', fn)
        img, h = load_img(fn)
        brightness = float(h['Brightness'])
        binsize = float(h['ImageBinsize'])

        img, scale = autoscale(img)

        holes = find_holes(img,
                           plot=False,
                           fname=None,
                           verbose=False,
                           max_eccentricity=0.8)

        size = max(hole.equivalent_diameter
                   for hole in holes) * binsize / scale

        print(
            f'Brightness: {brightness:.0f}, equivalent diameter: {size:.1f}px')
        values.append((brightness, size))

    values = np.array(values)
    c = CalibBrightness.from_data(*values.T)
    c.plot()

    return c
def calibrate_stage_lowmag_live(ctrl,
                                gridsize=5,
                                stepsize=50000,
                                save_images=False,
                                **kwargs):
    """Calibrate pixel->stageposition coordinates live on the microscope.

    ctrl: instance of `TEMController`
        contains tem + cam interface
    gridsize: `int`
        Number of grid points to take, gridsize=5 results in 25 points
    stepsize: `float`
        Size of steps for stage position along x and y
    exposure: `float`
        exposure time
    binsize: `int`

    return:
        instance of Calibration class with conversion methods
    """

    exposure = kwargs.get('exposure', ctrl.cam.default_exposure)
    binsize = kwargs.get('binsize', ctrl.cam.default_binsize)

    outfile = 'calib_start' if save_images else None

    # Accurate reading fo the center positions is needed so that we can come back to it,
    #  because this will be our anchor point
    img_cent, h_cent = ctrl.get_image(exposure=exposure,
                                      binsize=binsize,
                                      out=outfile,
                                      comment='Center image (start)')

    x_cent, y_cent, _, _, _ = h_cent['StagePosition']
    xy_cent = np.array([x_cent, y_cent])

    img_cent, scale = autoscale(img_cent)

    stagepos = []
    shifts = []

    n = int((gridsize - 1) / 2)  # number of points = n*(n+1)
    x_grid, y_grid = np.meshgrid(
        np.arange(-n, n + 1) * stepsize,
        np.arange(-n, n + 1) * stepsize)
    tot = gridsize * gridsize

    i = 0
    for dx, dy in np.stack([x_grid, y_grid]).reshape(2, -1).T:
        print()
        print(f'Position {i+1}/{tot}: x: {x_cent+dx:.0f}, y: {y_cent+dy:.0f}')

        ctrl.stage.set(x=x_cent + dx, y=y_cent + dy)
        print(ctrl.stage)

        outfile = f'calib_{i:04d}' if save_images else None

        comment = comment
        img, h = ctrl.get_image(exposure=exposure,
                                binsize=binsize,
                                out=outfile,
                                comment=comment,
                                header_keys='StagePosition')

        img = imgscale(img, scale)

        shift, error, phasediff = phase_cross_correlation(img_cent,
                                                          img,
                                                          upsample_factor=10)

        xobs, yobs, _, _, _ = h['StagePosition']
        stagepos.append((xobs, yobs))
        shifts.append(shift)

        i += 1

    print(' >> Reset to center')
    ctrl.stage.set(x=x_cent, y=y_cent)
    ctrl.stage.reset_xy()

    # correct for binsize, store as binsize=1
    shifts = np.array(shifts) * binsize / scale
    stagepos = np.array(stagepos) - np.array((x_cent, y_cent))

    m = gridsize**2 // 2
    if gridsize % 2 and stagepos[m].max() > 50:
        print(
            f' >> Warning: Large difference between image {m}, and center image. These should be close for a good calibration.'
        )
        print('    Difference:', stagepos[m])
        print()

    if save_images:
        ctrl.get_image(exposure=exposure,
                       binsize=binsize,
                       out='calib_end',
                       comment='Center image (end)')

    c = CalibStage.from_data(shifts,
                             stagepos,
                             reference_position=xy_cent,
                             header=h_cent)

    # Calling c.plot with videostream crashes program
    if not hasattr(ctrl.cam, 'VideoLoop'):
        c.plot(key)

    return c
Esempio n. 8
0
def calibrate_mag1_live(ctrl, gridsize=5, stepsize=5000, minimize_backlash=True, save_images=False, **kwargs):
    """Calibrate pixel->stageposition coordinates live on the microscope.

    ctrl: instance of `TEMController`
        contains tem + cam interface
    gridsize: `int`
        Number of grid points to take, gridsize=5 results in 25 points
    stepsize: `float`
        Size of steps for stage position along x and y
    minimize_backlash: bool,
        Attempt to minimize backlash by overshooting a bit
        Follows the routine from Oostergetel (1998): https://doi.org/10.1016/S0304-3991(98)00022-9
    exposure: `float`
        Exposure time in seconds
    binsize: `int`

    return:
        instance of Calibration class with conversion methods
    """

    work_drc = get_new_work_subdirectory(stem='calib_mag1')

    settle_delay = 1.0  # seconds

    # make sure the angle == 0.0
    for _ in range(3):
        ctrl.stage.a = 0.0
        time.sleep(settle_delay)

    exposure = kwargs.get('exposure', config.camera.default_exposure)
    binsize = kwargs.get('binsize', config.camera.default_binsize)

    if minimize_backlash:
        ctrl.stage.eliminate_backlash_xy(step=stepsize, settle_delay=settle_delay)

    outfile = work_drc / 'calib_start' if save_images else None

    # Accurate reading fo the center positions is needed so that we can come back to it,
    #  because this will be our anchor point
    img_cent, h_cent = ctrl.get_image(exposure=exposure, binsize=binsize, out=outfile, comment='Center image (start)')
    stage_cent = ctrl.stage.get()

    cam_dimensions = h_cent['ImageCameraDimensions']
    bin_x, bin_y = cam_dimensions / np.array(img_cent.shape)
    assert bin_x == bin_y, 'Binsizes do not match {bin_x} != {bin_y}'
    binsize = int(bin_x)

    x_cent = stage_cent.x
    y_cent = stage_cent.y

    xy_cent = np.array([x_cent, y_cent])

    img_cent, scale = autoscale(img_cent)

    stagepos = []
    shifts = []

    n = int((gridsize - 1) / 2)  # number of points = n*(n+1)
    x_grid, y_grid = np.meshgrid(np.arange(-n, n + 1) * stepsize, np.arange(-n, n + 1) * stepsize)
    tot = gridsize * gridsize

    i = 0

    x_range = np.arange(-n, n + 1) * stepsize
    y_range = np.arange(-n, n + 1) * stepsize

    if minimize_backlash:
        xtarget = x_cent + x_range[0]
        ytarget = y_cent + y_range[0]
        ctrl.stage.set(x=xtarget - stepsize, y=ytarget - stepsize)
        time.sleep(settle_delay)

        print('(minimize_backlash) Overshoot a bit in XY: ', ctrl.stage.xy)

    for dx in x_range:
        for dy in y_range:
            ctrl.stage.set(x=x_cent + dx, y=y_cent + dy)
            time.sleep(settle_delay)
            stage = ctrl.stage.get()

            print()
            print(f'Position {I+1}/{tot}')
            print(stage)

            outfile = work_drc / f'calib_{i:04d}' if save_images else None

            comment = f'Calib image {i}: dx={dx} - dy={dy}'
            img, h = ctrl.get_image(exposure=exposure, binsize=binsize, out=outfile, comment=comment)

            img = imgscale(img, scale)

            shift, error, phasediff = phase_cross_correlation(img_cent, img, upsample_factor=10)

            xobs = stage.x
            yobs = stage.y

            stagepos.append((xobs, yobs))
            shifts.append(shift)

            i += 1

        if minimize_backlash:
            ytarget = y_cent + y_range[0]
            ctrl.stage.set(y=ytarget - stepsize)
            time.sleep(settle_delay)
            print('(minimize_backlash) Overshoot a bit in Y: ', ctrl.stage.xy)

    print(' >> Reset to center')
    ctrl.stage.set(x=x_cent, y=y_cent)
    time.sleep(settle_delay)
    # ctrl.stage.reset_xy()

    # correct for binsize, store as binsize=1
    shifts = np.array(shifts) * binsize / scale
    stagepos = np.array(stagepos) - np.array((x_cent, y_cent))

    m = gridsize**2 // 2
    if gridsize % 2 and stagepos[m].max() > 50:
        print(f' >> Warning: Large difference between image {m}, and center image. These should be close for a good calibration.')
        print('    Difference:', stagepos[m])
        print()

    if save_images:
        outfile = work_drc / 'calib_end'
        ctrl.get_image(exposure=exposure, binsize=binsize, out=outfile, comment='Center image (end)')

    c = CalibStage.from_data(shifts, stagepos, reference_position=xy_cent, camera_dimensions=cam_dimensions)
    c.plot()
    c.to_file(work_drc / 'calib.pickle')

    return c
Esempio n. 9
0
    def getImage(self, exposure=None, binsize=None):
        frame = self.cam.getImage(exposure=exposure, binsize=binsize)

        self.frame, scale = autoscale(frame, maxdim=self.display_dim)

        return frame
Esempio n. 10
0
def calibrate_brightness_live(ctrl, step=1000, save_images=False, **kwargs):
    """Calibrate pixel->brightness coordinates live on the microscope.

    ctrl: instance of `TEMController`
        contains tem + cam interface
    start: `float`
        start value for calibration (0.0 - 1.0)
    end: `float`
        end value for calibration (0.0 - 1.0)
    exposure: `float`
        exposure time
    binsize: `int`

    return:
        instance of CalibBrightness class with conversion methods
    """

    raise NotImplementedError(
        'calibrate_brightness_live function needs fixing...')

    exposure = kwargs.get('exposure', ctrl.cam.default_exposure)
    binsize = kwargs.get('binsize', ctrl.cam.default_binsize)

    values = []
    start = ctrl.brightness.value

    for i in range(10):
        target = start + i * step
        ctrl.brightness.value = int(target)

        outfile = f'calib_brightness_{i:04d}' if save_images else None

        comment = f'Calib image {i}: brightness={target}'
        img, h = ctrl.get_image(exposure=exposure,
                                out=outfile,
                                comment=comment,
                                header_keys='Brightness')

        img, scale = autoscale(img)

        brightness = float(h['Brightness'])

        holes = find_holes(img,
                           plot=False,
                           verbose=False,
                           max_eccentricity=0.8)

        if len(holes) == 0:
            print(' >> No holes found, continuing...')
            continue

        size = max(hole.equivalent_diameter
                   for hole in holes) * binsize / scale

        print(f'Brightness: {brightness:.f}, equivalent diameter: {size:.1f}')
        values.append((brightness, size))

    values = np.array(values)
    c = CalibBrightness.from_data(*values.T)

    # Calling c.plot with videostream crashes program
    if not hasattr(ctrl.cam, 'VideoLoop'):
        c.plot()

    return c
def calibrate_directbeam_live(ctrl,
                              key='DiffShift',
                              gridsize=None,
                              stepsize=None,
                              save_images=False,
                              outdir='.',
                              **kwargs):
    """Calibrate pixel->beamshift coordinates live on the microscope.

    ctrl: instance of `TEMController`
        contains tem + cam interface
    key: `str`
        Name of property to calibrate
    gridsize: `int` or None
        Number of grid points to take, gridsize=5 results in 25 points
    stepsize: `float` or None
        Size of steps for property along x and y
    exposure: `float` or None
        exposure time
    binsize: `int` or None

    In case paramers are not defined, camera specific default parameters are

    return:
        instance of Calibration class with conversion methods
    """

    if ctrl.mode != 'diff':
        print(' >> Switching to diffraction mode')
        ctrl.mode.set('diff')

    exposure = kwargs.get('exposure', ctrl.cam.default_exposure)
    binsize = kwargs.get('binsize', ctrl.cam.default_binsize)

    if not gridsize:
        gridsize = config.camera.calib_directbeam.get(key, {}).get(
            'gridsize', 5)  # dat syntax...
    if not stepsize:
        stepsize = config.camera.calib_directbeam.get(key, {}).get(
            'stepsize', 750)  # just to fit everything on 1 line =)

    attr = getattr(ctrl, key.lower())

    outfile = os.path.join(outdir,
                           f'calib_db_{key}_0000') if save_images else None
    img_cent, h_cent = ctrl.get_image(exposure=exposure,
                                      binsize=binsize,
                                      comment='Beam in center of image',
                                      out=outfile)
    x_cent, y_cent = readout_cent = np.array(h_cent[key])

    img_cent, scale = autoscale(img_cent)

    print('{}: x={} | y={}'.format(key, *readout_cent))

    shifts = []
    readouts = []

    n = int((gridsize - 1) / 2)  # number of points = n*(n+1)
    x_grid, y_grid = np.meshgrid(
        np.arange(-n, n + 1) * stepsize,
        np.arange(-n, n + 1) * stepsize)
    tot = gridsize * gridsize

    for i, (dx, dy) in enumerate(np.stack([x_grid, y_grid]).reshape(2, -1).T):
        i += 1

        attr.set(x=x_cent + dx, y=y_cent + dy)

        printer(f'Position: {i}/{tot}: {attr}')

        outfile = os.path.join(
            outdir, f'calib_db_{key}_{i:04d}') if save_images else None

        comment = f'Calib image {i}: dx={dx} - dy={dy}'
        img, h = ctrl.get_image(exposure=exposure,
                                binsize=binsize,
                                out=outfile,
                                comment=comment,
                                header_keys=key)
        img = imgscale(img, scale)

        shift, error, phasediff = phase_cross_correlation(img_cent,
                                                          img,
                                                          upsample_factor=10)

        readout = np.array(h[key])
        readouts.append(readout)
        shifts.append(shift)

    print('')
    # print "\nReset to center"
    attr.set(*readout_cent)

    # correct for binsize, store in binsize=1
    shifts = np.array(shifts) * binsize / scale
    readouts = np.array(readouts) - np.array(readout_cent)

    c = CalibDirectBeam.from_data(shifts,
                                  readouts,
                                  key,
                                  header=h_cent,
                                  **refine_params[key])

    # Calling c.plot with videostream crashes program
    # if not hasattr(ctrl.cam, "VideoLoop"):
    #     c.plot(key)

    return c
Esempio n. 12
0
def find_crystals(img, magnification, spread=2.0, plot=False, **kwargs):
    """Function for finding crystals in a low contrast images. Used adaptive
    thresholds to find local features. Edges are detected, and rejected, on the
    basis of a histogram. Kmeans clustering is used to spread points over the
    segmented area.

    img: 2d np.ndarray
        Input image to locate crystals on
    magnification: float
        value indicating the magnification used, needed in order to determine the size of the crystals
    spread: float
        Value in micrometer to roughly indicate the desired spread of centroids over individual regions
    plot: bool
        Whether to plot the results or not
    **kwargs:
    keywords to pass to segment_crystals
    """
    img, scale = autoscale(img, maxdim=256)  # scale down for faster

    # segment the image, and find objects
    arr, seg = segment_crystals(img, **kwargs)

    labels, numlabels = ndimage.label(seg)
    props = measure.regionprops(labels, img)

    # calculate the pixel dimensions in micrometer
    px = py = calibration['mag1']['pixelsize'][magnification] / 1000  # nm -> um

    iters = 20

    crystals = []
    for prop in props:
        area = prop.area * px * py
        bbox = np.array(prop.bbox)

        # origin of the prop
        origin = bbox[0:2]

        # edge detection
        if isedge(prop):
            continue

        # number of centroids for kmeans clustering
        nclust = int(area // spread) + 1

        if nclust > 1:
            # use skmeans clustering to segment large blobs
            coordinates = np.argwhere(prop.image)

            # kmeans needs normalized data (w), store std to calculate coordinates after
            w, std = whiten(coordinates)

            # nclust must be an integer for some reason
            cluster_centroids, closest_centroids = kmeans2(w,
                                                           nclust,
                                                           iter=iters,
                                                           minit='points')

            # convert to image coordinates
            xy = (cluster_centroids * std + origin[0:2]) / scale
            crystals.extend([
                CrystalPosition(x, y, False, nclust, area, prop.area)
                for x, y in xy
            ])
        else:
            x, y = prop.centroid
            crystals.append(
                CrystalPosition(x / scale, y / scale, True, nclust, area,
                                prop.area))

    if plot:
        plt.imshow(img)
        plt.contour(seg, [0.5], linewidths=1.2, colors='yellow')
        if len(crystals) > 0:
            x, y = np.array([(crystal.x * scale, crystal.y * scale)
                             for crystal in crystals]).T
            plt.scatter(y, x, color='red')
        ax = plt.axes()
        ax.set_axis_off()
        plt.show()

    return crystals
Esempio n. 13
0
def calibrate_beamshift_from_image_fn(center_fn, other_fn):
    """Calibrate pixel->beamshift coordinates from a set of images.

    center_fn: `str`
        Reference image with the beam at the center of the image
    other_fn: `tuple` of `str`
        Set of images to cross correlate to the first reference image

    return:
        instance of Calibration class with conversion methods
    """
    print()
    print('Center:', center_fn)

    img_cent, h_cent = load_img(center_fn)
    beamshift_cent = np.array(h_cent['BeamShift'])

    img_cent, scale = autoscale(img_cent, maxdim=512)

    binsize = h_cent['ImageBinsize']

    holes = find_holes(img_cent,
                       plot=False,
                       verbose=False,
                       max_eccentricity=0.8)
    pixel_cent = np.array(holes[0].centroid) * binsize / scale

    print('Beamshift: x={} | y={}'.format(*beamshift_cent))
    print('Pixel: x={:.2f} | y={:.2f}'.format(*pixel_cent))

    shifts = []
    beampos = []

    for fn in other_fn:
        img, h = load_img(fn)
        img = imgscale(img, scale)

        beamshift = np.array(h['BeamShift'])
        print()
        print('Image:', fn)
        print('Beamshift: x={} | y={}'.format(*beamshift))

        shift, error, phasediff = phase_cross_correlation(img_cent,
                                                          img,
                                                          upsample_factor=10)

        beampos.append(beamshift)
        shifts.append(shift)

    # correct for binsize, store as binsize=1
    shifts = np.array(shifts) * binsize / scale
    beampos = np.array(beampos) - beamshift_cent

    c = CalibBeamShift.from_data(shifts,
                                 beampos,
                                 reference_shift=beamshift_cent,
                                 reference_pixel=pixel_cent,
                                 header=h_cent)
    c.plot()

    return c
Esempio n. 14
0
def calibrate_beamshift_live(ctrl,
                             gridsize=None,
                             stepsize=None,
                             save_images=False,
                             outdir='.',
                             **kwargs):
    """Calibrate pixel->beamshift coordinates live on the microscope.

    ctrl: instance of `TEMController`
        contains tem + cam interface
    gridsize: `int` or None
        Number of grid points to take, gridsize=5 results in 25 points
    stepsize: `float` or None
        Size of steps for beamshift along x and y
        Defined at a magnification of 2500, scales stepsize down for other mags.
    exposure: `float` or None
        exposure time
    binsize: `int` or None

    In case paramers are not defined, camera specific default parameters are retrieved

    return:
        instance of Calibration class with conversion methods
    """

    exposure = kwargs.get('exposure', ctrl.cam.default_exposure)
    binsize = kwargs.get('binsize', ctrl.cam.default_binsize)

    if not gridsize:
        gridsize = config.camera.calib_beamshift.get('gridsize', 5)
    if not stepsize:
        stepsize = config.camera.calib_beamshift.get('stepsize', 250)

    img_cent, h_cent = ctrl.get_image(exposure=exposure,
                                      binsize=binsize,
                                      comment='Beam in center of image')
    x_cent, y_cent = beamshift_cent = np.array(h_cent['BeamShift'])

    magnification = h_cent['Magnification']
    stepsize = 2500.0 / magnification * stepsize

    print(f'Gridsize: {gridsize} | Stepsize: {stepsize:.2f}')

    img_cent, scale = autoscale(img_cent)

    outfile = os.path.join(outdir, 'calib_beamcenter') if save_images else None

    pixel_cent = find_beam_center(img_cent) * binsize / scale

    print('Beamshift: x={} | y={}'.format(*beamshift_cent))
    print('Pixel: x={} | y={}'.format(*pixel_cent))

    shifts = []
    beampos = []

    n = int((gridsize - 1) / 2)  # number of points = n*(n+1)
    x_grid, y_grid = np.meshgrid(
        np.arange(-n, n + 1) * stepsize,
        np.arange(-n, n + 1) * stepsize)
    tot = gridsize * gridsize

    i = 0
    for dx, dy in np.stack([x_grid, y_grid]).reshape(2, -1).T:
        ctrl.beamshift.set(x=x_cent + dx, y=y_cent + dy)

        printer('Position: {}/{}: {}'.format(i + 1, tot, ctrl.beamshift))

        outfile = os.path.join(
            outdir, 'calib_beamshift_{i:04d}') if save_images else None

        comment = f'Calib image {i}: dx={dx} - dy={dy}'
        img, h = ctrl.get_image(exposure=exposure,
                                binsize=binsize,
                                out=outfile,
                                comment=comment,
                                header_keys='BeamShift')
        img = imgscale(img, scale)

        shift, error, phasediff = phase_cross_correlation(img_cent,
                                                          img,
                                                          upsample_factor=10)

        beamshift = np.array(h['BeamShift'])
        beampos.append(beamshift)
        shifts.append(shift)

        i += 1

    print('')
    # print "\nReset to center"

    ctrl.beamshift.set(*beamshift_cent)

    # correct for binsize, store in binsize=1
    shifts = np.array(shifts) * binsize / scale
    beampos = np.array(beampos) - np.array(beamshift_cent)

    c = CalibBeamShift.from_data(shifts,
                                 beampos,
                                 reference_shift=beamshift_cent,
                                 reference_pixel=pixel_cent,
                                 header=h_cent)

    # Calling c.plot with videostream crashes program
    # if not hasattr(ctrl.cam, "VideoLoop"):
    #     c.plot()

    return c