Beispiel #1
0
def test_rotate():
    y, x = np.random.random((2, 7))
    radians = np.pi / 180 * 23
    r_y, r_x = c.identity() @ c.rotate(radians) @ (y, x)
    r_y2, r_x2 = rotate_deg(y, x, 23)
    assert np.allclose(r_y, r_y2)
    assert np.allclose(r_x, r_x2)
Beispiel #2
0
def test_masks():
    scaling = 4
    dtype = np.float64
    shape = (29, 30, 189 // scaling, 197 // scaling)
    #  ? shape = np.random.uniform(1, 300, (4,1,))

    # The acceleration voltage U in keV
    U = 300
    lambda_e = wavelength(U)
    # STEM pixel size in m, here 50 STEM pixels on 0.5654 nm
    dpix = 0.5654 / 50 * 1e-9
    # STEM semiconvergence angle in radians
    semiconv = 25e-3
    # Diameter of the primary beam in the diffraction pattern in pixels
    semiconv_pix = 78.6649 / scaling

    cy = 93 // scaling
    cx = 97 // scaling

    input_data = np.random.uniform(0, 1, shape)

    _, reference_masks = reference_ssb(input_data,
                                       U=U,
                                       dpix=dpix,
                                       semiconv=semiconv,
                                       semiconv_pix=semiconv_pix,
                                       cy=cy,
                                       cx=cx)

    # print(np.max(np.abs(np.abs(result['pixels']) - np.abs(result_f))))
    half_y = shape[0] // 2 + 1
    # Use symmetry and reshape like generate_masks()
    reference_masks = reference_masks[:half_y].reshape(
        (half_y * shape[1], shape[2], shape[3]))

    masks = generate_masks(reconstruct_shape=shape[:2],
                           mask_shape=shape[2:],
                           dtype=dtype,
                           lamb=lambda_e,
                           dpix=dpix,
                           semiconv=semiconv,
                           semiconv_pix=semiconv_pix,
                           cy=cy,
                           cx=cx,
                           transformation=identity(),
                           method="subpix").todense()

    assert reference_masks.shape == masks.shape
    print(reference_masks)
    print(masks)
    print(reference_masks - masks)
    print("maximum difference: ", np.max(np.abs(reference_masks - masks)))
    print(np.where(reference_masks != masks))
    assert np.any(reference_masks != 0)
    assert np.allclose(reference_masks, masks)
Beispiel #3
0
    def get_generic_results(self, img_sum, img_y, img_x):
        ref_x = self.parameters["cx"]
        ref_y = self.parameters["cy"]
        y_centers_raw, x_centers_raw = center_shifts(img_sum, img_y, img_x, ref_y, ref_x)
        shape = y_centers_raw.shape
        if self.parameters["flip_y"]:
            transform = flip_y()
        else:
            transform = identity()
        # Transformations are applied right to left
        transform = rotate_deg(self.parameters["scan_rotation"]) @ transform
        y_centers, x_centers = transform @ (y_centers_raw.reshape(-1), x_centers_raw.reshape(-1))

        y_centers = y_centers.reshape(shape)
        x_centers = x_centers.reshape(shape)

        if img_sum.dtype.kind == 'c':
            x_real, x_imag = np.real(x_centers), np.imag(x_centers)
            y_real, y_imag = np.real(y_centers), np.imag(y_centers)

            return COMResultSet([
                AnalysisResult(raw_data=x_real, visualized=visualize_simple(x_real),
                       key="x_real", title="x [real]", desc="x component of the center"),
                AnalysisResult(raw_data=y_real, visualized=visualize_simple(y_real),
                       key="y_real", title="y [real]", desc="y component of the center"),
                AnalysisResult(raw_data=x_imag, visualized=visualize_simple(x_imag),
                       key="x_imag", title="x [imag]", desc="x component of the center"),
                AnalysisResult(raw_data=y_imag, visualized=visualize_simple(y_imag),
                       key="y_imag", title="y [imag]", desc="y component of the center"),
            ])
        else:
            f = CMAP_CIRCULAR_DEFAULT.rgb_from_vector((y_centers, x_centers))
            d = divergence(y_centers, x_centers)
            c = curl_2d(y_centers, x_centers)
            m = magnitude(y_centers, x_centers)

            return COMResultSet([
                AnalysisResult(raw_data=(x_centers, y_centers), visualized=f,
                       key="field", title="field", desc="cubehelix colorwheel visualization",
                       include_in_download=False),
                AnalysisResult(raw_data=m, visualized=visualize_simple(m),
                       key="magnitude", title="magnitude", desc="magnitude of the vector field"),
                AnalysisResult(raw_data=d, visualized=visualize_simple(d),
                       key="divergence", title="divergence", desc="divergence of the vector field"),
                AnalysisResult(raw_data=c, visualized=visualize_simple(c),
                       key="curl", title="curl", desc="curl of the 2D vector field"),
                AnalysisResult(raw_data=x_centers, visualized=visualize_simple(x_centers),
                       key="x", title="x", desc="x component of the center"),
                AnalysisResult(raw_data=y_centers, visualized=visualize_simple(y_centers),
                       key="y", title="y", desc="y component of the center"),
            ])
Beispiel #4
0
def apply_correction(y_centers,
                     x_centers,
                     scan_rotation,
                     flip_y,
                     forward=True):
    shape = y_centers.shape
    if flip_y:
        transform = coordinates.flip_y()
    else:
        transform = coordinates.identity()
    # Transformations are applied right to left
    transform = coordinates.rotate_deg(scan_rotation) @ transform
    y_centers = y_centers.reshape(-1)
    x_centers = x_centers.reshape(-1)
    if not forward:
        transform = np.linalg.inv(transform)
    y_transformed, x_transformed = transform @ (y_centers, x_centers)
    y_transformed = y_transformed.reshape(shape)
    x_transformed = x_transformed.reshape(shape)
    return (y_transformed, x_transformed)
Beispiel #5
0
def generate_masks(reconstruct_shape,
                   mask_shape,
                   dtype,
                   lamb,
                   dpix,
                   semiconv,
                   semiconv_pix,
                   transformation=None,
                   cy=None,
                   cx=None,
                   cutoff=1,
                   cutoff_freq=np.float32('inf'),
                   method='subpix'):
    '''
    Generate the trotter mask stack.

    The y dimension is trimmed to size(y)//2 + 1 to exploit the inherent
    symmetry of the mask stack.

    Parameters
    ----------

    reconstruct_shape : tuple(int)
        Shape of the reconstructed area
    mask_shape : tuple(int)
        Shape of the detector
    dtype : numpy dtype
        dtype to use for the mask stack
    lamb : float
        Wavelength of the illuminating radiation in m
    dpix : float or (float, float)
        Scan step in m. Tuple (y, x) in case scan step is different in x and y direction.
    semiconv : float
        Semiconvergence angle of the illumination in radians
    semiconv_pix : float
        Semiconvergence angle in measured in detector pixel, i.e. radius of the zero order disk.
    transformation : numpy.ndarray, optional
        Matrix for affine transformation from the scan coordinate directions
        to the detector coordinate directions. This does not include the scale, which is handled by
        dpix, lamb, semiconv and semiconv_pix. It should only be used to rotate and flip
        the coordinate system as necessary. See also
        https://github.com/LiberTEM/LiberTEM/blob/master/src/libertem/corrections/coordinates.py
    cy, cx : float, optional
        Position of the optical axis on the detector in px, center of illumination.
        Default: Center of the detector
    cutoff : int, optional
        Minimum number of pixels in the positive and negative trotter. This can be used to purge
        very small trotters to reduce noise. Default is 1, i.e. no cutoff unless one trotter is
        empty.
    cutoff_freq: float
        Trotters belonging to a spatial frequency higher than this value in reciprocal pixel
        coordinates will be cut off.
    method : str, optional
        Can be :code:`'subpix'`(default) or :code:`'shift'` to switch between
        :meth:`mask_pair_subpix` and :meth:`mask_pair_shift` to generate a trotter pair.

    Returns
    -------
    masks : sparse.COO
        Masks in sparse.pydata.org COO format. y and x frequency index are FFT shifted, i.e. the
        zero frequency is at (0,0) and negative frequencies are in the far quadrant and reversed.
        The y frequency index is cut in half with size(y)//2 + 1 to exploit the inherent symmetry
        of a real-valued Fourier transform. The y and x index are then flattened to make it
        suitable for using it with MaskContainer.
    '''
    reconstruct_shape = np.array(reconstruct_shape)

    dpix = np.array(dpix)

    d_Kf = np.sin(semiconv) / lamb / semiconv_pix
    d_Qp = 1 / dpix / reconstruct_shape

    if cy is None:
        cy = mask_shape[0] / 2
    if cx is None:
        cx = mask_shape[1] / 2

    if transformation is None:
        transformation = identity()

    filter_center = circular(centerX=cx,
                             centerY=cy,
                             imageSizeX=mask_shape[1],
                             imageSizeY=mask_shape[0],
                             radius=semiconv_pix,
                             antialiased=True)

    half_reconstruct = (reconstruct_shape[0] // 2 + 1, reconstruct_shape[1])
    masks = []

    for row in range(half_reconstruct[0]):
        for column in range(half_reconstruct[1]):
            # Do an fftshift of q and p
            qp = np.array((row, column))
            flip = qp > (reconstruct_shape / 2)
            real_qp = qp.copy()
            real_qp[flip] = qp[flip] - reconstruct_shape[flip]

            if np.sum(real_qp**2) > cutoff_freq**2:
                masks.append(empty_mask(mask_shape, dtype=dtype))
                continue

            # Shift of diffraction order relative to zero order
            # without rotation in physical coordinates
            real_sy_phys, real_sx_phys = real_qp * d_Qp
            # We apply the transformation backwards to go
            # from physical orientation to detector orientation,
            # while the forward direction in center of mass analysis
            # goes from detector coordinates to physical coordinates
            # Afterwards, we transform from physical detector coordinates
            # to pixel coordinates
            sy, sx = ((real_sy_phys, real_sx_phys) @ transformation) / d_Kf

            masks.append(
                generate_mask(
                    cy=cy,
                    cx=cx,
                    sy=sy,
                    sx=sx,
                    filter_center=filter_center,
                    semiconv_pix=semiconv_pix,
                    cutoff=cutoff,
                    dtype=dtype,
                    method=method,
                ))

    # Since we go through masks in order, this gives a mask stack with
    # flattened (q, p) dimension to work with dot product and mask container
    masks = sparse.stack(masks)
    return masks
Beispiel #6
0
    def get_generic_results(self, img_sum, img_y, img_x, damage):
        from libertem.viz import CMAP_CIRCULAR_DEFAULT, visualize_simple
        ref_x = self.parameters["cx"]
        ref_y = self.parameters["cy"]
        y_centers_raw, x_centers_raw = center_shifts(img_sum, img_y, img_x,
                                                     ref_y, ref_x)
        shape = y_centers_raw.shape
        if self.parameters["flip_y"]:
            transform = flip_y()
        else:
            transform = identity()
        # Transformations are applied right to left
        transform = rotate_deg(self.parameters["scan_rotation"]) @ transform
        y_centers, x_centers = transform @ (y_centers_raw.reshape(-1),
                                            x_centers_raw.reshape(-1))

        y_centers = y_centers.reshape(shape)
        x_centers = x_centers.reshape(shape)

        if img_sum.dtype.kind == 'c':
            x_real, x_imag = np.real(x_centers), np.imag(x_centers)
            y_real, y_imag = np.real(y_centers), np.imag(y_centers)

            return COMResultSet([
                AnalysisResult(raw_data=x_real,
                               visualized=visualize_simple(x_real,
                                                           damage=damage),
                               key="x_real",
                               title="x [real]",
                               desc="x component of the center"),
                AnalysisResult(raw_data=y_real,
                               visualized=visualize_simple(y_real,
                                                           damage=damage),
                               key="y_real",
                               title="y [real]",
                               desc="y component of the center"),
                AnalysisResult(raw_data=x_imag,
                               visualized=visualize_simple(x_imag,
                                                           damage=damage),
                               key="x_imag",
                               title="x [imag]",
                               desc="x component of the center"),
                AnalysisResult(raw_data=y_imag,
                               visualized=visualize_simple(y_imag,
                                                           damage=damage),
                               key="y_imag",
                               title="y [imag]",
                               desc="y component of the center"),
            ])
        else:
            damage = damage & np.isfinite(x_centers) & np.isfinite(y_centers)
            # Make sure that an all-False `damage` is handled since np.max()
            # trips on an empty array.
            # As a remark -- the NumPy error message
            # "zero-size array to reduction operation maximum which has no identity"
            # is probably wrong since -np.inf is the identity element for maximum on
            # floating point numbers and should be returned here.
            if np.count_nonzero(damage) > 0:
                vmax = np.sqrt(
                    np.max(x_centers[damage]**2 + y_centers[damage]**2))
            else:
                vmax = 1
            f = CMAP_CIRCULAR_DEFAULT.rgb_from_vector(
                (x_centers, y_centers, 0), vmax=vmax)
            d = divergence(y_centers, x_centers)
            c = curl_2d(y_centers, x_centers)
            m = magnitude(y_centers, x_centers)

            return COMResultSet([
                AnalysisResult(raw_data=(x_centers, y_centers),
                               visualized=f,
                               key="field",
                               title="field",
                               desc="cubehelix colorwheel visualization",
                               include_in_download=False),
                AnalysisResult(raw_data=m,
                               visualized=visualize_simple(m, damage=damage),
                               key="magnitude",
                               title="magnitude",
                               desc="magnitude of the vector field"),
                AnalysisResult(raw_data=d,
                               visualized=visualize_simple(d, damage=damage),
                               key="divergence",
                               title="divergence",
                               desc="divergence of the vector field"),
                AnalysisResult(raw_data=c,
                               visualized=visualize_simple(c, damage=damage),
                               key="curl",
                               title="curl",
                               desc="curl of the 2D vector field"),
                AnalysisResult(raw_data=x_centers,
                               visualized=visualize_simple(x_centers,
                                                           damage=damage),
                               key="x",
                               title="x",
                               desc="x component of the center"),
                AnalysisResult(raw_data=y_centers,
                               visualized=visualize_simple(y_centers,
                                                           damage=damage),
                               key="y",
                               title="y",
                               desc="y component of the center"),
            ])
Beispiel #7
0
def generate_masks(reconstruct_shape,
                   mask_shape,
                   dtype,
                   lamb,
                   dpix,
                   semiconv,
                   semiconv_pix,
                   transformation=None,
                   center=None,
                   cutoff=1,
                   cutoff_freq=np.float32('inf'),
                   method='subpix'):

    reconstruct_shape = np.array(reconstruct_shape)

    dpix = np.array(dpix)

    d_Kf = np.sin(semiconv) / lamb / semiconv_pix
    d_Qp = 1 / dpix / reconstruct_shape

    if center is None:
        center = np.array(mask_shape) / 2

    if transformation is None:
        transformation = identity()

    cy, cx = center

    filter_center = circular(centerX=cx,
                             centerY=cy,
                             imageSizeX=mask_shape[1],
                             imageSizeY=mask_shape[0],
                             radius=semiconv_pix,
                             antialiased=True)

    half_reconstruct = (reconstruct_shape[0] // 2 + 1, reconstruct_shape[1])
    masks = []

    for row in range(half_reconstruct[0]):
        for column in range(half_reconstruct[1]):
            # Do an fftshift of q and p
            qp = np.array((row, column))
            flip = qp > (reconstruct_shape / 2)
            real_qp = qp.copy()
            real_qp[flip] = qp[flip] - reconstruct_shape[flip]

            if np.sum(real_qp**2) > cutoff_freq**2:
                masks.append(empty_mask(mask_shape, dtype=dtype))
                continue

            # Shift of diffraction order relative to zero order
            # without rotation in physical coordinates
            real_sy_phys, real_sx_phys = real_qp * d_Qp
            # We apply the transformation backwards to go
            # from physical orientation to detector orientation,
            # while the forward direction in center of mass analysis
            # goes from detector coordinates to physical coordinates
            # Afterwards, we transform from physical detector coordinates
            # to pixel coordinates
            sy, sx = ((real_sy_phys, real_sx_phys) @ transformation) / d_Kf

            masks.append(
                generate_mask(
                    cy=cy,
                    cx=cx,
                    sy=sy,
                    sx=sx,
                    filter_center=filter_center,
                    semiconv_pix=semiconv_pix,
                    cutoff=cutoff,
                    mask_shape=mask_shape,
                    dtype=dtype,
                    method=method,
                ))

    # Since we go through masks in order, this gives a mask stack with
    # flattened (q, p) dimension to work with dot product and mask container
    masks = sparse.stack(masks)
    return masks
Beispiel #8
0
def generate_skyline(reconstruct_shape, mask_shape, dtype, wavelength, dpix, semiconv,
        semiconv_pix, tiling_scheme, filter_center, debug_masks,
        transformation=None, center=None, cutoff=1):
    reconstruct_shape = np.array(reconstruct_shape)
    # print("mask shape", filter_center.shape)
    d_Kf = np.sin(semiconv)/wavelength/semiconv_pix
    d_Qp = 1/dpix/reconstruct_shape

    if center is None:
        center = np.array(mask_shape) / 2

    if transformation is None:
        transformation = identity()

    cy, cx = center

    half_reconstruct = (reconstruct_shape[0]//2 + 1, reconstruct_shape[1])

    target_ranges_p = np.zeros(
        (
            len(tiling_scheme),
            half_reconstruct[0],
            half_reconstruct[1],
            4  # y_start, y_stop, x_start, x_stop
        ),
        dtype=int
    )
    bbox_p = np.zeros_like(target_ranges_p)
    offsets_p = np.zeros(
        (
            len(tiling_scheme),
            half_reconstruct[0],
            half_reconstruct[1],
            2  # y, x
        ),
        dtype=int
    )
    nnz_p = np.zeros(
        (
            half_reconstruct[0],
            half_reconstruct[1],
        ),
        dtype=float
    )

    shift_map = np.zeros(half_reconstruct + (2, ))

    target_ranges_n = np.zeros_like(target_ranges_p)
    bbox_n = np.zeros_like(bbox_p)
    nnz_n = np.zeros_like(nnz_p)
    offsets_n = np.zeros_like(offsets_p)

    for tile_index, tile_slice in enumerate(tiling_scheme):
        tile_slice = tile_slice.discard_nav()
        center_tile = tile_slice.get(filter_center, sig_only=True)
        # print(tile_slice.origin)
        # print(tile_slice.shape)
        # print("cutoff generate skyline", cutoff)
        for row in range(half_reconstruct[0]):
            for column in range(half_reconstruct[1]):
                # Do an fftshift of q and p
                qp = np.array((row, column))
                flip = qp > (reconstruct_shape / 2)
                real_qp = qp.copy()
                real_qp[flip] = qp[flip] - reconstruct_shape[flip]

                # Shift of diffraction order relative to zero order
                # without rotation
                real_sy, real_sx = real_qp * d_Qp / d_Kf

                # We apply the transformation backwards to go
                # from physical coordinates to detector coordinates,
                # while the forward direction in center of mass analysis
                # goes from detector coordinates to physical coordinates
                sy, sx = (real_sy, real_sx) @ transformation

                shift_map[row, column] = (sy, sx)

                (mask_positive, target_tup_p, o_p,
                mask_negative, target_tup_n, o_n) = mask_tile_pair(
                    center_tile=center_tile,
                    tile_origin=np.array(tile_slice.origin),
                    tile_shape=np.array(tile_slice.shape),
                    filter_center=filter_center,
                    sy=sy,
                    sx=sx
                )
                if row != 0 or column != 0:
                    # m = (
                    #     mask_positive# / skyline["nnz_p"][row, column]
                    #     # - mask_negative# / skyline["nnz_n"][row, column]
                    # ) / 2
                    # print(row, column)
                    # print(np.where(np.abs(m - dbm) > 1e-7))
                    # print(np.max(np.abs(m - dbm)))
                    # print(dbm.shape)
                    # assert np.allclose(m, dbm)
                    nnz_p[row, column] += mask_positive.sum()
                    nnz_n[row, column] += mask_negative.sum()
                    target_ranges_p[tile_index, row, column] = target_tup_p.flatten()
                    target_ranges_n[tile_index, row, column] = target_tup_n.flatten()
                    offsets_p[tile_index, row, column] = o_p.flatten()
                    offsets_n[tile_index, row, column] = o_n.flatten()
                    bbox_p[tile_index, row, column] = bounding_box(mask_positive).flatten()
                    bbox_n[tile_index, row, column] = bounding_box(mask_negative).flatten()
                else:
                    c = center_tile*center_tile
                    # We will divide by 2 later in the skyline dot, have to
                    # compensate for that
                    nnz_p[0, 0] += c.sum() / 2
                    nnz_n[0, 0] += c.sum() / 2  # nnz_n remains to avoid culling
                    target_ranges_p[tile_index, 0, 0] = target_tup_p.flatten()
                    # target_ranges_n remains empty (0)
                    # offsets remain zero
                    bbox_p[tile_index, 0, 0] = bounding_box(c).flatten()
                    # Bounding box negative remains empty (0)
                    # m = center_tile
                # dbm = tile_slice.get(debug_masks[row, column], sig_only=True)
                # print(row, column)
                # print(np.where(np.abs(m - dbm) > 1e-7))
                # print(np.max(np.abs(m - dbm)))
                # print(dbm.shape)
                # assert np.allclose(m, dbm)
    for row in range(half_reconstruct[0]):
        for column in range(half_reconstruct[1]):
            if nnz_p[row, column] <= cutoff or nnz_n[row, column] <= cutoff:
                nnz_p[row, column] = 0
                nnz_n[row, column] = 0
                target_ranges_p[:, row, column] = 0
                target_ranges_n[:, row, column] = 0
                offsets_p[:, row, column] = 0
                offsets_n[:, row, column] = 0
                bbox_p[:, row, column] = 0
                bbox_n[:, row, column] = 0
    return {
        "target_ranges_p": target_ranges_p,
        "target_ranges_n": target_ranges_n,
        "nnz_p": nnz_p,
        "nnz_n": nnz_n,
        "offsets_p": offsets_p,
        "offsets_n": offsets_n,
        "bbox_p": bbox_p,
        "bbox_n": bbox_n,
        "shift_map": shift_map
    }