autofocus.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. """
  2. Special autofocus function based on radia power density function with
  3. radial exponential distribution fit
  4. Luyang Han
  5. 2019.08.01 Inital version
  6. 2020.05.18 Add auto stig function
  7. """
  8. import numpy as np
  9. import tempfile, time
  10. from imageio import imread, imsave
  11. from numpy import fft
  12. from scipy.optimize import curve_fit
  13. from skimage import filters
  14. def dif_of_gaussian(img, sigma = 10):
  15. # return difference of gaussian image
  16. # image will be automatically normalized.
  17. img = img / img.max()
  18. img_blur = filters.gaussian(img, sigma)
  19. return img - img_blur
  20. def _func(x,I0,T,N):
  21. return I0*T**2*x/(1+x**2*T**2)**1.5+N*x
  22. def sharpness_nv_g(img):
  23. # a sharpness definition of normalized variance
  24. img = dif_of_gaussian(img)
  25. mu = np.average(img)
  26. w,h = img.shape
  27. #sharp = np.sum((img-mu)**2)/w/h/mu
  28. sharp = np.sum((img-mu)**2)/w/h
  29. return sharp
  30. def sharpness_nv(img):
  31. # a sharpness definition of normalized variance
  32. #img = dif_of_gaussian(img)
  33. mu = np.average(img)
  34. w,h = img.shape
  35. sharp = np.sum((img-mu)**2)/w/h/mu
  36. #sharp = np.sum((img-mu)**2)/w/h
  37. return sharp
  38. def sharpness_psd(img):
  39. F2 = fft.fftshift(fft.fft2(img))
  40. psd2D = np.abs( F2 )**2
  41. y, x = np.indices(psd2D.shape)
  42. y0, x0 = np.array(psd2D.shape)/2
  43. r = ((x-x0)**2+(y-y0)**2)**0.5
  44. ind = np.argsort(r.flat)
  45. r_sorted = r.flat[ind]
  46. i_sorted = psd2D.flat[ind]
  47. r_int = r_sorted.astype(int)
  48. deltar = r_int[1:] - r_int[:-1] # Assumes all radii represented
  49. rind = np.where(deltar)[0] # location of changed radius
  50. nr = rind[1:] - rind[:-1]
  51. csim = np.cumsum(i_sorted, dtype=float)
  52. tbin = csim[rind[1:]] - csim[rind[:-1]]
  53. radial_prof = tbin / nr
  54. radial_prof_r = radial_prof * np.arange(len(radial_prof))
  55. radial_prof_r = radial_prof_r / np.max(radial_prof_r)
  56. #select_r = int(len(radial_prof)*0.8)
  57. select_r = int(len(radial_prof_r)*0.9)
  58. try:
  59. res = curve_fit(_func, np.arange(len(radial_prof_r))[0:select_r], radial_prof_r[0:select_r],
  60. (radial_prof_r[0:select_r].max(), 1,0),
  61. bounds=([0, 1/(len(radial_prof_r)), 0], [radial_prof_r[0:select_r].max()*5/(10/(len(radial_prof_r)/2)), 10, 2*radial_prof_r.max()/len(radial_prof_r)]))
  62. area_s = res[0][0]
  63. area_n = res[0][2]*len(radial_prof_r)**2/2
  64. snr = area_s / area_n
  65. #return np.abs(1/res[0][1])
  66. if snr > 0.005:
  67. return np.abs(1/res[0][1])
  68. else:
  69. return 0
  70. except:
  71. return 0
  72. def sharpness_psd2(img):
  73. # a modified sharpness measure. The sharpness is weighed against snr.
  74. F2 = fft.fftshift(fft.fft2(img))
  75. psd2D = np.abs( F2 )**2
  76. y, x = np.indices(psd2D.shape)
  77. y0, x0 = np.array(psd2D.shape)/2
  78. r = ((x-x0)**2+(y-y0)**2)**0.5
  79. ind = np.argsort(r.flat)
  80. r_sorted = r.flat[ind]
  81. i_sorted = psd2D.flat[ind]
  82. r_int = r_sorted.astype(int)
  83. deltar = r_int[1:] - r_int[:-1] # Assumes all radii represented
  84. rind = np.where(deltar)[0] # location of changed radius
  85. nr = rind[1:] - rind[:-1]
  86. csim = np.cumsum(i_sorted, dtype=float)
  87. tbin = csim[rind[1:]] - csim[rind[:-1]]
  88. radial_prof = tbin / nr
  89. radial_prof_r = radial_prof * np.arange(len(radial_prof))
  90. radial_prof_r = radial_prof_r / np.max(radial_prof_r)
  91. #select_r = int(len(radial_prof)*0.8)
  92. select_r = int(len(radial_prof_r)*0.9)
  93. try:
  94. res = curve_fit(_func, np.arange(len(radial_prof_r))[0:select_r], radial_prof_r[0:select_r],
  95. (radial_prof_r[0:select_r].max(), 1,0),
  96. bounds=([0, 1/(len(radial_prof_r)), 0], [radial_prof_r[0:select_r].max()*5/(10/(len(radial_prof_r)/2)), 10, 2*radial_prof_r.max()/len(radial_prof_r)]))
  97. area_s = res[0][0]
  98. area_n = res[0][2]*len(radial_prof_r)**2/2
  99. snr = area_s / area_n
  100. #return np.abs(1/res[0][1])
  101. if snr > 0.005:
  102. return np.abs(1/res[0][1]) * snr
  103. else:
  104. return 0
  105. except:
  106. return 0
  107. # if np.argmax(radial_prof_r) > len(radial_prof_r)*0.7:
  108. # return 0
  109. # else:
  110. # res = curve_fit(_func, np.arange(select_r), radial_prof_r[0:select_r], (radial_prof_r.max(), 1 , 0.1))
  111. # return np.abs(1/res[0][1])
  112. def sharpness_psd3(img):
  113. # yet another psd method, use overfit to quantify sharpness.
  114. sigma_width = np.pi/2
  115. F2 = fft.fftshift(fft.fft2(img))
  116. psd2D = np.abs( F2 )**2
  117. y, x = np.indices(psd2D.shape)
  118. y0, x0 = np.array(psd2D.shape)/2
  119. r = ((x-x0)**2+(y-y0)**2)**0.5
  120. ind = np.argsort(r.flat)
  121. r_sorted = r.flat[ind]
  122. i_sorted = psd2D.flat[ind]
  123. r_int = r_sorted.astype(int)
  124. deltar = r_int[1:] - r_int[:-1] # Assumes all radii represented
  125. rind = np.where(deltar)[0] # location of changed radius
  126. nr = rind[1:] - rind[:-1]
  127. csim = np.cumsum(i_sorted, dtype=float)
  128. tbin = csim[rind[1:]] - csim[rind[:-1]]
  129. radial_prof = tbin / nr
  130. radial_prof_r = radial_prof * np.arange(len(radial_prof))
  131. radial_prof_r = radial_prof_r / np.max(radial_prof_r)
  132. #select_r = int(len(radial_prof)*0.8)
  133. select_r = int(len(radial_prof_r)*0.8)
  134. try:
  135. res = curve_fit(_func, np.arange(len(radial_prof_r))[0:select_r], radial_prof_r[0:select_r],
  136. (radial_prof_r[0:select_r].max(), 1,0),
  137. bounds=([0, 1/(len(radial_prof_r)), 0], [radial_prof_r[0:select_r].max()*5/(10/(len(radial_prof_r)/2)), 10, 2*radial_prof_r.max()/len(radial_prof_r)]))
  138. area_s = res[0][0]
  139. area_n = res[0][2]*len(radial_prof_r)**2/2
  140. snr = area_s / area_n
  141. fit_y = _func(np.arange(len(radial_prof_r)), *res[0])
  142. half_len = len(radial_prof_r)//2
  143. extra_p = np.sum((radial_prof_r-fit_y)[0:half_len])
  144. p_factor = np.tanh(extra_p*sigma_width)+1
  145. sharp = np.abs(1/res[0][1])
  146. new_sharp = sharp*p_factor
  147. return new_sharp
  148. except:
  149. return 0
  150. def gaussian(x, a, b, c, d):
  151. return a*np.exp(-(x-b)**2/(2*c**2)) + d
  152. def gaussian2(x, a, b, c):
  153. return a*np.exp(-(x-b)**2/(2*c**2))
  154. def autofocus(sem, search_step=10, wd_searh_factor=3, retry = 3, refine = True):
  155. delay = sem.GetValue("AP_FRAME_TIME")/1000*1.5
  156. buffer = tempfile.TemporaryFile(suffix='.bmp').name
  157. X = sem.GetValue("AP_RED_RASTER_POSN_X")
  158. Y = sem.GetValue("AP_RED_RASTER_POSN_Y")
  159. H = sem.GetValue("AP_RED_RASTER_H")
  160. W = sem.GetValue("AP_RED_RASTER_W")
  161. # local function to optimize
  162. def target(wd):
  163. sem.SetValue("AP_WD", wd)
  164. time.sleep(delay)
  165. sem.Grab(buffer,X,Y,W,H)
  166. img = imread(buffer)
  167. val = sharpness_nv_g(img)
  168. print("WD={:.3f},sharpness={}".format(wd*1000, val))
  169. return val
  170. def search(span, step):
  171. start_wd = sem.GetValue("AP_WD")
  172. wd_list = np.linspace(start_wd-span, start_wd+span, num = step)
  173. sem.SetValue("AP_WD", wd_list[0])
  174. time.sleep(0.3)
  175. sharpness_list = np.array([target(wd) for wd in wd_list])
  176. start_a = sharpness_list.max()
  177. start_b = wd_list[np.argmax(sharpness_list)]
  178. start_c = (wd_list.max() - wd_list.min())/2
  179. try:
  180. #res = curve_fit(gaussian, wd_list, sharpness_list, (start_a, start_b, start_c, start_d))
  181. res = curve_fit(gaussian2, wd_list, sharpness_list, (start_a, start_b, start_c))
  182. b_std = res[1][1,1]**0.5
  183. if res[0][1] + 0.1*span > wd_list[-1]:
  184. sem.SetValue("AP_WD", wd_list[-1])
  185. return "outside"
  186. elif res[0][1] - 0.1*span < wd_list[0]:
  187. sem.SetValue("AP_WD", wd_list[0])
  188. return "outside"
  189. else:
  190. sem.SetValue("AP_WD", wd_list[0])
  191. time.sleep(0.3)
  192. sem.SetValue("AP_WD", res[0][1])
  193. return "success"
  194. except:
  195. sem.SetValue("AP_WD", wd_list[0])
  196. time.sleep(0.3)
  197. sem.SetValue("AP_WD", wd_list[np.argmax(sharpness_list)])
  198. return "failed"
  199. factor = wd_searh_factor
  200. state = ""
  201. # first find a rough focus
  202. count = 0
  203. while state != "success" and count < retry:
  204. state = search(sem.GetValue("AP_WIDTH")*factor, search_step)
  205. print(state)
  206. if state == "failed":
  207. factor = factor * 1.5
  208. count += 1
  209. if state == "outside":
  210. factor = factor * 1.4
  211. count += 1
  212. if state == "success" and refine:
  213. search(sem.GetValue("AP_WIDTH"), search_step)
  214. return state
  215. def autofocusstig(sem, search_step=10, iter_n=2, wd_searh_factor=3, stig_range=2, iter_range_damping = 0.8):
  216. delay = sem.GetValue("AP_FRAME_TIME")/1000*1.7
  217. buffer = tempfile.TemporaryFile(suffix='.bmp').name
  218. X = sem.GetValue("AP_RED_RASTER_POSN_X")
  219. Y = sem.GetValue("AP_RED_RASTER_POSN_Y")
  220. H = sem.GetValue("AP_RED_RASTER_H")
  221. W = sem.GetValue("AP_RED_RASTER_W")
  222. # maximum iteration 5 times.
  223. iter_n = np.clip(iter_n, 1, 5)
  224. # local function to optimize
  225. def target(wd):
  226. sem.SetValue("AP_WD", wd)
  227. time.sleep(delay)
  228. sem.Grab(buffer,X,Y,W,H)
  229. img = imread(buffer)
  230. val = sharpness_nv_g(img)
  231. print("WD={:.3f},sharpness={}".format(wd*1000, val))
  232. return val
  233. def target_stig(target_val, stig_type = "X"):
  234. if stig_type == "X":
  235. AP_NAME = "AP_STIG_X"
  236. elif stig_type == "Y":
  237. AP_NAME = "AP_STIG_Y"
  238. sem.SetValue(AP_NAME, target_val)
  239. time.sleep(delay)
  240. sem.Grab(buffer,X,Y,W,H)
  241. img = imread(buffer)
  242. val = sharpness_nv_g(img)
  243. print(f"Stig {stig_type}={target_val:.3f},sharpness={val}")
  244. return val
  245. def search_wd(span, step):
  246. start_wd = sem.GetValue("AP_WD")
  247. wd_list = np.linspace(start_wd-span, start_wd+span, num = step)
  248. sem.SetValue("AP_WD", wd_list[0])
  249. time.sleep(0.3)
  250. sharpness_list = np.array([target(wd) for wd in wd_list])
  251. start_a = sharpness_list.max()
  252. start_b = wd_list[np.argmax(sharpness_list)]
  253. start_c = (wd_list.max() - wd_list.min())/2
  254. try:
  255. #res = curve_fit(gaussian, wd_list, sharpness_list, (start_a, start_b, start_c, start_d))
  256. res = curve_fit(gaussian2, wd_list, sharpness_list, (start_a, start_b, start_c))
  257. #b_std = res[1][1,1]**0.5
  258. if res[0][1] + 0.1*span > wd_list[-1]:
  259. sem.SetValue("AP_WD", wd_list[-1])
  260. return "outside"
  261. elif res[0][1] - 0.1*span < wd_list[0]:
  262. sem.SetValue("AP_WD", wd_list[0])
  263. return "outside"
  264. else:
  265. sem.SetValue("AP_WD", wd_list[0])
  266. time.sleep(0.3)
  267. sem.SetValue("AP_WD", res[0][1])
  268. return "success"
  269. except:
  270. sem.SetValue("AP_WD", wd_list[0])
  271. time.sleep(0.3)
  272. sem.SetValue("AP_WD", wd_list[np.argmax(sharpness_list)])
  273. return "failed"
  274. def search_stig(span, step, stig_type = "X"):
  275. if stig_type == "X":
  276. AP_NAME = "AP_STIG_X"
  277. elif stig_type == "Y":
  278. AP_NAME = "AP_STIG_Y"
  279. start_stig = sem.GetValue(AP_NAME)
  280. stig_list = np.linspace(start_stig-span, start_stig+span, num = step)
  281. sem.SetValue(AP_NAME, stig_list[0])
  282. time.sleep(0.3)
  283. sharpness_list = np.array([target_stig(val, stig_type) for val in stig_list])
  284. start_a = sharpness_list.max()
  285. start_b = stig_list[np.argmax(sharpness_list)]
  286. start_c = (stig_list.max() - stig_list.min())/2
  287. try:
  288. res = curve_fit(gaussian2, stig_list, sharpness_list, (start_a, start_b, start_c))
  289. b_std = res[1][1,1]**0.5
  290. if res[0][1]+b_std > stig_list[-1]:
  291. sem.SetValue(AP_NAME, stig_list[-1])
  292. return "outside"
  293. elif res[0][1]-b_std < stig_list[0]:
  294. sem.SetValue(AP_NAME, stig_list[0])
  295. return "outside"
  296. else:
  297. sem.SetValue(AP_NAME, stig_list[0])
  298. time.sleep(0.3)
  299. sem.SetValue(AP_NAME, res[0][1])
  300. return "success"
  301. except:
  302. sem.SetValue(AP_NAME, stig_list[np.argmax(sharpness_list)])
  303. print("cannot fit.")
  304. return "failed"
  305. # now start the main function
  306. default_wd_range = wd_searh_factor * sem.GetValue("AP_WIDTH")
  307. state = ""
  308. # first find a rough focus. This must be successful in the first place
  309. count = 0
  310. retry = 3
  311. while state != "success" and count < retry:
  312. state = search_wd(default_wd_range, search_step)
  313. print(state)
  314. if state == "failed":
  315. default_wd_range = default_wd_range * 1.5
  316. count += 1
  317. if state == "outside":
  318. default_wd_range = default_wd_range * 1.4
  319. count += 1
  320. if state == "success":
  321. # while state != "success":
  322. # state = search_wd(default_wd_range, search_step)
  323. # print(state)
  324. # if state == "failed":
  325. # default_wd_range = default_wd_range * 1.5
  326. # elif state == "outside":
  327. # pass
  328. wd_range = wd_searh_factor * sem.GetValue("AP_WIDTH") * iter_range_damping
  329. for i in range(iter_n):
  330. current_wd_range = wd_range * iter_range_damping**i
  331. state = current_stig_range = stig_range * iter_range_damping**i
  332. state = search_stig(current_stig_range, search_step, "X")
  333. state = search_stig(current_stig_range, search_step, "Y")
  334. state = search_wd(current_wd_range, search_step)
  335. return state