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)
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)
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"), ])
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)
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
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"), ])
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
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 }