|
@@ -0,0 +1,586 @@
|
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
|
+"""
|
|
|
|
+This is an implementation of SEM API Wrapper
|
|
|
|
+
|
|
|
|
+How to use:
|
|
|
|
+
|
|
|
|
+The SEM_API class provides a context manager. It is highly recommended to use
|
|
|
|
+it through _with_ expression
|
|
|
|
+
|
|
|
|
+with SEM_API(state='remote') as sem:
|
|
|
|
+ do_something(sem)
|
|
|
|
+
|
|
|
|
+The SEM_API class must be initialized by specifying if the EM Server is running
|
|
|
|
+remotely or locally (state = 'remote' or 'local'). This will influence the
|
|
|
|
+behavior of how the real time image interface behaves.
|
|
|
|
+
|
|
|
|
+The interface provides following functions:
|
|
|
|
+
|
|
|
|
+1. Functions derived from SmartSEM API:
|
|
|
|
+
|
|
|
|
+ GetValue(AP_name, style='float')
|
|
|
|
+ SetValue(AP_name, value)
|
|
|
|
+ GetValueMin(AP_name)
|
|
|
|
+ GetValueMax(AP_name)
|
|
|
|
+ GetState(DP_name, style='string')
|
|
|
|
+ SetState(DP_name, value)
|
|
|
|
+ Execute(CMD_name)
|
|
|
|
+ GetStagePosition()
|
|
|
|
+ MoveStage(coord)
|
|
|
|
+ Grab(self, fname, left = 0.0, right = 1.0, top = 0.0, bottom = 1.0, overlay = False)
|
|
|
|
+
|
|
|
|
+2. Interface to get real time image. The real time image will be updated in
|
|
|
|
+ following properties:
|
|
|
|
+ img_array
|
|
|
|
+ If running remotely, it provides uint8 array of current image using the current
|
|
|
|
+ pixel store. If running locally, it provides uint16 array instead.
|
|
|
|
+ img_array_reduced
|
|
|
|
+ Only present if running locally. It provides uint8 array of current image in
|
|
|
|
+ 1024x768 pixel store.
|
|
|
|
+
|
|
|
|
+ The following commands control the start, pause and terminate of continuous
|
|
|
|
+ image acquisition:
|
|
|
|
+ UpdateImage_Start()
|
|
|
|
+ UpdateImage_Pause()
|
|
|
|
+ UpdateImage_Terminate()
|
|
|
|
+ The update rate can be set through the following property:
|
|
|
|
+ update_rate
|
|
|
|
+ possible values from 0.01 to 10
|
|
|
|
+
|
|
|
|
+3. Often used high level function:
|
|
|
|
+ wait_for_stage_idle()
|
|
|
|
+ Wait till the stage arrives in destination.
|
|
|
|
+ grab_full_image(fname)
|
|
|
|
+ Freeze and wait till scan finished to capture a full image.
|
|
|
|
+ set_scan_speed(scan_speed)
|
|
|
|
+ Directly set certain scan speed.
|
|
|
|
+ set_scan_mode(scan_mode)
|
|
|
|
+ Directly set the noise reduction mode of scan.
|
|
|
|
+ do_autofocus()
|
|
|
|
+ Peform autofocus, block the thread till it finishes.
|
|
|
|
+
|
|
|
|
+4. Event interface:
|
|
|
|
+ The event interface is implemented as "bag of handler function". The handler function is
|
|
|
|
+ called as:
|
|
|
|
+ handler(ParameterName, Reason, ParameterID, LastKnownValue)
|
|
|
|
+ The following function and attributes are used to control the event interface:
|
|
|
|
+ Add_Event(func)
|
|
|
|
+ Add func in the bag of handler.
|
|
|
|
+ Remove_Event(func)
|
|
|
|
+ Remove func from the bag of handler.
|
|
|
|
+ Set_Notify(PARAM)
|
|
|
|
+ Start monitoring PARAM, trigger the event when PARAM is changed.
|
|
|
|
+ Unset_Notify(PARAM)
|
|
|
|
+ Stop monitoring PARAM.
|
|
|
|
+ event_time
|
|
|
|
+ Set the time interval in second to check event trigger. Default is 0.1 S.
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+Version: 0.3
|
|
|
|
+Author: Luyang Han
|
|
|
|
+
|
|
|
|
+ChangLog:
|
|
|
|
+v0.3
|
|
|
|
+04.09.2019 : Add event interface
|
|
|
|
+"""
|
|
|
|
+
|
|
|
|
+import numpy as np
|
|
|
|
+import sys
|
|
|
|
+sys.coinit_flags = 0
|
|
|
|
+
|
|
|
|
+from win32com import client
|
|
|
|
+import pythoncom
|
|
|
|
+import threading, time, tempfile, mmap, warnings
|
|
|
|
+
|
|
|
|
+try:
|
|
|
|
+ from imageio import imread
|
|
|
|
+except:
|
|
|
|
+ try:
|
|
|
|
+ from scipy.misc import imread
|
|
|
|
+ except:
|
|
|
|
+ try:
|
|
|
|
+ from skimage.io import imread
|
|
|
|
+ except:
|
|
|
|
+ print("Cannot find the module with imread. Please install one of the following package:\n imageio, skimage or scipy.")
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# following changes are necessary for event to work
|
|
|
|
+from win32com.client import makepy
|
|
|
|
+sys.argv = ["makepy", r"CZ.EmApiCtrl.1"]
|
|
|
|
+makepy.main()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+_excpetion_dict = \
|
|
|
|
+{1000: ('API_E_GET_TRANSLATE_FAIL',
|
|
|
|
+ 'Failed to translate parameter into an id'),
|
|
|
|
+ 1001: ('API_E_GET_AP_FAIL', 'Failed to get analogue value'),
|
|
|
|
+ 1002: ('API_E_GET_DP_FAIL', 'Failed to get digital value'),
|
|
|
|
+ 1003: ('API_E_GET_BAD_PARAMETER',
|
|
|
|
+ 'Parameter supplied is not analogue nor digital'),
|
|
|
|
+ 1004: ('API_E_SET_TRANSLATE_FAIL',
|
|
|
|
+ 'Failed to translate parameter into an id'),
|
|
|
|
+ 1005: ('API_E_SET_STATE_FAIL', 'Failed to set a digital state'),
|
|
|
|
+ 1006: ('API_E_SET_FLOAT_FAIL', 'Failed to set a float value'),
|
|
|
|
+ 1007: ('API_E_SET_FLOAT_LIMIT_LOW', 'Value supplied is too low'),
|
|
|
|
+ 1008: ('API_E_SET_FLOAT_LIMIT_HIGH', 'Value supplied is too high'),
|
|
|
|
+ 1009: ('API_E_SET_BAD_VALUE', 'Value supplied is is of wrong type'),
|
|
|
|
+ 1010: ('API_E_SET_BAD_PARAMETER',
|
|
|
|
+ 'Parameter supplied is not analogue nor digital'),
|
|
|
|
+ 1011: ('API_E_EXEC_TRANSLATE_FAIL', 'Failed to translate command into an id'),
|
|
|
|
+ 1012: ('API_E_EXEC_CMD_FAIL', 'Failed to execute command'),
|
|
|
|
+ 1013: ('API_E_EXEC_MCF_FAIL', 'Failed to execute file macro'),
|
|
|
|
+ 1014: ('API_E_EXEC_MCL_FAIL', 'Failed to execute library macro'),
|
|
|
|
+ 1015: ('API_E_EXEC_BAD_COMMAND', 'Command supplied is not implemented'),
|
|
|
|
+ 1016: ('API_E_GRAB_FAIL', 'Grab command failed'),
|
|
|
|
+ 1017: ('API_E_GET_STAGE_FAIL', 'Get Stage position failed'),
|
|
|
|
+ 1018: ('API_E_MOVE_STAGE_FAIL', 'Move Stage position failed'),
|
|
|
|
+ 1019: ('API_E_NOT_INITIALISED', 'API not initialised'),
|
|
|
|
+ 1020: ('API_E_NOTIFY_TRANSLATE_FAIL',
|
|
|
|
+ 'Failed to translate parameter to an id'),
|
|
|
|
+ 1021: ('API_E_NOTIFY_SET_FAIL', 'Set notification failed'),
|
|
|
|
+ 1022: ('API_E_GET_LIMITS_FAIL', 'Get limits failed'),
|
|
|
|
+ 1023: ('API_E_GET_MULTI_FAIL', 'Get multiple parameters failed'),
|
|
|
|
+ 1024: ('API_E_SET_MULTI_FAIL', 'Set multiple parameters failed'),
|
|
|
|
+ 1025: ('API_E_NOT_LICENSED', 'Missing API license'),
|
|
|
|
+ 1026: ('API_E_NOT_IMPLEMENTED', 'Reserved or not implemented'),
|
|
|
|
+ 1027: ('API_E_GET_USER_NAME_FAIL',
|
|
|
|
+ 'Failed to get user name (Remoting Interface only)'),
|
|
|
|
+ 1028: ('API_E_GET_USER_IDLE_FAIL',
|
|
|
|
+ 'Failed to get user idle state (Remoting Interface only)'),
|
|
|
|
+ 1029: ('API_E_GET_LAST_REMOTING_CONNECT_ERROR_FAIL',
|
|
|
|
+ 'Failed to get the last remoting connection error string (Remoting Interface Only)'),
|
|
|
|
+ 1030: ('API_E_EMSERVER_LOGON_FAILED',
|
|
|
|
+ 'Failed to remotely logon to the EM Server (username and password may be incorrect or EM Server is not running or User is already logged on, Remoting only)'),
|
|
|
|
+ 1031: ('API_E_EMSERVER_START_FAILED',
|
|
|
|
+ 'Failed to start the EM Server. This may be because the Server is already running or has an internal error (Remoting Interface only)'),
|
|
|
|
+ 1032: ('API_E_PARAMETER_IS_DISABLED',
|
|
|
|
+ 'The command or parameter is currently disabled (you cannot execute or set it. Remoting Interface only)'),
|
|
|
|
+ 2027: ('API_E_REMOTING_NOT_CONFIGURED',
|
|
|
|
+ 'Remoting incorrectly configured, use RConfigure to correct'),
|
|
|
|
+ 2028: ('API_E_REMOTING_FAILED_TO_CONNECT',
|
|
|
|
+ 'Remoting did not connect to the server'),
|
|
|
|
+ 2029: ('API_E_REMOTING_COULD_NOT_CREATE_INTERFACE',
|
|
|
|
+ 'Remoting could not start (unknown reason)'),
|
|
|
|
+ 2030: ('API_E_REMOTING_EMSERVER_NOT_RUNNING',
|
|
|
|
+ 'Remoting: Remote server is not running currently'),
|
|
|
|
+ 2031: ('API_E_REMOTING_NO_USER_LOGGED_IN',
|
|
|
|
+ 'Remoting: Remote server has no user logged in')}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# class to handle exceptions
|
|
|
|
+class API_ERROR(Exception):
|
|
|
|
+ def __init__(self, error_code):
|
|
|
|
+ global _excpetion_dict
|
|
|
|
+ error_text = ('\n').join(_excpetion_dict[error_code])
|
|
|
|
+ raise Exception(error_text)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# periodically run function
|
|
|
|
+class SwitchThread(threading.Thread):
|
|
|
|
+ """
|
|
|
|
+ class to run a function repeatedly in the background.
|
|
|
|
+ The delay between each run is given as delay in s.
|
|
|
|
+ The thread can be paused and also ended.
|
|
|
|
+ """
|
|
|
|
+ def __init__(self, target=None, name=None, args=(), kwargs={}, delay = 1):
|
|
|
|
+ super(SwitchThread, self).__init__(group=None, target=target, name=name, args=(), kwargs={})
|
|
|
|
+ self.target = target
|
|
|
|
+ self.args = args
|
|
|
|
+ self.kwargs = kwargs
|
|
|
|
+ self.delay = delay
|
|
|
|
+ self.enable = True
|
|
|
|
+ self.stop = False
|
|
|
|
+ return
|
|
|
|
+ def run(self):
|
|
|
|
+ while not self.stop:
|
|
|
|
+ if self.enable:
|
|
|
|
+ self.target(*self.args, **self.kwargs)
|
|
|
|
+ time.sleep(self.delay)
|
|
|
|
+ def pause(self):
|
|
|
|
+ self.enable = False
|
|
|
|
+ def resume(self):
|
|
|
|
+ self.enable = True
|
|
|
|
+ def terminate(self):
|
|
|
|
+ self.stop = True
|
|
|
|
+
|
|
|
|
+EventBase = client.getevents("CZ.EMApiCtrl.1")
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class SEM_Handle(EventBase):
|
|
|
|
+ def __init__(self, sem):
|
|
|
|
+ self.subscribers = []
|
|
|
|
+ super().__init__(sem)
|
|
|
|
+ def OnNotifyWithCurrentValue(self, lpszParameter, Reason, paramid, dLastKnownValue):
|
|
|
|
+ for sub in self.subscribers:
|
|
|
|
+ sub (lpszParameter, Reason, paramid, dLastKnownValue)
|
|
|
|
+
|
|
|
|
+# EventBase = client.getevents("CZ.EMApiCtrl.1")
|
|
|
|
+# class SEM_Handle(EventBase):
|
|
|
|
+# def OnNotifyWithCurrentValue(self, lpszParameter, Reason, paramid, dLastKnownValue):
|
|
|
|
+# print("Reason:%d. The parameter %s changed to %d." %(Reason, lpszParameter, dLastKnownValue))
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class SEM_API:
|
|
|
|
+ """
|
|
|
|
+ Python Wrapper to SEM API interface
|
|
|
|
+
|
|
|
|
+ The class provide a context manager to do some house keeping, it is advised
|
|
|
|
+ to use with statement.
|
|
|
|
+
|
|
|
|
+ with SEM_API(state='remote') as sem:
|
|
|
|
+ do_something(sem)
|
|
|
|
+
|
|
|
|
+ """
|
|
|
|
+
|
|
|
|
+ def __init__(self, state='local'):
|
|
|
|
+ """
|
|
|
|
+ Function to initialize the API interface.
|
|
|
|
+ This function uses the long InitialiseRemote(void) command.
|
|
|
|
+
|
|
|
|
+ """
|
|
|
|
+ self.mic = client.Dispatch('CZ.EMApiCtrl.1')
|
|
|
|
+ if not (state == "remote" or state == "local"):
|
|
|
|
+ raise ValueError("state is either remote or local")
|
|
|
|
+ self.__state = state
|
|
|
|
+ # first initialize
|
|
|
|
+ res = self.mic.InitialiseRemoting()
|
|
|
|
+ if res == 0:
|
|
|
|
+ # prepare the background working
|
|
|
|
+ # prepare the MMF for local operation
|
|
|
|
+ if self.__state == "local":
|
|
|
|
+ self.__pymap = mmap.mmap(-1, 1024*768+1064,"Capture0.MMF",mmap.ACCESS_READ)
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
+ self.__background_worker = SwitchThread(target=self.__update_image, delay = 1.0)
|
|
|
|
+ self.__background_worker.start()
|
|
|
|
+ self.__background_worker.pause()
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
+ # self.subs = []
|
|
|
|
+ # self.event = SEM_Handle(self.mic, self.subs)
|
|
|
|
+ self.event_time = 0.1
|
|
|
|
+ self.event = SEM_Handle(self.mic)
|
|
|
|
+ self.__event_stop = False
|
|
|
|
+ self.__event_thread = threading.Thread(target = self.__pump)
|
|
|
|
+ self.__event_thread.start()
|
|
|
|
+ else:
|
|
|
|
+ raise API_ERROR(res)
|
|
|
|
+
|
|
|
|
+ # decorator for the wrapper function
|
|
|
|
+ def __error_handling(func):
|
|
|
|
+ def func_wrapper(*arg, **kwargs):
|
|
|
|
+ res = func(*arg, **kwargs)
|
|
|
|
+ if type(res) == int:
|
|
|
|
+ return_code = res
|
|
|
|
+ if return_code != 0:
|
|
|
|
+ raise API_ERROR(return_code)
|
|
|
|
+ elif type(res) == tuple:
|
|
|
|
+ return_code = res[0]
|
|
|
|
+ result = res[1]
|
|
|
|
+ if return_code == 0:
|
|
|
|
+ return result
|
|
|
|
+ else:
|
|
|
|
+ raise API_ERROR(return_code)
|
|
|
|
+ return func_wrapper
|
|
|
|
+
|
|
|
|
+ # function to handle the event
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ def __pump(self):
|
|
|
|
+ pythoncom.CoInitializeEx(pythoncom.COINIT_MULTITHREADED)
|
|
|
|
+ while self.__event_stop is not True:
|
|
|
|
+ pythoncom.PumpWaitingMessages()
|
|
|
|
+ time.sleep(self.event_time)
|
|
|
|
+ pythoncom.CoUninitialize()
|
|
|
|
+
|
|
|
|
+ def Add_Event(self, func):
|
|
|
|
+ self.event.subscribers.append(func)
|
|
|
|
+
|
|
|
|
+ def Remove_Event(self, func):
|
|
|
|
+ self.event.subscribers.remove(func)
|
|
|
|
+
|
|
|
|
+ @__error_handling
|
|
|
|
+ def Set_Notify(self, PARAM):
|
|
|
|
+ res = self.mic.SetNotify(PARAM, True)
|
|
|
|
+ return res
|
|
|
|
+
|
|
|
|
+ @__error_handling
|
|
|
|
+ def Unset_Notify(self, PARAM):
|
|
|
|
+ res = self.mic.SetNotify(PARAM, False)
|
|
|
|
+ return res
|
|
|
|
+
|
|
|
|
+ # some variables to hold the data
|
|
|
|
+ __fname = tempfile.TemporaryFile(suffix='.bmp').name
|
|
|
|
+
|
|
|
|
+ # function to update the image file
|
|
|
|
+ def __update_image(self):
|
|
|
|
+ if self.__state == 'remote':
|
|
|
|
+ # when working remote, write the image to a bmp file and read back.
|
|
|
|
+ self.mic.Grab(0,0,1024,768,0,self.__fname)
|
|
|
|
+ self.img_array = imread(self.__fname)
|
|
|
|
+ elif self.__state == 'local':
|
|
|
|
+ # when working local, write the image to mmap file and read back.
|
|
|
|
+ # as each time the size of the frame can change, we create a mmap
|
|
|
|
+ # every time on the fly to grab the image
|
|
|
|
+ pixel_density = tuple(map(int,self.GetState("DP_IMAGE_STORE").split("*")))
|
|
|
|
+ self.mic.Grab(0,0,1024,768,0,"CZ.MMF")
|
|
|
|
+ py_map = mmap.mmap(-1, pixel_density[0]*pixel_density[1]*2,"CZ.MMF",mmap.ACCESS_READ)
|
|
|
|
+ self.img_array = np.copy(np.frombuffer(py_map, dtype="uint16").reshape((pixel_density[1],pixel_density[0])))
|
|
|
|
+ py_map.close()
|
|
|
|
+ # update local image
|
|
|
|
+ self.img_array_reduce = np.flip(np.frombuffer(self.__pymap, dtype="uint8", offset=1064).reshape((768,1024)), axis=0)
|
|
|
|
+ return
|
|
|
|
+
|
|
|
|
+ @property
|
|
|
|
+ def update_rate(self):
|
|
|
|
+ return self.__background_worker.delay
|
|
|
|
+
|
|
|
|
+ @update_rate.setter
|
|
|
|
+ def update_rate(self, value):
|
|
|
|
+ if value < 0.01:
|
|
|
|
+ value = 0.01
|
|
|
|
+ warnings.warn("Update delay cannot be smaller than 10 ms.")
|
|
|
|
+ elif value > 10:
|
|
|
|
+ value = 10
|
|
|
|
+ warnings.warn("Update delay cannot be larger than 10 s.")
|
|
|
|
+ self.__background_worker.delay = value
|
|
|
|
+
|
|
|
|
+ # function for AP
|
|
|
|
+ @__error_handling
|
|
|
|
+ def GetValue(self, AP_name, style='float'):
|
|
|
|
+ """
|
|
|
|
+ function to get an analog value.
|
|
|
|
+ style: float or string
|
|
|
|
+ float returns a floating number, string returns a string representation with unit.
|
|
|
|
+ """
|
|
|
|
+ if style == 'float':
|
|
|
|
+ buffer = 0.0
|
|
|
|
+ res = self.mic.Get(AP_name, buffer)
|
|
|
|
+ return res
|
|
|
|
+ elif style == 'string':
|
|
|
|
+ buffer = ''
|
|
|
|
+ res = self.mic.Get(AP_name, buffer)
|
|
|
|
+ return res
|
|
|
|
+ else:
|
|
|
|
+ raise AttributeError("style = float or string")
|
|
|
|
+
|
|
|
|
+ @__error_handling
|
|
|
|
+ def GetValueMin(self, AP_name):
|
|
|
|
+ buffer = 0.0
|
|
|
|
+ res = self.mic.GetLimits(AP_name, buffer, buffer)
|
|
|
|
+ return (res[0], res[1])
|
|
|
|
+
|
|
|
|
+ @__error_handling
|
|
|
|
+ def GetValueMax(self, AP_name):
|
|
|
|
+ buffer = 0.0
|
|
|
|
+ res = self.mic.GetLimits(AP_name, buffer, buffer)
|
|
|
|
+ return (res[0], res[2])
|
|
|
|
+
|
|
|
|
+ @__error_handling
|
|
|
|
+ def SetValue(self, AP_name, value):
|
|
|
|
+ """
|
|
|
|
+ function to set an analog value.
|
|
|
|
+ The function can take either floating point or a formatted string.
|
|
|
|
+ """
|
|
|
|
+ if type(value) == str:
|
|
|
|
+ pass
|
|
|
|
+ else:
|
|
|
|
+ value = float(value)
|
|
|
|
+ res = self.mic.Set(AP_name, value)
|
|
|
|
+ return res
|
|
|
|
+
|
|
|
|
+ # function for DP
|
|
|
|
+
|
|
|
|
+ @__error_handling
|
|
|
|
+ def GetState(self, DP_name, style='string'):
|
|
|
|
+ """
|
|
|
|
+ function to get an digital value.
|
|
|
|
+ style: float or string
|
|
|
|
+ int returns a integer number, string returns a string representation.
|
|
|
|
+ """
|
|
|
|
+ if style == 'int':
|
|
|
|
+ buffer = 0
|
|
|
|
+ res = self.mic.Get(DP_name, buffer)
|
|
|
|
+ return res
|
|
|
|
+ elif style == 'string':
|
|
|
|
+ buffer = ''
|
|
|
|
+ res = self.mic.Get(DP_name, buffer)
|
|
|
|
+ return res
|
|
|
|
+ else:
|
|
|
|
+ raise AttributeError("style = int or string")
|
|
|
|
+
|
|
|
|
+ @__error_handling
|
|
|
|
+ def SetState(self, DP_name, value):
|
|
|
|
+ """
|
|
|
|
+ function to set a digital state.
|
|
|
|
+ The function can take either integer or a formatted string.
|
|
|
|
+ """
|
|
|
|
+ if type(value) == str:
|
|
|
|
+ pass
|
|
|
|
+ else:
|
|
|
|
+ value = int(value)
|
|
|
|
+ res = self.mic.Set(DP_name, value)
|
|
|
|
+ return res
|
|
|
|
+
|
|
|
|
+ # function for CMD
|
|
|
|
+ @__error_handling
|
|
|
|
+ def Execute(self, CMD_name):
|
|
|
|
+ res = self.mic.Execute(CMD_name)
|
|
|
|
+ return res
|
|
|
|
+
|
|
|
|
+ # function about stage
|
|
|
|
+ @__error_handling
|
|
|
|
+ def GetStagePosition(self):
|
|
|
|
+ buffer = 0.0
|
|
|
|
+ res = self.mic.GetStagePosition(buffer, buffer, buffer, buffer, buffer, buffer)
|
|
|
|
+ return (res[0], res[1:])
|
|
|
|
+
|
|
|
|
+ @__error_handling
|
|
|
|
+ def MoveStage(self, coord):
|
|
|
|
+ if len(coord) == 6:
|
|
|
|
+ res = self.mic.MoveStage(*coord)
|
|
|
|
+ return res
|
|
|
|
+ else:
|
|
|
|
+ raise IndexError("coord must be a list or tuple of 6 floating number")
|
|
|
|
+
|
|
|
|
+ # function about grab image
|
|
|
|
+ @__error_handling
|
|
|
|
+ def Grab(self, fname, X = 0, Y = 0, W = 1024, H = 768, overlay = False):
|
|
|
|
+ """
|
|
|
|
+ The function to grab the current image.
|
|
|
|
+ left, right, top, button defines the desired size of the imaged, express as the
|
|
|
|
+ coordinate relative to the full image.
|
|
|
|
+ For example, if 10% of the edge should be excluded, the coordinate should be
|
|
|
|
+ left = 0.1, right = 0.9, top = 0,1, button = 0.9
|
|
|
|
+ overlay determines whether the image overlay such as datazone should be included.
|
|
|
|
+ """
|
|
|
|
+ #X = int(left * 1024)
|
|
|
|
+ #Y = int(top * 768)
|
|
|
|
+ #W = int((right-left)*1024)
|
|
|
|
+ #H = int((bottom-top)*768)
|
|
|
|
+ if overlay:
|
|
|
|
+ res = self.mic.Grab(X,Y,W,H,-1,fname)
|
|
|
|
+ else:
|
|
|
|
+ res = self.mic.Grab(X,Y,W,H,0,fname)
|
|
|
|
+ return res
|
|
|
|
+
|
|
|
|
+ # parameter and functions dealing with the memory map file and real time imaging.
|
|
|
|
+ # implementation pending
|
|
|
|
+ # The real time image access only works with local client.
|
|
|
|
+
|
|
|
|
+ # 2 different interface are supplied, the img_array gives the real time image with
|
|
|
|
+ # current pixel density in 16 bit grey. the image_array_reduced gives 8 bit grey
|
|
|
|
+ # image of image reduced to 1024x768
|
|
|
|
+ # the value should be updated regularly by a seperate thread in the background.
|
|
|
|
+ img_array = np.zeros((768,1024))
|
|
|
|
+ img_array_reduce = np.zeros((768,1024))
|
|
|
|
+ # img_array = np.zeros((768,1024)).astype('uint16')
|
|
|
|
+ def UpdateImage_Start(self):
|
|
|
|
+ """
|
|
|
|
+ function to start continuous updating the imaging array
|
|
|
|
+ """
|
|
|
|
+ self.__background_worker.resume()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ def UpdateImage_Pause(self):
|
|
|
|
+ """
|
|
|
|
+ function to pause continuous updating the imaging array
|
|
|
|
+ """
|
|
|
|
+ self.__background_worker.pause()
|
|
|
|
+
|
|
|
|
+ # some typically used higher level functions
|
|
|
|
+ # implementation pending
|
|
|
|
+ def move_stage_relative_xy(self, dx, dy):
|
|
|
|
+ pos = self.GetStagePosition()
|
|
|
|
+ self.MoveStage((pos[0] + dx, pos[1]+dy, pos[2],pos[3],pos[4],pos[5]))
|
|
|
|
+
|
|
|
|
+ def wait_for_stage_idle(self):
|
|
|
|
+ """
|
|
|
|
+ wait for the stage movement to finish.
|
|
|
|
+ """
|
|
|
|
+ while not self.GetState("DP_STAGE_IS") == "Idle":
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
+
|
|
|
|
+ def set_scan_speed(self, DP_Param):
|
|
|
|
+ c_dict = {
|
|
|
|
+ "0":"CMD_SCANRATE0",
|
|
|
|
+ "1":"CMD_SCANRATE1",
|
|
|
|
+ "2":"CMD_SCANRATE2",
|
|
|
|
+ "3":"CMD_SCANRATE3",
|
|
|
|
+ "4":"CMD_SCANRATE4",
|
|
|
|
+ "5":"CMD_SCANRATE5",
|
|
|
|
+ "6":"CMD_SCANRATE6",
|
|
|
|
+ "7":"CMD_SCANRATE7",
|
|
|
|
+ "8":"CMD_SCANRATE8",
|
|
|
|
+ "9":"CMD_SCANRATE9",
|
|
|
|
+ "10":"CMD_SCANRATE10",
|
|
|
|
+ "11":"CMD_SCANRATE11",
|
|
|
|
+ "12":"CMD_SCANRATE12",
|
|
|
|
+ "13":"CMD_SCANRATE13",
|
|
|
|
+ "14":"CMD_SCANRATE14",
|
|
|
|
+ "15":"CMD_SCANRATE15"
|
|
|
|
+ }
|
|
|
|
+ self.Execute(c_dict[str(DP_Param)])
|
|
|
|
+
|
|
|
|
+ def set_scan_mode(self, DP_Param):
|
|
|
|
+ c_dict = {
|
|
|
|
+ "Pixel Avg.":"CMD_PIXNR",
|
|
|
|
+ "Frame Avg":"CMD_FRAME_AVERAGE",
|
|
|
|
+ "Frame Int. Busy":"CMD_FRAME_INT",
|
|
|
|
+ "Frame Int. Done":"CMD_FRAME_INT",
|
|
|
|
+ "Line Avg":"CMD_LINE_AVG",
|
|
|
|
+ "Line Int. Busy":"CMD_LINE_INT",
|
|
|
|
+ "Line Int. Done":"CMD_LINE_INT",
|
|
|
|
+ "Continuous Avg.":"CMD_CONTINUOUS_AVG",
|
|
|
|
+ "Drift Comp. Frame Int. Busy":"CMD_DC_FRAME_INT",
|
|
|
|
+ "Drift Comp. Frame Int. Done":"CMD_DC_FRAME_INT",
|
|
|
|
+ "Drift Comp. Frame Avg.":"CMD_DC_FRAME_AVG"
|
|
|
|
+ }
|
|
|
|
+ self.Execute(c_dict[DP_Param])
|
|
|
|
+
|
|
|
|
+ def do_autofocus(self):
|
|
|
|
+ self.SetState("DP_AUTOFOCUS_VERSION", "Line Scan based Autofocus Version")
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
+ self.Execute("CMD_AUTO_FOCUS_FINE")
|
|
|
|
+ time.sleep(2)
|
|
|
|
+ while self.GetState("DP_AUTO_FN_STATUS") == "Busy":
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ def grab_full_image(self, fname, overlay = False):
|
|
|
|
+ """
|
|
|
|
+ grab the current full image by restarting the scan and till the complete
|
|
|
|
+ frame is finished, and then save the image.
|
|
|
|
+ """
|
|
|
|
+ self.SetState("DP_FREEZE_ON", "End Frame")
|
|
|
|
+ self.Execute("CMD_UNFREEZE_ALL")
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
+ #self.Execute("CMD_MODE_NORMAL")
|
|
|
|
+ #time.sleep(0.1)
|
|
|
|
+ if self.GetState("DP_NOISE_REDUCTION") in ("Frame Avg","Line Avg",\
|
|
|
|
+ "Pixel Avg.", "Continuous Avg.", "Drift Comp. Frame Avg."):
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
+ self.Execute("CMD_FREEZE_ALL")
|
|
|
|
+ while self.GetState("DP_FROZEN") == "Live":
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
+ self.Grab(fname, overlay = overlay)
|
|
|
|
+
|
|
|
|
+ # context interface
|
|
|
|
+ # it is important to use this class through with statement
|
|
|
|
+ # the exit function will properly clean up the wrapper.
|
|
|
|
+ def __enter__(self):
|
|
|
|
+ return self
|
|
|
|
+
|
|
|
|
+ def __exit__(self, *arg):
|
|
|
|
+ self.__background_worker.terminate()
|
|
|
|
+ self.__background_worker.join()
|
|
|
|
+ # stop event interface
|
|
|
|
+ self.__event_stop = True
|
|
|
|
+ self.__event_thread.join()
|
|
|
|
+ self.event.close()
|
|
|
|
+ del self.event
|
|
|
|
+ # clear the MMF
|
|
|
|
+ if self.__state == 'local':
|
|
|
|
+ self.__pymap.close()
|
|
|
|
+ self.mic.Grab(0,0,0,0,0,"CZ.MMF")
|
|
|
|
+ self.mic.ClosingControl()
|
|
|
|
+
|