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,460 @@
1
+ """
2
+ in-situ TEM Toolbox - TEM raw data conversion
3
+
4
+ Assembles of functions related to read and process raw dm4, dm3 or FEI TIA or Ceta datas.
5
+
6
+ Example:
7
+
8
+ import insitu_EMRaw as EM
9
+ IO.f2tif(path,1)
10
+
11
+ Created on Aug 8 2024
12
+ @author: Meng Li
13
+ """
14
+ import numpy as np
15
+ import tqdm
16
+ import ncempy
17
+ import tifffile
18
+
19
+ def img_2_8bit(img):
20
+ """
21
+ Convert image to Uint8bit with custom min and max intensity: vmin to 0, vmax=255
22
+
23
+ """
24
+ vmax = int(np.median(img)*2)
25
+ vmin = int(np.min(img))
26
+ if (vmax-vmin)==0:
27
+ scale =1
28
+ else:
29
+ scale = 255/(vmax-vmin)
30
+ imgOut=(img*scale).astype(np.uint8)
31
+ return imgOut
32
+
33
+ def getScaleBar(barLenPix,scale):
34
+ """
35
+ Function to convert truescale into str with correct format
36
+ """
37
+ truescale= barLenPix/scale
38
+
39
+ # Find the nearest power of 10 to the truescale
40
+ nearest_power = 10 ** np.floor(np.log10(truescale))
41
+
42
+ # Determine the most appropriate scale bar value
43
+ if truescale >= 5 * nearest_power:
44
+ scalebar = 5 * nearest_power
45
+ elif truescale >= 2 * nearest_power:
46
+ scalebar = 2 * nearest_power
47
+ else:
48
+ scalebar = nearest_power
49
+
50
+ # Calculate the scale in pixels
51
+ scalepix = int(scalebar * scale)
52
+
53
+ # Determine the appropriate unit for the scale bar text
54
+ if scalebar >= 1000:
55
+ text = f"{int(scalebar/1000)} um"
56
+ else:
57
+ text = f"{int(scalebar)} nm"
58
+ return scalepix, text
59
+
60
+ def AddScalePix(img,barLenPix =250, scale = 34.5, px=0.02,py=0.96, barthick=5,lw=1,stroke=True,tscale=1,textThick=4):
61
+ """
62
+ Function to add scale to image
63
+ Input:
64
+ img: greyscale image
65
+ scaleLen: desired scale length
66
+ scale: scale of the image: px/nm: 1000kX- 49.5; 700kX - 34.5
67
+ (px,py): position of the scale bar: r(0-1)
68
+ lw: boarder of the text
69
+ stroke=1: add stroke; stroke=0 No stroke
70
+
71
+ """
72
+ import cv2
73
+ h,w=img.shape
74
+
75
+ scalepix, text = getScaleBar(barLenPix, scale)
76
+
77
+ # w_scale=int(scale*barLen)
78
+ x1=int(w*px)
79
+ y1=int(h*py)
80
+ x2=x1+scalepix
81
+ y2=y1+barthick
82
+
83
+
84
+ #find bcakground color and make the reverse as scalebar color
85
+ img_bg=img[y1:y2,x1:x2]
86
+ mean_intensity=np.mean(img_bg)
87
+
88
+ if mean_intensity<128:
89
+ color = 255
90
+ else:
91
+ color = 0
92
+
93
+ bcolor=255-color
94
+
95
+ # text = str(barLen)+' nm'
96
+ font = cv2.FONT_HERSHEY_SIMPLEX#CV_FONT_HERSHEY_SIMPLEX normal size sans-serif font
97
+ if stroke == True:
98
+ cv2.putText(img,text,(x1,y1-8), font, tscale, bcolor,int(textThick+lw), cv2.LINE_AA) #Stroke
99
+ cv2.rectangle(img,(x1,y1),(x2,y2),bcolor,2)#boarder color
100
+
101
+
102
+ cv2.rectangle(img,(x1,y1),(x2,y2),color,-1)
103
+
104
+ cv2.putText(img,text,(x1,y1-8), font,tscale, color, textThick, cv2.LINE_AA)
105
+
106
+
107
+ def DM2Tiff(path,addscale = True,scalebar_pix=200, tscale=1.2, textThick=4):
108
+ """
109
+ Function to convert dm4 file to tiff.
110
+ If the input is .dm4 stacked image, convert the stack to tiff stack.
111
+ If the input is .dm4 single image, convert the image to tiff image.
112
+ If the input is a folder with .dm4 single images, convert the folder into tiff stack.
113
+ """
114
+ import ncempy
115
+ import tifffile
116
+
117
+
118
+
119
+ if path.endswith(('.dm4', '.dm3')):
120
+
121
+ dm = ncempy.read(path)
122
+ pathout =path[:-4]+'.tif'
123
+
124
+ if len(dm['data'].shape)==2:
125
+ #single image
126
+ print("convert .dm3/4 file to tiff image")
127
+ #for single image
128
+ frame=dm['data']
129
+ img=img_2_8bit(frame)
130
+
131
+ if addscale==True:
132
+
133
+ scale = 1/(dm['pixelSize'][0]*1e3)
134
+
135
+ AddScalePix(img,barLenPix =scalebar_pix, scale = scale, px=0.02,py=0.98, barthick=5,lw=1,stroke=True,tscale=tscale,textThick=textThick)
136
+
137
+ tifffile.imwrite(pathout,img, append=False)
138
+ else:
139
+ #image stacks
140
+ print("convert .dm4 stack to tiff stack")
141
+ imgs=dm['data']
142
+ nFrames,w,h =dm['data'].shape
143
+
144
+
145
+ with tifffile.TiffWriter(pathout,bigtiff=True) as tif:
146
+ for i in tqdm.tqdm(range(nFrames)):
147
+ img=imgs[i]
148
+ # img = cv2.convertScaleAbs(img)
149
+ # img = (img/ 65535.0 * 255).astype(np.uint8)
150
+ img=img_2_8bit(img)
151
+
152
+ # img =cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
153
+ if addscale==True:
154
+ scale = 1/(dm['pixelSize'][0]*1e3)
155
+ AddScalePix(img,barLenPix =scalebar_pix, scale = scale, px=0.02,py=0.98,barthick=5,lw=1,stroke=True,tscale=tscale,textThick=textThick)
156
+
157
+ tif.write(img, contiguous=True)
158
+
159
+ else:
160
+ files = [os.path.join(path, f) for f in os.listdir(path) if f.endswith(('.dm4', '.dm3'))]
161
+ #convert each file to tiff stack
162
+ print('Convert folder with dm3/4 to tiff stack')
163
+ nFrames=len(files)
164
+ pathout=path+'.tif'
165
+ with tifffile.TiffWriter(pathout,bigtiff=True) as tif:
166
+ for file_path in tqdm.tqdm(files):
167
+
168
+ dm = ncempy.read(file_path)
169
+ frame=dm['data']
170
+ img=img_2_8bit(frame)
171
+
172
+ if addscale==True:
173
+
174
+ scale = 1/(dm['pixelSize'][0]*1e3)
175
+
176
+ AddScalePix(img,barLenPix =scalebar_pix, scale = scale, px=0.02,py=0.98, barthick=5,lw=1,stroke=True,tscale=tscale,textThick=textThick)
177
+
178
+ tif.write(img, contiguous=True)
179
+ print("Convertion done!")
180
+
181
+
182
+ def GMS2folder(root_dir):
183
+
184
+ import os
185
+ import shutil
186
+ import re
187
+ import tqdm
188
+ """
189
+ Function to convert GMS saved in situ data into one folder named by frame name
190
+
191
+ input: path to root_dir : the folder before Hour_00
192
+ output: dm4 images saved in one folder
193
+ """
194
+
195
+ # Define the target directory
196
+ target_dir = root_dir + '_output'
197
+ if not os.path.exists(target_dir):
198
+ os.makedirs(target_dir)
199
+
200
+ # Initialize a list to hold file paths and their corresponding timestamps
201
+ images = []
202
+
203
+ # Regular expression to extract hour, minute, second, and frame number from filenames
204
+ pattern = re.compile(r"Hour_(\d+)_Minute_(\d+)_Second_(\d+)_Frame_(\d+)\.dm4")
205
+
206
+ print("Converting GMS datasets into one folder...")
207
+
208
+ # Walk through the directory structure and count frames per second
209
+ fps = 0
210
+ for root, dirs, files in os.walk(root_dir):
211
+ for file in files:
212
+ if file.endswith(".dm4"): # Check if the file is an image file
213
+ match = pattern.search(file)
214
+ if match:
215
+ hour, minute, second, frame = match.groups()
216
+ # Create a timestamp tuple for sorting
217
+ timestamp = (int(hour), int(minute), int(second), int(frame))
218
+ # Add the file path and timestamp to the list
219
+ images.append((os.path.join(root, file), timestamp))
220
+ # Count frames in the Second_00 folder
221
+ if int(second) == 0:
222
+ fps += 1
223
+
224
+ print(f"Frames per second (fps) detected: {fps}")
225
+
226
+ # Sort the images by their timestamps
227
+ images.sort(key=lambda x: x[1])
228
+
229
+ # Copy and rename images to the target directory in the correct order
230
+ for index, (filepath, timestamp) in enumerate(images, start=0):
231
+ new_filename = f"F{index:06d}.dm4"
232
+ new_filepath = os.path.join(target_dir, new_filename)
233
+ shutil.copy(filepath, new_filepath)
234
+
235
+ print(f"Total number of images: {len(images)}")
236
+ print(f"All images have been saved to: {target_dir}")
237
+ return target_dir,fps
238
+
239
+ def DMfolder2tiff(folder_path):
240
+ """
241
+ Function to convert entire folder of dm files into tiff
242
+ If the dm file is single image, convert to tiff single image
243
+ If the dm file is stack, convert to tiff stack
244
+ """
245
+
246
+ print("Batch convert folder into tiff or movie: ")
247
+ print("----------------------------------------")
248
+ # List all files in the directory
249
+ import os
250
+ files = os.listdir(folder_path)
251
+
252
+ # Filter files with .ser extension
253
+ dm_files = [file for file in files if file.endswith(('.dm4', '.dm3'))]
254
+
255
+ # Process each .ser file
256
+ for dm_file in dm_files:
257
+ print("convert ", dm_file)
258
+ # Check file size
259
+
260
+ file_path = os.path.join(folder_path, dm_file)
261
+ file_size = os.path.getsize(file_path)
262
+ if file_size == 0:
263
+ print(f"Skipping empty file: {dm_file}")
264
+ continue
265
+ DM2Tiff(file_path,scalebar_pix=250,tscale=1.2, textThick=4)
266
+ print("----------------------------------------")
267
+ print("Convertion done! ")
268
+
269
+
270
+ # def getScaleBar(barLenPix,scale):
271
+ # """
272
+ # Function to convert truescale into str with correct format
273
+ # """
274
+ # truescale= barLenPix/scale
275
+
276
+ # # Find the nearest power of 10 to the truescale
277
+ # nearest_power = 10 ** np.floor(np.log10(truescale))
278
+
279
+ # # Determine the most appropriate scale bar value
280
+ # if truescale >= 5 * nearest_power:
281
+ # scalebar = 5 * nearest_power
282
+ # elif truescale >= 2 * nearest_power:
283
+ # scalebar = 2 * nearest_power
284
+ # else:
285
+ # scalebar = nearest_power
286
+
287
+ # # Calculate the scale in pixels
288
+ # scalepix = int(scalebar * scale)
289
+
290
+ # # Determine the appropriate unit for the scale bar text
291
+ # if scalebar >= 1000:
292
+ # text = f"{int(scalebar/1000)} um"
293
+ # else:
294
+ # text = f"{int(scalebar)} nm"
295
+ # return scalepix, text
296
+
297
+ # def AddScalePix(img,barLenPix =250, scale = 34.5, px=0.02,py=0.96, barthick=5,lw=1,stroke=True,tscale=1,textThick=4):
298
+ # """
299
+ # Function to add scale to image
300
+ # Input:
301
+ # img: greyscale image
302
+ # scaleLen: desired scale length
303
+ # scale: scale of the image: px/nm: 1000kX- 49.5; 700kX - 34.5
304
+ # (px,py): position of the scale bar: r(0-1)
305
+ # lw: boarder of the text
306
+ # stroke=1: add stroke; stroke=0 No stroke
307
+ # tscale: scale of the text
308
+ # textThick: thickness of the text in pxs
309
+
310
+ # """
311
+ # import cv2
312
+ # h,w=img.shape
313
+
314
+ # scalepix, text = getScaleBar(barLenPix, scale)
315
+
316
+ # # w_scale=int(scale*barLen)
317
+ # x1=int(w*px)
318
+ # y1=int(h*py)
319
+ # x2=x1+scalepix
320
+ # y2=y1+barthick
321
+
322
+
323
+ # #find bcakground color and make the reverse as scalebar color
324
+ # img_bg=img[y1:y2,x1:x2]
325
+ # mean_intensity=np.mean(img_bg)
326
+
327
+ # if mean_intensity<128:
328
+ # color = 255
329
+ # else:
330
+ # color = 0
331
+
332
+
333
+ # bcolor=255-color
334
+
335
+
336
+
337
+ # # text = str(barLen)+' nm'
338
+ # font = cv2.FONT_HERSHEY_SIMPLEX#CV_FONT_HERSHEY_SIMPLEX normal size sans-serif font
339
+ # if stroke == True:
340
+ # cv2.putText(img,text,(x1,y1-8), font, tscale, bcolor,int(textThick+lw), cv2.LINE_AA) #Stroke
341
+ # cv2.rectangle(img,(x1,y1),(x2,y2),bcolor,2)#boarder color
342
+
343
+
344
+ # cv2.rectangle(img,(x1,y1),(x2,y2),color,-1)
345
+
346
+ # cv2.putText(img,text,(x1,y1-8), font,tscale, color, textThick, cv2.LINE_AA)
347
+ # #Save image to tiff or tiff stack
348
+
349
+ def convertser(path,addscale = True,scalebar_pct=20,fps=10):
350
+ """
351
+ Convert ser file to image, stack to tiff stack and movie
352
+ input:
353
+ path: .ser file path
354
+ addscale: whether scale bar is added
355
+ scalebar_pix: roughly how many pixels the scale bar takes
356
+ scalebar_pct: roughly how much percnet the scale bar takes on the image
357
+ fps: for .ser stack, the fps of the original data
358
+ tscale: scale of the text for the scale bar
359
+ textThick: thickness of the text for the scale bar
360
+ Output:
361
+ single .ser image: Tiff image
362
+ .ser stack: Tiff stack and avi movie
363
+ """
364
+ import ncempy
365
+ import tifffile
366
+ import tqdm
367
+ import cv2
368
+
369
+ import os
370
+ os.environ["IMAGEIO_FFMPEG_EXE"] = "/opt/homebrew/bin/ffmpeg"
371
+ import insituTEM.insitu_IO as IO
372
+
373
+ # print("convert .ser file to tiff image")
374
+ ser = ncempy.read(path)
375
+ pathout =path[:-4]+'.tif'
376
+
377
+
378
+ if len(ser['data'].shape)==2:
379
+ #single image
380
+ # print("convert .ser file to tiff image")
381
+ #for single image
382
+ img=ser['data']
383
+ img =cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
384
+ # img = cv2.convertScaleAbs(img)
385
+ # img = (img/ 65535.0 * 255).astype(np.uint8)
386
+
387
+ if addscale==True:
388
+ w= ser['data'].shape[0]
389
+ scalebar_pix = round(w/100) *scalebar_pct
390
+ tscale=round(w/1024)
391
+ textThick=round(w/1024)*2
392
+
393
+ scale = 1/(ser['pixelSize'][0]*1e9)
394
+
395
+ # scalebar= int(scalebar_pix*scale/100)*100
396
+
397
+ AddScalePix(img,barLenPix =scalebar_pix, scale = scale, px=0.02,py=0.98, barthick=5,lw=1,stroke=True,tscale=tscale,textThick=textThick)
398
+
399
+ tifffile.imwrite(pathout,img, append=False)
400
+
401
+ else:
402
+ #image stacks
403
+ # print("convert .ser file to tiff stack")
404
+ imgs=ser['data']
405
+ nFrames,w,h =ser['data'].shape
406
+ scalebar_pix = round(w/100) *scalebar_pct
407
+ # fps = int(input('Input desired output fps:'))
408
+ pathout2 =path[:-4]+'_'+str(fps)+'.avi'
409
+ codec = cv2.VideoWriter_fourcc(*'MJPG')
410
+ video_out = cv2.VideoWriter(pathout2, codec , fps, (w, h),isColor=False)
411
+
412
+ with tifffile.TiffWriter(pathout,bigtiff=True) as tif:
413
+ for i in tqdm.tqdm(range(nFrames)):
414
+ img=imgs[i]
415
+ # img = cv2.convertScaleAbs(img)
416
+ # img = (img/ 65535.0 * 255).astype(np.uint8)
417
+ img =cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
418
+ if addscale==True:
419
+ scale = 1/(ser['pixelSize'][0]*1e9)
420
+ AddScalePix(img,barLenPix =scalebar_pix, scale = scale, px=0.02,py=0.98,barthick=5,lw=1,stroke=True,tscale=tscale,textThick=textThick)
421
+
422
+ tif.write(img, contiguous=True)
423
+ video_out.write(img)
424
+
425
+
426
+
427
+
428
+ def SerFolder2Tiff(folder_path,fps=15):
429
+ print("Batch convert folder into tiff or movie: ")
430
+ print("----------------------------------------")
431
+ # List all files in the directory
432
+ import os
433
+ files = os.listdir(folder_path)
434
+
435
+ # Filter files with .ser extension
436
+ ser_files = [file for file in files if file.endswith('.ser')]
437
+
438
+ # Process each .ser file
439
+ for ser_file in ser_files:
440
+ print("convert ", ser_file)
441
+ # Check file size
442
+
443
+ file_path = os.path.join(folder_path, ser_file)
444
+ file_size = os.path.getsize(file_path)
445
+ if file_size == 0:
446
+ print(f"Skipping empty file: {ser_file}")
447
+ continue
448
+
449
+ # Try to convert the SER file, and skip if an error occurs
450
+ try:
451
+ # convertser(file_path, fps=fps, scalebar_pix=scalebar_pix, tscale=tscale, textThick=textThick)
452
+ convertser(file_path, fps=fps, scalebar_pct=20)
453
+
454
+ except Exception as e:
455
+ print(f"Error converting {ser_file}: {e}")
456
+ continue # Skip to the next file
457
+ # convertser(file_path,fps=10,scalebar_pix=scalebar_pix,tscale=tscale, textThick=textThick)
458
+
459
+ print("----------------------------------------")
460
+ print("Convertion done! ")