|
55 | 55 | from scipy.optimize import leastsq
|
56 | 56 | from sklearn.cluster import DBSCAN
|
57 | 57 |
|
| 58 | +from ase.build import fcc110 |
| 59 | +from pyTEMlib import probe_tools |
| 60 | + |
| 61 | +from scipy.ndimage import rotate |
| 62 | +from scipy.interpolate import RegularGridInterpolator |
| 63 | +from scipy.signal import fftconvolve |
| 64 | + |
58 | 65 |
|
59 | 66 | _SimpleITK_present = True
|
60 | 67 | try:
|
|
68 | 75 | 'install with: conda install -c simpleitk simpleitk ')
|
69 | 76 |
|
70 | 77 |
|
| 78 | +def get_atomic_pseudo_potential(fov, atoms, size=512, rotation=0): |
| 79 | + # Big assumption: the atoms are not near the edge of the unit cell |
| 80 | + # If any atoms are close to the edge (ex. [0,0]) then the potential will be clipped |
| 81 | + # before calling the function, shift the atoms to the center of the unit cell |
| 82 | + |
| 83 | + pixel_size = fov / size |
| 84 | + max_size = int(size * np.sqrt(2) + 1) # Maximum size to accommodate rotation |
| 85 | + |
| 86 | + # Create unit cell potential |
| 87 | + positions = atoms.get_positions()[:, :2] |
| 88 | + atomic_numbers = atoms.get_atomic_numbers() |
| 89 | + unit_cell_size = atoms.cell.cellpar()[:2] |
| 90 | + |
| 91 | + unit_cell_potential = np.zeros((max_size, max_size)) |
| 92 | + for pos, atomic_number in zip(positions, atomic_numbers): |
| 93 | + x = pos[0] / pixel_size |
| 94 | + y = pos[1] / pixel_size |
| 95 | + atom_width = 0.5 # Angstrom |
| 96 | + gauss_width = atom_width/pixel_size # important for images at various fov. Room for improvement with theory |
| 97 | + gauss = probe_tools.make_gauss(max_size, max_size, width = gauss_width, x0=x, y0=y) |
| 98 | + unit_cell_potential += gauss * atomic_number # gauss is already normalized to 1 |
| 99 | + |
| 100 | + # Create interpolation function for unit cell potential |
| 101 | + x_grid = np.linspace(0, fov * max_size / size, max_size) |
| 102 | + y_grid = np.linspace(0, fov * max_size / size, max_size) |
| 103 | + interpolator = RegularGridInterpolator((x_grid, y_grid), unit_cell_potential, bounds_error=False, fill_value=0) |
| 104 | + |
| 105 | + # Vectorized computation of the full potential map with max_size |
| 106 | + x_coords, y_coords = np.meshgrid(np.linspace(0, fov, max_size), np.linspace(0, fov, max_size), indexing="ij") |
| 107 | + xtal_x = x_coords % unit_cell_size[0] |
| 108 | + xtal_y = y_coords % unit_cell_size[1] |
| 109 | + potential_map = interpolator((xtal_x.ravel(), xtal_y.ravel())).reshape(max_size, max_size) |
| 110 | + |
| 111 | + # Rotate and crop the potential map |
| 112 | + potential_map = rotate(potential_map, rotation, reshape=False) |
| 113 | + center = potential_map.shape[0] // 2 |
| 114 | + potential_map = potential_map[center - size // 2:center + size // 2, center - size // 2:center + size // 2] |
| 115 | + |
| 116 | + potential_map = scipy.ndimage.gaussian_filter(potential_map,3) |
| 117 | + |
| 118 | + return potential_map |
| 119 | + |
| 120 | +def convolve_probe(ab, potential): |
| 121 | + # the pixel sizes should be the exact same as the potential |
| 122 | + final_sizes = potential.shape |
| 123 | + |
| 124 | + # Perform FFT-based convolution |
| 125 | + pad_height = pad_width = potential.shape[0] // 2 |
| 126 | + potential = np.pad(potential, ((pad_height, pad_height), (pad_width, pad_width)), mode='constant') |
| 127 | + |
| 128 | + probe, A_k, chi = probe_tools.get_probe(ab, potential.shape[0], potential.shape[1], scale = 'mrad', verbose= False) |
| 129 | + |
| 130 | + |
| 131 | + convolved = fftconvolve(potential, probe, mode='same') |
| 132 | + |
| 133 | + # Crop to original potential size |
| 134 | + start_row = pad_height |
| 135 | + start_col = pad_width |
| 136 | + end_row = start_row + final_sizes[0] |
| 137 | + end_col = start_col + final_sizes[1] |
| 138 | + |
| 139 | + image = convolved[start_row:end_row, start_col:end_col] |
| 140 | + |
| 141 | + return probe, image |
| 142 | + |
| 143 | + |
71 | 144 | # Wavelength in 1/nm
|
72 | 145 | def get_wavelength(e0):
|
73 | 146 | """
|
@@ -280,20 +353,21 @@ def diffractogram_spots(dset, spot_threshold, return_center=True, eps=0.1):
|
280 | 353 | return spots, center
|
281 | 354 |
|
282 | 355 |
|
283 |
| -def center_diffractogram(dset, return_plot = True, histogram_factor = None, smoothing = 1, min_samples = 100): |
| 356 | +def center_diffractogram(dset, return_plot = True, smoothing = 1, min_samples = 10, beamstop_size = 0.1): |
284 | 357 | try:
|
285 | 358 | diff = np.array(dset).T.astype(np.float16)
|
286 | 359 | diff[diff < 0] = 0
|
287 |
| - |
288 |
| - if histogram_factor is not None: |
289 |
| - hist, bins = np.histogram(np.ravel(diff), bins=256, range=(0, 1), density=True) |
290 |
| - threshold = threshold_otsu(diff, hist = hist * histogram_factor) |
291 |
| - else: |
292 |
| - threshold = threshold_otsu(diff) |
| 360 | + threshold = threshold_otsu(diff) |
293 | 361 | binary = (diff > threshold).astype(float)
|
294 | 362 | smoothed_image = ndimage.gaussian_filter(binary, sigma=smoothing) # Smooth before edge detection
|
295 | 363 | smooth_threshold = threshold_otsu(smoothed_image)
|
296 | 364 | smooth_binary = (smoothed_image > smooth_threshold).astype(float)
|
| 365 | + |
| 366 | + # add a circle to mask the beamstop |
| 367 | + x, y = np.meshgrid(np.arange(dset.shape[0]), np.arange(dset.shape[1])) |
| 368 | + circle = (x - dset.shape[0] / 2) ** 2 + (y - dset.shape[1] / 2) ** 2 < (beamstop_size * dset.shape[0] / 2) ** 2 |
| 369 | + smooth_binary[circle] = 1 |
| 370 | + |
297 | 371 | # Find the edges using the Sobel operator
|
298 | 372 | edges = sobel(smooth_binary)
|
299 | 373 | edge_points = np.argwhere(edges)
|
@@ -322,18 +396,21 @@ def calc_distance(c, x, y):
|
322 | 396 |
|
323 | 397 | finally:
|
324 | 398 | if return_plot:
|
325 |
| - fig, ax = plt.subplots(1, 4, figsize=(10, 4)) |
| 399 | + fig, ax = plt.subplots(1, 5, figsize=(14, 4), sharex=True, sharey=True) |
326 | 400 | ax[0].set_title('Diffractogram')
|
327 | 401 | ax[0].imshow(dset.T, cmap='viridis')
|
328 | 402 | ax[1].set_title('Otsu Binary Image')
|
329 | 403 | ax[1].imshow(binary, cmap='gray')
|
330 | 404 | ax[2].set_title('Smoothed Binary Image')
|
331 |
| - ax[2].imshow(smooth_binary, cmap='gray') |
332 |
| - ax[3].set_title('Edge Detection and Fitting') |
333 |
| - ax[3].imshow(edges, cmap='gray') |
334 |
| - ax[3].scatter(center[0], center[1], c='r', s=10) |
| 405 | + ax[2].imshow(smoothed_image, cmap='gray') |
| 406 | + |
| 407 | + ax[3].set_title('Smoothed Binary Image') |
| 408 | + ax[3].imshow(smooth_binary, cmap='gray') |
| 409 | + ax[4].set_title('Edge Detection and Fitting') |
| 410 | + ax[4].imshow(edges, cmap='gray') |
| 411 | + ax[4].scatter(center[0], center[1], c='r', s=10) |
335 | 412 | circle = plt.Circle(center, mean_radius, color='red', fill=False)
|
336 |
| - ax[3].add_artist(circle) |
| 413 | + ax[4].add_artist(circle) |
337 | 414 | for axis in ax:
|
338 | 415 | axis.axis('off')
|
339 | 416 | fig.tight_layout()
|
|
0 commit comments