Ensemble ResNet for in-situ Spectroscopy#

\(_{Yongtao}\) \(_{Liu,}\)
\(_{youngtaoliu@gmail.com}\)

\(_{Feb}\) \(_{2024}\)

Install and Import#

import os
import win32com.client
import numpy as np
import time
import h5py
import sidpy
import pyNSID
import matplotlib.pyplot as plt
from tqdm import tqdm
import atomai as aoi

from scipy.ndimage import gaussian_filter
from IPython.display import clear_output
from skimage.morphology import skeletonize
from sklearn.metrics import euclidean_distances


# import acquition.py
from Acquisition_v0_6 import Acquisition   # include the Acquistion_v0.py in the same directory

Start BEPyAE.exe and set VI#

  • Start BEPyAE.ext

  • Set VI of BEPyAE; if this version includes PyScanner, also set VIs for PyScanner

newexp = Acquisition(exe_path = r"G:\My Drive\AE\PyAE\BEPyAE 060123 01\BEPyAE.exe")   # exe_path is the directory of BEPyAE; 

Initialize Igor AR18#

  • Set offline development

  • Build a connection between BEPyAE and AR18

  • Get parameters in AR18

newexp.init_BEPyAE(offline_development = True) # set offline_development=True if doing offline development
                                                # executing this will also initlize AR18

Set tip parameters#

  • set setpoint, tip locations

newexp.tip_control(tip_parms_dict = {"set_point_V_00": 1, "next_x_pos_00": -0.5, "next_y_pos_01": 0.5},
                   do_move_tip = True, 
                   do_set_setpoint = True) # Executing this code will set setpoint to 1 V, 
                                           # and move tip to location [0.5, 0.5]
Setpoint is:  1.0
Tip parameters are:  (-0.5, 0.5, 0.5)
Please reset if some parameters are incorrect

Set IO#

This defines IO parameters, such as AFM platform: AR18, amplifiers, channel data types, etc

newexp.define_io_cluster(IO_cluster_parms_dict = {"analog_output_amplifier_06": 1, 
                                                  "channel_01_type_07": 1, 
                                                  "channel_02_type_08": 2,"channel_03_type_09": 3,})
IO control parameters are:  ('0 Cypher AR18', '6124', 4000000.0, 10.0, 10.0, 'AC and DC on AO0', 10.0, 'topography', 'current', 'aux', 'external')
Please reset if some parameters are incorrect

Set BE pulse parameters#

# set BE parameters
newexp.define_be_parms(be_parms_dict = {"center_frequency_Hz_00": 335, "band_width_Hz_01": 100,
                                       "amplitude_V_02": 1, "phase_variation_03": 1,
                                       "repeats_04": 4, "req_pulse_duration_s_05": 4,
                                       "auto_smooth_ring_06": 1}, 
                      do_create_be_waveform = True)
BE parameters are:  (335000.0, 100000.0, 1.0, 1.0, 4, 0.004, 1, 3352.2952763920002, 0.12159459061880915)
Please reset if some parameters are incorrect
(335000.0,
 100000.0,
 1.0,
 1.0,
 4,
 0.004,
 1,
 3352.2952763920002,
 0.12159459061880915)

BE Line scan to test BE parameters#

  • This is a single BE line scan

  • This returns 5 datasets: quick_fitting, complex spectra, and 3 channels

# Do a single line scan
qk_fit, com_spec, chn1, chn2, chn3 = newexp.do_line_scan(line_scan_parms_dict = {"num_BE_pulses_01": 32,
                                                                                 "start_x_pos_00": -0.5, "start_y_pos_01": 0,
                                                                                 "stop_x_pos_02": 0.5, "stop_y_pos_03": 0},
                                                         upload_to_daq = True, do_line_scan = True)
voltage offset and number of BE pulse are:  (0.0, 32)
line scan start and end positions:  (-0.5, 0.0, 0.5, 0.0)

Experiment Starts#

Prior to expeirment, set a directory for saving data#

os.chdir("/content/save directory/")
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_25324/3425510952.py in <module>
----> 1 os.chdir("/content/save directory/")

FileNotFoundError: [WinError 3] The system cannot find the path specified: '/content/save directory/'

Step 1. Perform an image measurement, e.g., BEPFM#

dset_pfm, dset_chns, dset_cs = newexp.raster_scan(raster_parms_dict = {"scan_pixel": 256, "scan_x_start": -1.0,
                                                                       "scan_y_start": -1.0,"scan_x_stop": 1.0,
                                                                       "scan_y_stop": 1.0}, file_name = "BEPFM")

f, (ax1, ax2, ax3, ax4, ax5, ax6) = plt.subplots(1, 6, figsize = (30, 5), dpi = 100)
ax1.imshow(dset_pfm[:,:,0])
ax2.imshow(dset_pfm[:,:,1])
ax3.imshow(dset_pfm[:,:,2])
ax4.imshow(dset_pfm[:,:,3])
ax5.imshow(dset_chns[0,:,:])
ax6.imshow(dset_chns[1,:,:])
plt.show()
20 locations are ready for experiments

Step 2. Pick the image channel involving the target objects#

# Load target image
target_raw = dset_pfm[:,:,0]
target_raw = np.copy(target_raw)
# normalize target_img
target_raw = (target_raw-target_raw.min())/(target_raw.ptp())

Step 3. Pre-trained ResNet idenfies target objects#

# Load pre-trained ResNet weights
smodel, ensemble= aoi.models.load_ensemble("MHP_Grainboundry_ensemble_metadict.tar")

# Prediction
predictor = aoi.predictors.EnsemblePredictor(smodel, ensemble, nb_classes=1)
pred_mean, pred_var = predictor.predict(obtained_amp)
target_object = pred_mean.squeeze()
target_uncertainty = pred_var.squeeze()
    
# Plot prediction and uncertainty
f,(ax0, ax1, ax2) = plt.subplots(1, 3, dpi = 100)
ax0.imshow(target_img)
ax0.axis ('off')
ax0.set_title('Raw Image')
ax1.imshow(target_object, cmap = 'gray')
ax1.axis('off')
ax1.set_title('Prediction')
ax2.imshow(target_uncertainty, cmap = 'gray')
ax2.axis('off')
ax2.set_title('Uncertainty')
plt.show()

Step 4. Extract coordinates of target objects#

# convert the target object image to a binary image
_, target_object_binary = cv2.threshold(target_object, 0.4, 1, cv2.THRESH_BINARY)
# skeletonzie the lines to avoide duplicated coordinates corresponding to the same object
target_object_img = skeletonize(target_object_binary==1)

# plot binary image and skeletonize image
_, (ax1, ax2) = plt.subplots(1, 2, dpi = 100)
ax1.imshow(target_object_binary, origin = 'lower')
ax1.set_title("Binary")
ax1.set_axis("off")
ax2.imshow(target_object_img, origin = 'lower')
ax2.set_title("Skeletonize")
ax2.set_axis("off")
# Obtain all target coordinates
target_coordinates = np.asarray(np.where(target_object_img==1)).T 

# Rearange the coordinates to make the route along the object
# start at the default first location, ususally this is the lcoation near the bottom left corner
current_location = np.zeros((1,2))
current_location[:,] = target_coordinates[0,] 

pristine_route = np.copy(target_coordinates[1:,])
new_route = np.copy(current_location)

while len(pristine_route) != 0:  # Iterate until move all coordiantes in prisinte route to new route
    dist = euclidean_distances(current_location,pristine_route) 
    next_location = np.asarray(np.where(dist==dist.min())) # The next location is the one closest to the current location
    current_location [:,] = route[next_location[-1][-1]] # Update current location as the next location
    new_route = np.append (new_route, current_location, 0) # Append next location to the new route
    pristine_route = np.delete(pristine_route, next_loccation[:][1], 0) # Remove this locaiton from the prinstine route
  
## Plot the new route
plt.figure(dpi = 100)
c = np.arange (len(new_route[:,0]))
plt.scatter(new_route[:,1], new_route[:,0], c = c, cmap = "jet")
plt.show()
print("Total locations are: ", len(new_route[:,0]))
# In some cases, it is not necessary to measure all target locations, so we can skip some.
skip_every = 5
skip_index = np.arange(0, len(new_route[:,0]), skip_every)  
measure_coordinates = new_route[skip_index]  # skip every 'skip_every' step to create a measurement coordiantes 

plt.figure(dpi = 100)
c = np.arange(len(measure_coordinates[:,0]))
plt.scatter(measure_coordinates[:,1], measure_coordinates[:,0], c = c, cmap = "jet")
plt.show()
print ("Total measurement locations are: ", len(measure_coordinates[:,0]))

Step 5. Perform pre-planned measurements (e.g., BEPS) at target locations#

# Convert image coordinates to parameters for probe location
measure_locations = newexp.convert_coordinates(measure_coordinates)
measure_steps = len(measure_coordinates[:,0])

for i in tqdm(range (measure_steps)):
    print("Step {}/{}".format(i, measure_steps))
    
    # Measurement location
    next_coordinate = measure_locations[i]
    
    # Do measurement
    newexp.do_beps(measure_locations)
    clear_output()

Step 6. Save all results as an h5 file#

hf = h5py.File("Processing_Result", 'a')
hf['Images/Raw'] = np.asarray(target_raw)
hf['Images/Prediction'] = np.asarray(target_object)
hf['Images/Uncertainty'] = np.asarray(target_uncertainty)
hf['Images/Binary Object'] = np.asarray(object_binary)
hf['Images/Skeletonize Object'] = np.asarray(target_object_img)
hf['Coordinates/Target'] = np.asarray(target_coordinates)
hf['Coordinates/Rearange'] = np.asarray(new_route)
hf['Coordinates/Measure'] = np.asarray(measure_coordinates)
hf['Coordinates/Convert'] = np.asarray(measure_locations)