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.
- insituTEM/__ init __.py +14 -0
- insituTEM/insitu_DENS.py +318 -0
- insituTEM/insitu_DP.py +228 -8
- insituTEM/insitu_Diff.py +359 -0
- insituTEM/insitu_EMraw.py +460 -0
- insituTEM/insitu_IO.py +512 -111
- insituTEM/insitu_Preprocess.py +229 -25
- insituTEM/insitu_alignment.py +219 -44
- insitutem-0.1.7.dist-info/METADATA +20 -0
- insitutem-0.1.7.dist-info/RECORD +12 -0
- {insituTEM-0.1.6.dist-info → insitutem-0.1.7.dist-info}/WHEEL +1 -1
- insituTEM-0.1.6.dist-info/METADATA +0 -13
- insituTEM-0.1.6.dist-info/RECORD +0 -9
- {insituTEM-0.1.6.dist-info → insitutem-0.1.7.dist-info}/top_level.txt +0 -0
insituTEM/__ init __.py
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
__version__ = '1.0.0'
|
|
3
|
+
__author__ = 'Meng Li'
|
|
4
|
+
|
|
5
|
+
import insitu_alignment as AL
|
|
6
|
+
import insitu_DP as DP
|
|
7
|
+
import insitu_IO as IO
|
|
8
|
+
import insitu_Preprocess as PP
|
|
9
|
+
import insitu_Diff as Diff
|
|
10
|
+
import insitu_EMraw as EM
|
|
11
|
+
import insitu_DENS as DENS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ['function1', 'Class2']
|
insituTEM/insitu_DENS.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
in-situ TEM Toolbox - DENS holder data process
|
|
3
|
+
|
|
4
|
+
Assembles of functions related to process raw data by DENS holders
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
|
|
8
|
+
from insitu_TEM import insitu_DENS as DENS
|
|
9
|
+
DENS.XXX
|
|
10
|
+
|
|
11
|
+
Created on Tue May 13 2025
|
|
12
|
+
@author: Meng Li mli4@bnl.gov
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
Codes for Stream data process
|
|
18
|
+
"""
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
def read_pstrace_csv(filepath):
|
|
22
|
+
# Try several encodings until one works
|
|
23
|
+
for encoding in ['utf-16', 'utf-8', 'ISO-8859-1']:
|
|
24
|
+
try:
|
|
25
|
+
with open(filepath, 'r', encoding=encoding) as f:
|
|
26
|
+
lines = [line.strip() for line in f if line.strip()]
|
|
27
|
+
break
|
|
28
|
+
except UnicodeDecodeError:
|
|
29
|
+
continue
|
|
30
|
+
else:
|
|
31
|
+
raise UnicodeDecodeError("Failed to decode file using common encodings.")
|
|
32
|
+
|
|
33
|
+
# Detect measurement type (e.g., CV I vs E Scan 1)
|
|
34
|
+
measurement_type = None
|
|
35
|
+
for line in lines:
|
|
36
|
+
if "vs" in line and not line.lower().startswith("date"):
|
|
37
|
+
measurement_type = line
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
# Detect actual column header (like "V,µA" or "s,V")
|
|
41
|
+
header_index = None
|
|
42
|
+
for i, line in enumerate(lines):
|
|
43
|
+
if ',' in line and all(any(c.isalpha() for c in token) for token in line.split(',')):
|
|
44
|
+
header_index = i
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
if header_index is None:
|
|
48
|
+
raise ValueError("Could not find a valid data header (e.g., 'V,µA' or 's,V')")
|
|
49
|
+
|
|
50
|
+
headers = [h.strip() for h in lines[header_index].split(',')]
|
|
51
|
+
data_lines = lines[header_index + 1:]
|
|
52
|
+
|
|
53
|
+
# Read and convert numeric data only
|
|
54
|
+
data = []
|
|
55
|
+
for line in data_lines:
|
|
56
|
+
try:
|
|
57
|
+
row = [float(val) for val in line.split(',')]
|
|
58
|
+
data.append(row)
|
|
59
|
+
except ValueError:
|
|
60
|
+
continue # skip non-numeric lines
|
|
61
|
+
|
|
62
|
+
df = pd.DataFrame(data, columns=headers)
|
|
63
|
+
return measurement_type, df
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
import matplotlib.pyplot as plt
|
|
67
|
+
|
|
68
|
+
def plot_multiple_xy(datasets, labels=None, xlabel=None, ylabel=None, title=None):
|
|
69
|
+
"""
|
|
70
|
+
Plot multiple datasets (each as a 2-column DataFrame) on one plot.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
datasets: list of DataFrames with 2 columns
|
|
74
|
+
labels: list of labels for each line
|
|
75
|
+
xlabel, ylabel: optional axis labels
|
|
76
|
+
title: optional plot title
|
|
77
|
+
"""
|
|
78
|
+
fig, ax = plt.subplots(figsize=(6, 4), dpi=300)
|
|
79
|
+
|
|
80
|
+
colors = ['blue', 'green', 'red', 'orange', 'purple']
|
|
81
|
+
|
|
82
|
+
for i, df in enumerate(datasets):
|
|
83
|
+
x = df.iloc[:, 0]
|
|
84
|
+
y = df.iloc[:, 1]
|
|
85
|
+
label = labels[i] if labels else None
|
|
86
|
+
color = colors[i % len(colors)]
|
|
87
|
+
ax.plot(x, y, label=label, color=color, linewidth=1)
|
|
88
|
+
|
|
89
|
+
# X and Y = 0 guide lines
|
|
90
|
+
ax.axhline(0, color='gray', linestyle='-', linewidth=0.5)
|
|
91
|
+
ax.axvline(0, color='gray', linestyle='-', linewidth=0.5)
|
|
92
|
+
|
|
93
|
+
# Axis style: boxed
|
|
94
|
+
for side in ['top', 'bottom', 'left', 'right']:
|
|
95
|
+
ax.spines[side].set_linewidth(1)
|
|
96
|
+
ax.spines[side].set_color('black')
|
|
97
|
+
|
|
98
|
+
# Axis labels
|
|
99
|
+
ax.set_xlabel(xlabel if xlabel else datasets[0].columns[0], fontsize=12, fontweight='bold', labelpad=10)
|
|
100
|
+
ax.set_ylabel(ylabel if ylabel else datasets[0].columns[1], fontsize=12, fontweight='bold', labelpad=10)
|
|
101
|
+
|
|
102
|
+
# Axis limits
|
|
103
|
+
# Extract x/y and lengths
|
|
104
|
+
x_list = [df.iloc[:, 0].values for df in datasets]
|
|
105
|
+
y_list = [df.iloc[:, 1].values for df in datasets]
|
|
106
|
+
all_x = np.concatenate(x_list)
|
|
107
|
+
all_y = np.concatenate(y_list)
|
|
108
|
+
# ax.set_xlim(all_x.min()*1.1, all_x.max()*1.1)
|
|
109
|
+
# ax.set_ylim(all_y.min()*1.2, all_y.max()*1.1)
|
|
110
|
+
# Add 10% margin to x and y limits
|
|
111
|
+
x_margin = (all_x.max() - all_x.min()) * 0.05
|
|
112
|
+
y_margin = (all_y.max() - all_y.min()) * 0.05
|
|
113
|
+
|
|
114
|
+
ax.set_xlim(all_x.min() - x_margin, all_x.max() + x_margin)
|
|
115
|
+
ax.set_ylim(all_y.min() - y_margin, all_y.max() + y_margin)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Ticks and layout
|
|
119
|
+
ax.tick_params(direction='in', top=True, right=True)
|
|
120
|
+
ax.grid(False)
|
|
121
|
+
|
|
122
|
+
# Title and legend
|
|
123
|
+
if title:
|
|
124
|
+
ax.set_title(title, fontsize=12,fontweight='bold', pad=10)
|
|
125
|
+
if labels:
|
|
126
|
+
ax.legend()
|
|
127
|
+
|
|
128
|
+
plt.tight_layout()
|
|
129
|
+
plt.show()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
mport matplotlib.pyplot as plt
|
|
133
|
+
|
|
134
|
+
def plot_xy(df, measurement_type=None):
|
|
135
|
+
"""
|
|
136
|
+
Plot single xy curve
|
|
137
|
+
"""
|
|
138
|
+
x_col = df.columns[0]
|
|
139
|
+
y_col = df.columns[1]
|
|
140
|
+
x = df[x_col]
|
|
141
|
+
y = df[y_col]
|
|
142
|
+
|
|
143
|
+
fig, ax = plt.subplots(figsize=(6,4),dpi=300) # square aspect ratio
|
|
144
|
+
|
|
145
|
+
# Plot data
|
|
146
|
+
ax.plot(x, y, linewidth=1, color='blue')
|
|
147
|
+
|
|
148
|
+
# Keep left and bottom axis
|
|
149
|
+
ax.spines['left'].set_visible(True)
|
|
150
|
+
ax.spines['bottom'].set_visible(True)
|
|
151
|
+
|
|
152
|
+
# Show top and right spine to make a box
|
|
153
|
+
ax.spines['top'].set_visible(True)
|
|
154
|
+
ax.spines['right'].set_visible(True)
|
|
155
|
+
|
|
156
|
+
# Draw all 4 spines in black (like a square)
|
|
157
|
+
for side in ['top', 'bottom', 'left', 'right']:
|
|
158
|
+
ax.spines[side].set_linewidth(1)
|
|
159
|
+
ax.spines[side].set_color('black')
|
|
160
|
+
|
|
161
|
+
# Add x=0 and y=0 guide lines
|
|
162
|
+
ax.axhline(0, color='orange', linestyle='-', linewidth=0.5)
|
|
163
|
+
ax.axvline(0, color='orange', linestyle='-', linewidth=0.5)
|
|
164
|
+
|
|
165
|
+
# Axis label styling
|
|
166
|
+
ax.set_xlabel('Voltage (V)', fontsize=12, fontweight='bold', labelpad=10)
|
|
167
|
+
ax.set_ylabel('Current (uA)' , fontsize=12, fontweight='bold', labelpad=10)
|
|
168
|
+
|
|
169
|
+
# Title
|
|
170
|
+
if measurement_type:
|
|
171
|
+
ax.set_title(measurement_type, fontsize=12, fontweight='bold', pad=10)
|
|
172
|
+
|
|
173
|
+
# Tick styling
|
|
174
|
+
ax.tick_params(direction='in', top=True, right=True)
|
|
175
|
+
|
|
176
|
+
# Turn off grid
|
|
177
|
+
ax.grid(False)
|
|
178
|
+
|
|
179
|
+
plt.tight_layout()
|
|
180
|
+
plt.show()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# Convert the plot to movie
|
|
184
|
+
|
|
185
|
+
import matplotlib.pyplot as plt
|
|
186
|
+
import matplotlib.animation as animation
|
|
187
|
+
import numpy as np
|
|
188
|
+
import cv2
|
|
189
|
+
import tifffile
|
|
190
|
+
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
|
191
|
+
import tqdm
|
|
192
|
+
|
|
193
|
+
def animate_CVplots(datasets, labels=["Scan 1", "Scan 2", "Scan 3"], xlabel="Voltage (V)", ylabel="Current (µA)", title="CV Scan",
|
|
194
|
+
fps=60, save_basename="cv_animation"):
|
|
195
|
+
"""
|
|
196
|
+
Function to animate the plot
|
|
197
|
+
datasets, # List of DataFrames, each with 2 columns (x, y) for a scan
|
|
198
|
+
labels=None, # Optional list of labels for each scan (used in legend)
|
|
199
|
+
xlabel=None, # Optional label for x-axis (default: first column name)
|
|
200
|
+
ylabel=None, # Optional label for y-axis (default: second column name)
|
|
201
|
+
title=None, # Optional plot title
|
|
202
|
+
fps=50, # fps of the input data. Original speed: 4 fps
|
|
203
|
+
save_basename="cv_animation" # Prefix for saved output files (AVI + TIFF stack)
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
# Setup plot and canvas
|
|
207
|
+
fig, ax = plt.subplots(figsize=(6, 4), dpi=150)
|
|
208
|
+
fig.subplots_adjust(left=0.15, right=0.95, top=0.9, bottom=0.15)
|
|
209
|
+
canvas = FigureCanvasAgg(fig)
|
|
210
|
+
|
|
211
|
+
# Extract x/y and lengths
|
|
212
|
+
x_list = [df.iloc[:, 0].values for df in datasets]
|
|
213
|
+
y_list = [df.iloc[:, 1].values for df in datasets]
|
|
214
|
+
lengths = [len(x) for x in x_list]
|
|
215
|
+
cum_lengths = np.cumsum(lengths)
|
|
216
|
+
total_frames = cum_lengths[-1]
|
|
217
|
+
|
|
218
|
+
# Axis formatting
|
|
219
|
+
for side in ['top', 'bottom', 'left', 'right']:
|
|
220
|
+
ax.spines[side].set_linewidth(1)
|
|
221
|
+
ax.spines[side].set_color('black')
|
|
222
|
+
|
|
223
|
+
ax.axhline(0, color='gray', linewidth=0.5)
|
|
224
|
+
ax.axvline(0, color='gray', linewidth=0.5)
|
|
225
|
+
ax.tick_params(direction='in', top=True, right=True)
|
|
226
|
+
ax.set_xlabel(xlabel or datasets[0].columns[0], fontsize=12, fontweight='bold', labelpad=10)
|
|
227
|
+
ax.set_ylabel(ylabel or datasets[0].columns[1], fontsize=12, fontweight='bold', labelpad=10)
|
|
228
|
+
if title:
|
|
229
|
+
ax.set_title(title, fontsize=10)
|
|
230
|
+
|
|
231
|
+
# Prepare plot elements
|
|
232
|
+
colors = ['blue', 'green', 'red', 'purple']
|
|
233
|
+
lines, tips = [], []
|
|
234
|
+
for i in range(len(datasets)):
|
|
235
|
+
line, = ax.plot([], [], color=colors[i % len(colors)], label=(labels[i] if labels else None), linewidth=1)
|
|
236
|
+
tip, = ax.plot([], [], color=colors[i % len(colors)], marker='o', markersize=4)
|
|
237
|
+
lines.append(line)
|
|
238
|
+
tips.append(tip)
|
|
239
|
+
|
|
240
|
+
# Add live current value in top-left
|
|
241
|
+
value_text = ax.text(0.02, 0.95, '', transform=ax.transAxes,
|
|
242
|
+
fontsize=10, color='black', ha='left', va='top')
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
if labels:
|
|
246
|
+
ax.legend()
|
|
247
|
+
|
|
248
|
+
# Axis limits
|
|
249
|
+
all_x = np.concatenate(x_list)
|
|
250
|
+
all_y = np.concatenate(y_list)
|
|
251
|
+
|
|
252
|
+
# Add 10% margin to x and y limits
|
|
253
|
+
x_margin = (all_x.max() - all_x.min()) * 0.05
|
|
254
|
+
y_margin = (all_y.max() - all_y.min()) * 0.05
|
|
255
|
+
|
|
256
|
+
ax.set_xlim(all_x.min() - x_margin, all_x.max() + x_margin)
|
|
257
|
+
ax.set_ylim(all_y.min() - y_margin, all_y.max() + y_margin)
|
|
258
|
+
# ax.set_xlim(all_x.min()*1.1, all_x.max()*1.1)
|
|
259
|
+
# ax.set_ylim(all_y.min()*1.2, all_y.max()*1.1)
|
|
260
|
+
|
|
261
|
+
# Video writer setup
|
|
262
|
+
canvas.draw()
|
|
263
|
+
width, height = canvas.get_width_height()
|
|
264
|
+
# fps=int(1000/interval)
|
|
265
|
+
aviout=save_basename+"_"+str(fps)+".avi"
|
|
266
|
+
out = cv2.VideoWriter(aviout, cv2.VideoWriter_fourcc(*'MJPG'), fps, (width, height))
|
|
267
|
+
# tiff_frames = []
|
|
268
|
+
|
|
269
|
+
# Setup TIFF writer with BigTIFF support
|
|
270
|
+
tif_writer = tifffile.TiffWriter(f"{save_basename}.tif", bigtiff=True)
|
|
271
|
+
|
|
272
|
+
# Render frames
|
|
273
|
+
print("Plotting the curves into movies~")
|
|
274
|
+
for frame in tqdm.tqdm(range(total_frames)):
|
|
275
|
+
ax.set_title(title, fontsize=12,fontweight='bold') # reset title in case of matplotlib bugs
|
|
276
|
+
|
|
277
|
+
for i, (x, y, line, tip) in enumerate(zip(x_list, y_list, lines, tips)):
|
|
278
|
+
start = cum_lengths[i - 1] if i > 0 else 0
|
|
279
|
+
end = cum_lengths[i]
|
|
280
|
+
if frame < start:
|
|
281
|
+
# Not started yet
|
|
282
|
+
line.set_data([], [])
|
|
283
|
+
tip.set_data([], [])
|
|
284
|
+
elif frame >= end:
|
|
285
|
+
# Completed
|
|
286
|
+
line.set_data(x, y)
|
|
287
|
+
tip.set_data([], [])
|
|
288
|
+
else:
|
|
289
|
+
# Active scan
|
|
290
|
+
idx = frame - start + 1
|
|
291
|
+
line.set_data(x[:idx], y[:idx])
|
|
292
|
+
tip.set_data([x[idx - 1]], [y[idx - 1]])
|
|
293
|
+
value_text.set_text(
|
|
294
|
+
f"{labels[i] if labels else f'Scan {i+1}'}\nV = {x[idx - 1]:.3f} V\nI = {y[idx - 1]:.3f} µA"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Render and convert to image
|
|
298
|
+
canvas.draw()
|
|
299
|
+
buf = np.frombuffer(canvas.tostring_rgb(), dtype=np.uint8)
|
|
300
|
+
image = buf.reshape((height, width, 3))
|
|
301
|
+
image_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
|
302
|
+
out.write(image_bgr)
|
|
303
|
+
# Save TIFF frame (with compression)
|
|
304
|
+
tif_writer.write(image, photometric='rgb', compression='deflate')
|
|
305
|
+
|
|
306
|
+
out.release()
|
|
307
|
+
tif_writer.close()
|
|
308
|
+
|
|
309
|
+
# tifffile.imwrite(f"{save_basename}.tif", tiff_frames, photometric='rgb',compression='deflate')
|
|
310
|
+
print("Conversion done!")
|
|
311
|
+
print(f"Saved AVI: {save_basename}.avi")
|
|
312
|
+
print(f"Saved TIFF stack: {save_basename}.tif")
|
|
313
|
+
|
|
314
|
+
from tkinter import Tk, filedialog
|
|
315
|
+
def select_multiple_files(filetypes=(("csv files", "*.csv"), ("All files", "*.*"))):
|
|
316
|
+
Tk().withdraw() # Hide the root window
|
|
317
|
+
filepaths = filedialog.askopenfilenames(title="Select PSTrace .csv files", filetypes=filetypes)
|
|
318
|
+
return list(filepaths) # Convert from tuple to list
|
insituTEM/insitu_DP.py
CHANGED
|
@@ -10,7 +10,7 @@ Example:
|
|
|
10
10
|
IO.f2tif(path,1)
|
|
11
11
|
|
|
12
12
|
Created on Tue Jun 11 10:25:25 2019
|
|
13
|
-
@author:
|
|
13
|
+
@author: Meng Li
|
|
14
14
|
"""
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
@@ -57,12 +57,12 @@ def readcsv(path,col_list):
|
|
|
57
57
|
return D
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def writeCSV(pathout,data):
|
|
61
|
-
# import csv
|
|
62
|
-
|
|
63
|
-
# (length,width)=np.shape(data)
|
|
64
|
-
# np.savetxt(pathout, data, fmt='%1.4d', delimiter=",")
|
|
65
|
-
|
|
60
|
+
# def writeCSV(pathout,data):
|
|
61
|
+
# # import csv
|
|
62
|
+
# import numpy as np
|
|
63
|
+
# # (length,width)=np.shape(data)
|
|
64
|
+
# # np.savetxt(pathout, data, fmt='%1.4d', delimiter=",")
|
|
65
|
+
# np.savetxt(pathout, data, fmt='%.4f', delimiter=",")
|
|
66
66
|
|
|
67
67
|
def plot2ani(x,y,outpath,label,w=800,h=600,fps=10,dpi=72,tl_size=20,c='r'):
|
|
68
68
|
"""
|
|
@@ -158,4 +158,224 @@ def AutoROIbg(img):
|
|
|
158
158
|
else:
|
|
159
159
|
ROI = 0
|
|
160
160
|
return ROI
|
|
161
|
-
|
|
161
|
+
|
|
162
|
+
def readscale(path):
|
|
163
|
+
"""
|
|
164
|
+
Function to measure scalebar from movie first frame
|
|
165
|
+
Input: path - movie path
|
|
166
|
+
scalebarcolr- 0 for black, 255 for white
|
|
167
|
+
"""
|
|
168
|
+
import os
|
|
169
|
+
os.environ["IMAGEIO_FFMPEG_EXE"] = "/opt/homebrew/bin/ffmpeg"
|
|
170
|
+
import moviepy.editor as mp
|
|
171
|
+
import cv2
|
|
172
|
+
|
|
173
|
+
video = mp.VideoFileClip(path)
|
|
174
|
+
fr = video.get_frame(mp.cvsecs(0))
|
|
175
|
+
fr = cv2.cvtColor(fr, cv2.COLOR_BGR2GRAY)
|
|
176
|
+
|
|
177
|
+
# Select ROI using cv2.selectROI()
|
|
178
|
+
roi = cv2.selectROI('Select scalebar ROI, quit with SPACE', fr)
|
|
179
|
+
cv2.waitKey(10)
|
|
180
|
+
# cv2.destroyAllWindows()
|
|
181
|
+
cv2.destroyWindow('Select scalebar ROI, quit with SPACE')
|
|
182
|
+
|
|
183
|
+
# print(roi)
|
|
184
|
+
# Crop the image based on the selected ROI
|
|
185
|
+
cropped_image = fr[int(roi[1]):int(roi[1] + roi[3]), int(roi[0]):int(roi[0] + roi[2])]
|
|
186
|
+
|
|
187
|
+
# # # Display the cropped image
|
|
188
|
+
# from PIL import Image
|
|
189
|
+
# from IPython.display import display
|
|
190
|
+
# pil_image= Image.fromarray(cropped_image)
|
|
191
|
+
# display(pil_image)
|
|
192
|
+
|
|
193
|
+
# plt.imshow(cropped_image)
|
|
194
|
+
|
|
195
|
+
IO.showFr(cropped_image)
|
|
196
|
+
#calculate the number of pixels in scalebar
|
|
197
|
+
# print(cropped_image[-5,:])
|
|
198
|
+
import numpy as np
|
|
199
|
+
|
|
200
|
+
scalebar_color = int(input("Input the scalebar color, 0-black, 255-white: "))
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
scalebar_px = np.count_nonzero(cropped_image[-10,:]==scalebar_color)
|
|
204
|
+
|
|
205
|
+
print("scalebar_px=", scalebar_px)
|
|
206
|
+
|
|
207
|
+
scale_nm = int(input("Input the scalebar in nm: "))
|
|
208
|
+
scale =scalebar_px/scale_nm
|
|
209
|
+
print("scale=",scale,"px/nm")
|
|
210
|
+
return scale
|
|
211
|
+
|
|
212
|
+
def next_power_of_2(x):
|
|
213
|
+
return 1 if x == 0 else 2**(x - 1).bit_length()
|
|
214
|
+
|
|
215
|
+
def pad_to_nearest_power_of_2(image):
|
|
216
|
+
import cv2
|
|
217
|
+
# Get original dimensions
|
|
218
|
+
h, w = image.shape
|
|
219
|
+
size=max(h,w)
|
|
220
|
+
|
|
221
|
+
# Calculate the nearest power of 2 dimensions
|
|
222
|
+
new_size = next_power_of_2(size)
|
|
223
|
+
|
|
224
|
+
# Calculate padding amounts
|
|
225
|
+
top_pad = (new_size - h) // 2
|
|
226
|
+
bottom_pad = new_size - h - top_pad
|
|
227
|
+
left_pad = (new_size - w) // 2
|
|
228
|
+
right_pad = new_size - w - left_pad
|
|
229
|
+
|
|
230
|
+
# Apply padding
|
|
231
|
+
padded_image = cv2.copyMakeBorder(
|
|
232
|
+
image,
|
|
233
|
+
top=top_pad, bottom=bottom_pad,
|
|
234
|
+
left=left_pad, right=right_pad,
|
|
235
|
+
borderType=cv2.BORDER_CONSTANT,
|
|
236
|
+
value=0 # Black color
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return padded_image
|
|
240
|
+
|
|
241
|
+
def getFFT(im):
|
|
242
|
+
"""
|
|
243
|
+
Function to get FFT from grayscale image, auto pad the image to keep the image size 2^n
|
|
244
|
+
Input: grayscale image (np array)
|
|
245
|
+
Output: grayscale FFT with 2^n size, 32 bit
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
pad_im=pad_to_nearest_power_of_2(im)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
fft_image = np.fft.fft2(pad_im)
|
|
252
|
+
fft_shifted = np.fft.fftshift(fft_image)
|
|
253
|
+
fft = 20 * np.log(np.abs(fft_shifted))
|
|
254
|
+
return fft
|
|
255
|
+
|
|
256
|
+
def FFTstack(path):
|
|
257
|
+
"""
|
|
258
|
+
Function to get FFT of the stack
|
|
259
|
+
input: path of the image stack
|
|
260
|
+
output: FFT stack with 2^n size 8 bit images
|
|
261
|
+
"""
|
|
262
|
+
import tifffile
|
|
263
|
+
import tqdm
|
|
264
|
+
import cv2
|
|
265
|
+
|
|
266
|
+
stack = tifffile.imread(path)
|
|
267
|
+
nFrames,h,w = stack.shape #make sure the order of h and w!
|
|
268
|
+
pathout=path[:-4]+'_FFT.tif'
|
|
269
|
+
|
|
270
|
+
print("------------------------------------------")
|
|
271
|
+
print('Calculating FFT of each frame')
|
|
272
|
+
with tifffile.TiffWriter(pathout,bigtiff=True) as tif:
|
|
273
|
+
for i in tqdm.tqdm(range(nFrames)):
|
|
274
|
+
fr=stack[i]
|
|
275
|
+
fft=getFFT(fr)
|
|
276
|
+
#convert the 32 bit image to 8 bit
|
|
277
|
+
normalized_image = cv2.normalize(fft, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX)
|
|
278
|
+
# Convert the normalized image to 8-bit
|
|
279
|
+
fft_8bit = np.uint8(normalized_image)
|
|
280
|
+
tif.write(fft_8bit, contiguous=True)
|
|
281
|
+
|
|
282
|
+
def FFT2Polar(fft):
|
|
283
|
+
"""
|
|
284
|
+
Function to convert FFT to polar image, y= 180˚, x= Rho
|
|
285
|
+
Input: grayscale fft image (np array)
|
|
286
|
+
"""
|
|
287
|
+
import cv2
|
|
288
|
+
#function to convert fft image to polar coordinates from the center from 0-180 deg
|
|
289
|
+
center = (fft.shape[1] // 2, fft.shape[0] // 2)
|
|
290
|
+
polar_fft = cv2.linearPolar(fft, center, center[0], cv2.WARP_FILL_OUTLIERS)
|
|
291
|
+
# polar_fft= polar_fft[:,:center[1]]
|
|
292
|
+
#resize the image to realistic size
|
|
293
|
+
resized_polar_fft = cv2.resize(polar_fft, (center[0], 360))
|
|
294
|
+
# plt.imshow(resized_polar_fft)
|
|
295
|
+
|
|
296
|
+
p_fft1=resized_polar_fft[:180,:]
|
|
297
|
+
p_fft2=resized_polar_fft[180:,:]
|
|
298
|
+
p_fft=p_fft1+p_fft2
|
|
299
|
+
return p_fft
|
|
300
|
+
|
|
301
|
+
def FFTidentify(fft,pathout,scale=1,sigmaMax=2.5,sigmaMin=2.4,thrs=0.96,sigma_ratio=10):
|
|
302
|
+
"""
|
|
303
|
+
Function to identify DPs from FFT
|
|
304
|
+
input:
|
|
305
|
+
fft: image
|
|
306
|
+
scale: scale of the original image : 1 px = XXX nm
|
|
307
|
+
parameters for blobs detection: sigmaMax, sigmaMin, thrs, sigma_ratio
|
|
308
|
+
pathout: path for output image (.tif) and csv
|
|
309
|
+
|
|
310
|
+
output:
|
|
311
|
+
idnetified image
|
|
312
|
+
indexed csv
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
"""
|
|
317
|
+
import math
|
|
318
|
+
import matplotlib.pyplot as plt
|
|
319
|
+
from scipy import ndimage
|
|
320
|
+
from skimage.feature import blob_dog, blob_log, blob_doh
|
|
321
|
+
from math import sqrt
|
|
322
|
+
from insituTEM import insitu_IO as IO
|
|
323
|
+
|
|
324
|
+
h,w = fft.shape
|
|
325
|
+
x_c=int(w/2)
|
|
326
|
+
y_c=int(h/2)
|
|
327
|
+
r=int(x_c/2)
|
|
328
|
+
if w>320:
|
|
329
|
+
fft_crop=fft[x_c-r:x_c+r,y_c-r:y_c+r]
|
|
330
|
+
image= ndimage.maximum_filter(fft_crop, size=2, mode='wrap')
|
|
331
|
+
else:
|
|
332
|
+
fft_crop = fft
|
|
333
|
+
image= ndimage.maximum_filter(fft_crop, size=3, mode='wrap')
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
blobs_dog = blob_dog(image, max_sigma=sigmaMax,min_sigma=sigmaMin, threshold=thrs,sigma_ratio=sigma_ratio,exclude_border=True)
|
|
339
|
+
blobs_dog[:, 2] = blobs_dog[:, 2] * sqrt(2)
|
|
340
|
+
|
|
341
|
+
title = 'DP detection'
|
|
342
|
+
|
|
343
|
+
fig, axes = plt.subplots(1, 2, figsize=(10, 5), sharex=True, sharey=True)
|
|
344
|
+
ax = axes.ravel()
|
|
345
|
+
|
|
346
|
+
ax[0].set_title('FFT_')
|
|
347
|
+
ax[0].imshow(fft_crop)
|
|
348
|
+
# ax[0].set_axis_off()
|
|
349
|
+
ax[1].set_title(title)
|
|
350
|
+
ax[1].imshow(image)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
plt.tight_layout()
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
data=np.zeros((len(blobs_dog),6))
|
|
357
|
+
data[:,0]=blobs_dog[:,1]
|
|
358
|
+
data[:,1]=blobs_dog[:,0]
|
|
359
|
+
|
|
360
|
+
for j in range(len(blobs_dog)):
|
|
361
|
+
y=data[j,1]-r
|
|
362
|
+
x=data[j,0]-r
|
|
363
|
+
|
|
364
|
+
data[j,2]=math.sqrt(x**2+y**2)
|
|
365
|
+
# if data[j,2] < 10:
|
|
366
|
+
# data[j,2]=0
|
|
367
|
+
# theta = 0
|
|
368
|
+
# else:
|
|
369
|
+
# theta=math.atan(y/x)
|
|
370
|
+
theta=math.atan(y/x)
|
|
371
|
+
data[j,3]=math.degrees(theta)
|
|
372
|
+
|
|
373
|
+
data[:,4]=data[:,2]*scale
|
|
374
|
+
data[:,5]=1/data[:,4]
|
|
375
|
+
|
|
376
|
+
plt.scatter(data[:,0],data[:,1],marker='+',color='red')
|
|
377
|
+
for i, (x_i, y_i) in enumerate(zip(data[:,0],data[:,1])):
|
|
378
|
+
plt.annotate(str(i+1), (x_i, y_i), textcoords="offset points", xytext=(0,10), ha='center',color='white')
|
|
379
|
+
pathoutcsv=pathout[:-4]+'.csv'
|
|
380
|
+
IO.writeCSV(pathoutcsv,data,fmt='%1.3f')
|
|
381
|
+
plt.savefig(pathout)
|