insituTEM 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,359 @@
1
+ """
2
+ in-situ TEM Toolbox - Diffraction pattern analysis
3
+
4
+ Assembles of functions related to process FFT and diffraction patterns
5
+
6
+ Example:
7
+
8
+ import insitu_Diff as Diff
9
+ Diff.f2tif(path,1)
10
+
11
+ Created on Aug 8 2024
12
+ @author: Meng Li
13
+ """
14
+ import numpy as np
15
+ import cv2
16
+ # from skimage.feature import blob_dog
17
+ import matplotlib.pyplot as plt
18
+ # from scipy import ndimage
19
+ import math
20
+
21
+ # import pandas as pd
22
+
23
+ def getFFT(im):
24
+ """
25
+ Function to calculate FFT of the image
26
+ input: im- np array of image data
27
+ output: fft image
28
+ """
29
+ # Get the original dimensions of the image
30
+ h, w = im.shape
31
+
32
+ size = min(h,w) #in case h w don't match
33
+ im_crop=im[:size,:size] #crop image
34
+ fft_image = np.fft.fft2(im_crop)
35
+ fft_shifted = np.fft.fftshift(fft_image)
36
+ fft = 20 * np.log(np.abs(fft_shifted) + 1e-10) # Add small constant to avoid log(0)
37
+
38
+ return fft
39
+
40
+ def spotFFT(fft, sigmaMax=2.5, sigmaMin=2.45, sigma_ratio=20, thrs=0.98, R_tol=2, fftfilter=3, tolerance=5, display=True):
41
+ """
42
+ Function to get diffraction spot positions in FFT
43
+ Input: fft- FFT image
44
+ sigmaMax, sigmaMin, sigma_ratio, thrs - Parameters for blob detection
45
+ R_tol - tolerance of center spot
46
+ fftfilter- filtersize to denoise FFT to make diffraction spots more visible
47
+ tolerance - tolerance for symmetry matching
48
+
49
+ Output:
50
+ Data of all detected spots: X(px) Y(px)
51
+ Image labeling the spots
52
+ """
53
+ from skimage.feature import blob_dog
54
+ import matplotlib.pyplot as plt
55
+ from scipy import ndimage
56
+ import math
57
+
58
+ import numpy as np
59
+ import pandas as pd
60
+
61
+ h, w = fft.shape
62
+ size = max(h, w)
63
+ scale_fft = 1 / (size)
64
+
65
+ x_c, y_c = int(w / 2), int(h / 2)
66
+ image = ndimage.maximum_filter(fft, size=fftfilter, mode='wrap')
67
+ blobs_dog = blob_dog(image, max_sigma=sigmaMax, min_sigma=sigmaMin, threshold=thrs, sigma_ratio=sigma_ratio, exclude_border=True)
68
+ blobs_dog[:, 2] *= math.sqrt(2)
69
+
70
+
71
+
72
+ data = []
73
+ blob_set = set()
74
+
75
+ for j in range(len(blobs_dog)):
76
+ y, x = blobs_dog[j, 1] - y_c, blobs_dog[j, 0] - x_c
77
+ distance = math.sqrt(x**2 + y**2)
78
+ if distance < R_tol:
79
+ continue
80
+
81
+ data.append([blobs_dog[j, 1], blobs_dog[j, 0]])
82
+ blob_set.add((blobs_dog[j, 1], blobs_dog[j, 0]))
83
+
84
+ # Ensure symmetry
85
+ for x, y in data:
86
+ sym_x = 2 * x_c - x
87
+ sym_y = 2 * y_c - y
88
+
89
+ if not any(math.isclose(sym_x, bx, abs_tol=tolerance) and math.isclose(sym_y, by, abs_tol=tolerance) for bx, by in blob_set):
90
+ data.append([sym_x, sym_y])
91
+ blob_set.add((sym_x, sym_y))
92
+
93
+ data = np.array(data)
94
+
95
+ if display == True:
96
+ fig, axes = plt.subplots(1, 2, figsize=(10, 5), sharex=True, sharey=True)
97
+ ax = axes.ravel()
98
+ ax[0].set_title('FFT')
99
+ ax[0].imshow(fft)
100
+ ax[1].set_title('DP detection')
101
+ ax[1].imshow(image)
102
+ plt.tight_layout()
103
+ plt.scatter(data[:, 0], data[:, 1], marker='+', color='red')
104
+ for i, (x_i, y_i) in enumerate(zip(data[:, 0], data[:, 1])):
105
+ plt.annotate(str(i+1), (x_i, y_i), textcoords="offset points", xytext=(0,10), ha='center', color='white')
106
+
107
+ return data
108
+
109
+ def DP_mask(fft,circle_radius=10, center_rad=1,sigmaMax=2.5, sigmaMin=2.45, sigma_ratio=20, thrs=0.98, R_tol=2, fftfilter=3, tolerance=5):
110
+ """
111
+ Create a mask image with circles drawn at detected diffraction points.
112
+
113
+ Parameters:
114
+ image_shape (tuple): Shape of the image (height, width).
115
+ points (np.ndarray): Array of detected points [[x1, y1], [x2, y2], ...].
116
+ circle_radius (int): Radius of the circles to draw.
117
+ center_rad: radius of center circle
118
+ sigma...: parameters for detect DP
119
+
120
+ Returns:
121
+ np.ndarray: Mask image with drawn circles.
122
+ """
123
+
124
+ mask = np.zeros(fft.shape, dtype=np.uint8)
125
+ points= spotFFT(fft,sigmaMax,sigmaMin,sigma_ratio,thrs,R_tol,fftfilter,tolerance,display=False)
126
+ for x, y in points:
127
+ cv2.circle(mask, (int(x), int(y)), circle_radius, 255, thickness=-1)
128
+
129
+ if center_rad != 0:
130
+ center=int(fft.shape[0]/2)
131
+ cv2.circle(mask, (center, center), center_rad, 255, thickness=-1)
132
+ return mask
133
+
134
+ def maskedIFFT(image,mask,mix_ratio=0.3):
135
+ """
136
+ generate masked image from FFT
137
+ Input: original image; mask; mix_ratio for IFFT filtering
138
+ Output: FFT_image, IFFT image, IFFT filterd image
139
+ Example: fft_image, ifft_image,filtered_image=maskedIFFT(image,mask,mix_ratio)
140
+ """
141
+ h,w=image.shape
142
+
143
+ if h !=w:
144
+ d=min(h,w)
145
+ image=image[0:d,0:d]
146
+
147
+ # Perform FFT on the image
148
+ fft = cv2.dft(np.float32(image), flags=cv2.DFT_COMPLEX_OUTPUT)
149
+ fft_shifted = np.fft.fftshift(fft)
150
+ fft_image=np.log(1 + cv2.magnitude(fft_shifted[:, :, 0], fft_shifted[:, :, 1])) #using fft_image to apply mask results in empty image
151
+
152
+ # Ensure the mask image has the same size as the FFT
153
+ mask_image = cv2.resize(mask,(fft_shifted.shape[1], fft_shifted.shape[0]))
154
+
155
+ # Apply the mask to the FFT
156
+ masked_fft_shifted = cv2.bitwise_and(fft_shifted, fft_shifted, mask=mask_image)
157
+
158
+ # Perform IFFT on the masked FFT
159
+ masked_fft = np.fft.ifftshift(masked_fft_shifted)
160
+ masked_image = cv2.idft(masked_fft)
161
+ masked_image = cv2.magnitude(masked_image[:, :, 0], masked_image[:, :, 1])
162
+
163
+ #convert to 8bit images
164
+ fft_image=cv2.normalize(fft_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
165
+ ifft_image=cv2.normalize(masked_image, None, 0, 128, cv2.NORM_MINMAX, cv2.CV_8U) #cv2.normalize(src, dst, alpha, beta, norm_type, dtype)
166
+
167
+ # # Normalize the pixel values to the range of 0-255
168
+ ifft_image_8 = cv2.normalize(ifft_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
169
+
170
+
171
+ # Add IFFT to original image with mix_ratio
172
+ masked_image_normalized = cv2.normalize(ifft_image, None, 0, int(255*mix_ratio), cv2.NORM_MINMAX, cv2.CV_8U)
173
+ image_normalized = cv2.normalize(image, None, 0, int(255*(1-mix_ratio*0.5)), cv2.NORM_MINMAX, cv2.CV_8U)
174
+
175
+ filtered_image=cv2.add(image_normalized,masked_image_normalized)
176
+
177
+
178
+ return fft_image, ifft_image, filtered_image
179
+
180
+
181
+ def measureFFT(fft,path,scale,j=0,sigmaMax=2.5,sigmaMin=2.4,thrs=0.97,R_tol=2,fftfilter=2,savefig=True,displayfig=True):
182
+ """
183
+ Function to measure diffraction spots in FFT
184
+ Input: fft- FFT image
185
+ path: path to save data
186
+ sigmaMax, sigmaMin, thrs - Parameters for blob detection
187
+ R_tol - tolerance of center spot
188
+
189
+ Output:
190
+ Data of all detected spots: Point X(px) Y(px) distance(px) angle(˚) 1/d (1/nm) d (nm)
191
+ Image labeling the spots
192
+
193
+ """
194
+ from skimage.feature import blob_dog
195
+ import matplotlib.pyplot as plt
196
+ from scipy import ndimage
197
+ import math
198
+
199
+ import numpy as np
200
+ import pandas as pd
201
+
202
+ h,w = fft.shape
203
+ size=max(h,w)
204
+ scale_fft=1/(scale*size)
205
+
206
+ x_c=int(w/2)
207
+ y_c=int(h/2)
208
+ center=x_c/2 #center of cropped_fft
209
+
210
+ fft_crop=fft[x_c-int(center):x_c+int(center),y_c-int(center):y_c+int(center)]
211
+ image= ndimage.maximum_filter(fft_crop, size=fftfilter, mode='wrap')
212
+ blobs_dog = blob_dog(image, max_sigma=sigmaMax,min_sigma=sigmaMin, threshold=thrs,sigma_ratio=10,exclude_border=True)
213
+ blobs_dog[:, 2] = blobs_dog[:, 2] * math.sqrt(2)
214
+
215
+
216
+ title = 'DP detection'
217
+ title1= 'FFT_'+str(j)
218
+ fig, axes = plt.subplots(1, 2, figsize=(10, 5), sharex=True, sharey=True)
219
+ ax = axes.ravel()
220
+
221
+ ax[0].set_title(title1)
222
+ ax[0].imshow(fft_crop)
223
+ ax[1].set_title(title)
224
+ ax[1].imshow(image)
225
+
226
+ plt.tight_layout()
227
+
228
+ pathout=path[:-4]+'_'+str(j)+'_FFT.csv'
229
+ pathout_img=path[:-4]+'_'+str(j)+'_DP.tif'
230
+
231
+
232
+ data=np.zeros((len(blobs_dog)-1,8))
233
+
234
+
235
+ idx=0
236
+ for j in range(len(blobs_dog)):
237
+
238
+
239
+ y=blobs_dog[j,1]-center
240
+ x=blobs_dog[j,0]-center
241
+
242
+
243
+ distance=math.sqrt(x**2+y**2)#px distance to center
244
+ if distance<R_tol: #skip center point
245
+ continue
246
+
247
+ theta=math.atan2(x,y)#atan gives +-90˚ values, atan2 gives 180
248
+ angle=0-math.degrees(theta)
249
+ data[idx,:7] = [j , blobs_dog[j, 1], blobs_dog[j, 0], distance, angle, distance * scale_fft, 1 / (distance * scale_fft)]
250
+ idx += 1
251
+
252
+
253
+ plt.scatter(data[:,1],data[:,2],marker='+',color='red')
254
+ for i, (x_i, y_i) in enumerate(zip(data[:,1],data[:,2])):
255
+ plt.annotate(str(i+1), (x_i, y_i), textcoords="offset points", xytext=(0,10), ha='center',color='white')
256
+
257
+
258
+ data[:,7]=data[:,4]-data[0,4]
259
+
260
+ columns= ["Point","X(px)","Y(px)","distance(px)","angle to X(˚)","1/d (1/nm)","d (nm)","angle to P1(˚)"]
261
+ # Set pandas global display format for floats
262
+ pd.options.display.float_format = '{:.3f}'.format
263
+ # Create a DataFrame
264
+ df = pd.DataFrame(data, columns=columns)
265
+ df.to_csv(pathout,index=False)
266
+ if savefig:
267
+ plt.savefig(pathout_img,bbox_inches='tight', pad_inches=0.1,dpi=150)
268
+ if displayfig:
269
+ plt.show()
270
+
271
+ return df
272
+
273
+
274
+
275
+
276
+ def FFT2Polar(fft):
277
+ """
278
+ Function to convert fft image to polar image
279
+ input: fft pimage
280
+ output: polar FFt image
281
+ """
282
+ import cv2
283
+ #function to convert fft image to polar coordinates from the center from 0-180 deg
284
+ center = (fft.shape[1] // 2, fft.shape[0] // 2)
285
+ polar_fft = cv2.linearPolar(fft, center, center[0], cv2.WARP_FILL_OUTLIERS)
286
+ # polar_fft= polar_fft[:,:center[1]]
287
+ #resize the image to realistic size
288
+ resized_polar_fft = cv2.resize(polar_fft, (center[0], 360))
289
+ # plt.imshow(resized_polar_fft)
290
+
291
+ p_fft1=resized_polar_fft[:180,:]
292
+ p_fft2=resized_polar_fft[180:,:]
293
+ p_fft=p_fft1+p_fft2
294
+ return p_fft
295
+
296
+ def maskedFFT(image,mask):
297
+ """
298
+ generate masked image from FFT
299
+ Input: original image; mask
300
+ Output: FFT_image, filtered IFFT image
301
+ """
302
+ h,w=image.shape
303
+
304
+ if h !=w:
305
+ d=min(h,w)
306
+ image=image[0:d,0:d]
307
+
308
+ # Perform FFT on the image
309
+ fft = cv2.dft(np.float32(image), flags=cv2.DFT_COMPLEX_OUTPUT)
310
+ fft_shifted = np.fft.fftshift(fft)
311
+ fft_image=np.log(1 + cv2.magnitude(fft_shifted[:, :, 0], fft_shifted[:, :, 1]))
312
+
313
+
314
+ # Ensure the mask image has the same size as the FFT
315
+ mask_image = cv2.resize(mask,(fft_shifted.shape[1], fft_shifted.shape[0]))
316
+
317
+ # Apply the mask to the FFT
318
+ masked_fft_shifted = cv2.bitwise_and(fft_shifted, fft_shifted, mask=mask_image)
319
+
320
+
321
+ # Perform inverse FFT on the masked FFT
322
+ masked_fft = np.fft.ifftshift(masked_fft_shifted)
323
+ masked_image = cv2.idft(masked_fft)
324
+ masked_image = cv2.magnitude(masked_image[:, :, 0], masked_image[:, :, 1])
325
+
326
+ #convert to 8bit images
327
+ fft_image=cv2.normalize(fft_image, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
328
+ masked_image=cv2.normalize(masked_image, None, 0, 128, cv2.NORM_MINMAX, cv2.CV_8U) #cv2.normalize(src, dst, alpha, beta, norm_type, dtype)
329
+
330
+
331
+
332
+ return fft_image, masked_image
333
+
334
+
335
+ def FFTstack(stackpath,ndfilersize=3,fps=15):
336
+ import tqdm
337
+ from scipy import ndimage
338
+ import tifffile
339
+ from insituTEM import insitu_IO as IO
340
+ """
341
+ Apply FFT to the entire stack.
342
+ """
343
+ pathout=stackpath[:-4]+'_FFT.tif'
344
+ stack =tifffile.imread(stackpath)
345
+
346
+ nFrames, h,w = stack.shape
347
+ print("------------------------------------------")
348
+ print("Getting FFT to the stack:")
349
+ with tifffile.TiffWriter(pathout,bigtiff=True) as tif:
350
+ for i in tqdm.tqdm(range(nFrames)):
351
+ fr=stack[i]
352
+ fft=Diff.getFFT(fr)
353
+ if ndfilersize!=0:
354
+ fft = ndimage.maximum_filter(fft, size=ndfilersize, mode='wrap')
355
+ fft_image=cv2.normalize(fft, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
356
+ tif.write(fft_image, contiguous=True)
357
+ IO.tiff2avi(pathout,fps=15)
358
+
359
+ print("Conversion done!")