class PIMotor: """ Physik Intrumente Objective motor control The objective is attached to a motor that can move it up and down. This file is a minimalistic version of xinmeng's 'focuser.py' but contains the exact same functionality. """ def __init__(self, objective_motor_handle=None): # Connect the objective motor if it is not given if objective_motor_handle == None: self.objective = GCSDevice( gcsdll=__file__ + '/../../' + '/PI_ObjectiveMotor/PI_GCS2_DLL_x64.dll') self.objective.ConnectUSB( serialnum='PI C-863 Mercury SN 0185500828') else: self.objective = objective_motor_handle def disconnect(self): """ Disconnects the objective motor, initialization necessary to use it again. """ self.objective.CloseConnection() def moveAbs(self, z): """ Moves the objective motor to a target position, 'z' is the position on the z-axis in millimeters. Example: z = 3.45 moves the objective 3.45 millimeters above zero. """ self.objective.MOV(self.objective.axes, z) pitools.waitontarget(self.objective) # below this line is not really necessary positions = self.objective.qPOS(self.objective.axes) for axis in self.objective.axes: print('position of axis {} = {:.5f}'.format(axis, positions[axis])) def getPos(self): """ Reports the position of the objective motor in units of 10 micron. Example: position = 3.45 means the motor position is 3.45 millimeters from 0 """ positions = self.objective.qPOS(self.objective.axes) return positions['1']
class PIMotor: def __init__(self): """ Provide a device, connected via the PI GCS DLL. def pipython.gcsdevice.GCSDevice.__init__ ( self, devname = '', gcsdll = '') Parameters devname : Name of device, chooses according DLL which defaults to PI_GCS2_DLL. gcsdll : Name or path to GCS DLL to use, overwrites 'devname'. Returns ------- None. """ CONTROLLERNAME = 'C-863.11' #STAGES = None STAGES = ('M-110.1DG') REFMODE = ('FNL') # Get the path to dll in the same folder. abspath = os.path.abspath(__file__) dname = os.path.dirname(abspath) + '/PI_GCS2_DLL_x64.dll' print(dname) self.pidevice = GCSDevice(gcsdll=dname) print(self.pidevice.EnumerateUSB()) # InterfaceSetupDlg() is an interactive dialog. There are other methods to # connect to an interface without user interaction. serialstring = self.pidevice.EnumerateUSB() print(serialstring[0]) #pidevice.InterfaceSetupDlg(key='sample') # pidevice.ConnectRS232(comport=1, baudrate=115200) self.pidevice.ConnectUSB(serialnum='PI C-863 Mercury SN 0185500828') # pidevice.ConnectTCPIP(ipaddress='192.168.178.42') # Each PI controller supports the qIDN() command which returns an # identification string with a trailing line feed character which # we "strip" away. print('connected: {}'.format(self.pidevice.qIDN().strip())) # Show the version info which is helpful for PI support when there # are any issues. if self.pidevice.HasqVER(): print('version info: {}'.format(self.pidevice.qVER().strip())) # allaxes = self.pidevice.qSAI_ALL() # return self.pidevice def move(self, target_pos): #pidevice.StopAll() #pidevice.SVO(pidevice.axes, [True] * len(pidevice.axes)) #pitools.waitontarget(pidevice, axes=pidevice.axes) # pitools.startup(pidevice, stages=STAGES, refmode=REFMODE) # Now we query the allowed motion range of all connected stages. # GCS commands often return an (ordered) dictionary with axes/channels # as "keys" and the according values as "values". # rangemin = list(pidevice.qTMN().values()) # rangemax = list(pidevice.qTMX().values()) # ranges = zip(rangemin, rangemax) targets = [target_pos] self.pidevice.MOV(self.pidevice.axes, targets) pitools.waitontarget(self.pidevice) time.sleep(0.3) positions = self.pidevice.qPOS(self.pidevice.axes) for axis in self.pidevice.axes: print('position of axis {} = {:.5f}'.format(axis, positions[axis])) def GetCurrentPos(self): # positions is a dictionary with key being axis name, here '1'. positions = self.pidevice.qPOS(self.pidevice.axes) return positions['1'] def CloseMotorConnection(self): self.pidevice.CloseConnection()
class DAQ_Move_PI(DAQ_Move_base): """ Plugin using the Pi wrapper shipped with new hardware. It is compatible with : DLLDEVICES = { 'PI_GCS2_DLL': ['C-413', 'C-663.11', 'C-863.11', 'C-867', 'C-877', 'C-884', 'C-885', 'C-887', 'C-891', 'E-517', 'E-518', 'E-545', 'E-709', 'E-712', 'E-723', 'E-725', 'E-727', 'E-753', 'E-754', 'E-755', 'E-852B0076', 'E-861', 'E-870', 'E-871', 'E-873', 'C-663.12'], 'C7XX_GCS_DLL': ['C-702', ], 'C843_GCS_DLL': ['C-843', ], 'C848_DLL': ['C-848', ], 'C880_DLL': ['C-880', ], 'E816_DLL': ['E-621', 'E-625', 'E-665', 'E-816', 'E816', ], 'E516_DLL': ['E-516', ], 'PI_Mercury_GCS_DLL': ['C-663.10', 'C-863.10', 'MERCURY', 'MERCURY_GCS1', ], 'PI_HydraPollux_GCS2_DLL': ['HYDRA', 'POLLUX', 'POLLUX2', 'POLLUXNT', ], 'E7XX_GCS_DLL': ['DIGITAL PIEZO CONTROLLER', 'E-710', 'E-761', ], 'HEX_GCS_DLL': ['HEXAPOD', 'HEXAPOD_GCS1', ], 'PI_G_GCS2_DLL': ['UNKNOWN', ], """ _controller_units = 'mm' # dependent on the stage type so to be updated accordingly using self.controller_units = new_unit GCS_path = "" GCS_paths = ["C:\\ProgramData\\PI\\GCSTranslator"] devices = [] #GCS_path = "C:\\Program Files (x86)\\PI\\GCSTranslator" dll_name = '' for GCS_path_tmp in GCS_paths: try: #check for installed dlls flag = False if '64' in platform.machine(): machine = "64" for dll_name_tmp in DLLDEVICES: for file in os.listdir(GCS_path_tmp): if dll_name_tmp in file and '.dll' in file and machine in file: dll_name = file flag = True if flag: break if flag: break gcs_device = GCSDevice(gcsdll=os.path.join(GCS_path_tmp, dll_name)) devices = gcs_device.EnumerateUSB() GCS_path = GCS_path_tmp except Exception as e: pass import serial.tools.list_ports as list_ports devices.extend([str(port) for port in list(list_ports.comports())]) is_multiaxes = False stage_names = [] params = [{ 'title': 'GCS2 library:', 'name': 'gcs_lib', 'type': 'browsepath', 'value': os.path.join(GCS_path_tmp, dll_name), 'filetype': True }, { 'title': 'Connection_type:', 'name': 'connect_type', 'type': 'list', 'value': 'USB', 'values': ['USB', 'TCP/IP', 'RS232'] }, { 'title': 'Devices:', 'name': 'devices', 'type': 'list', 'values': devices }, { 'title': 'Daisy Chain Options:', 'name': 'dc_options', 'type': 'group', 'children': [{ 'title': 'Use Daisy Chain:', 'name': 'is_daisy', 'type': 'bool', 'value': False }, { 'title': 'Is master?:', 'name': 'is_daisy_master', 'type': 'bool', 'value': False }, { 'title': 'Daisy Master Id:', 'name': 'daisy_id', 'type': 'int' }, { 'title': 'Daisy Devices:', 'name': 'daisy_devices', 'type': 'list' }, { 'title': 'Index in chain:', 'name': 'index_in_chain', 'type': 'int', 'enabled': True }] }, { 'title': 'Use Joystick:', 'name': 'use_joystick', 'type': 'bool', 'value': False }, { 'title': 'Closed loop?:', 'name': 'closed_loop', 'type': 'bool', 'value': True }, { 'title': 'Controller ID:', 'name': 'controller_id', 'type': 'str', 'value': '', 'readonly': True }, { 'title': 'Stage address:', 'name': 'axis_address', 'type': 'list' }, { 'title': 'MultiAxes:', 'name': 'multiaxes', 'type': 'group', 'visible': is_multiaxes, 'children': [ { 'title': 'is Multiaxes:', 'name': 'ismultiaxes', 'type': 'bool', 'value': is_multiaxes, 'default': False }, { 'title': 'Status:', 'name': 'multi_status', 'type': 'list', 'value': 'Master', 'values': ['Master', 'Slave'] }, { 'title': 'Axis:', 'name': 'axis', 'type': 'list', 'values': stage_names }, ] }] + comon_parameters def __init__(self, parent=None, params_state=None): super(DAQ_Move_PI, self).__init__(parent, params_state) self.settings.child(('epsilon')).setValue(0.01) self.is_referencing_function = True def commit_settings(self, param): """ | Activate any parameter changes on the PI_GCS2 hardware. | | Called after a param_tree_changed signal from DAQ_Move_main. =============== ================================ ======================== **Parameters** **Type** **Description** *param* instance of pyqtgraph Parameter The parameter to update =============== ================================ ======================== See Also -------- daq_utils.ThreadCommand, DAQ_Move_PI.enumerate_devices """ try: if param.name() == 'gcs_lib': try: self.controller.CloseConnection() except Exception as e: self.emit_status( ThreadCommand("Update_Status", [getLineInfo() + str(e), 'log'])) self.ini_device() elif param.name() == 'connect_type': self.enumerate_devices() elif param.name() == 'use_joystick': axes = self.controller.axes for ind, ax in enumerate(axes): try: if param.value(): res = self.controller.JAX(1, ind + 1, ax) res = self.controller.JON(ind + 1, True) else: self.controller.JON(ind + 1, False) except Exception as e: pass pass elif param.name() == 'axis_address': self.settings.child(('closed_loop')).setValue( self.controller.qSVO(param.value())[param.value()]) self.set_referencing( self.settings.child(('axis_address')).value()) elif param.name() == 'closed_loop': axe = self.settings.child(('axis_address')).value() if self.controller.qSVO(axe)[axe] != self.settings.child( ('closed_loop')).value(): self.controller.SVO(axe, param.value()) except Exception as e: self.emit_status( ThreadCommand("Update_Status", [getLineInfo() + str(e), 'log'])) def enumerate_devices(self): """ Enumerate PI_GCS2 devices from the connection type. Returns ------- string list The list of the devices port. See Also -------- daq_utils.ThreadCommand """ try: devices = [] if self.settings.child(('connect_type')).value() == 'USB': devices = self.controller.EnumerateUSB() elif self.settings.child(('connect_type')).value() == 'TCP/IP': devices = self.controller.EnumerateTCPIPDevices() elif self.settings.child(('connect_type')).value() == 'RS232': devices = [ str(port) for port in list(self.list_ports.comports()) ] self.settings.child(('devices')).setLimits(devices) return devices except Exception as e: self.emit_status( ThreadCommand("Update_Status", [getLineInfo() + str(e), 'log'])) def ini_device(self): """ load the correct dll given the chosen device See Also -------- DAQ_Move_base.close """ try: self.close() except: pass device = self.settings.child(('devices')).value() if self.settings.child( ('connect_type')).value() == 'TCP/IP' or self.settings.child( ('connect_type')).value() == 'RS232': dll_path_tot = self.settings.child(('gcs_lib')).value() else: # device = self.settings.child(('devices')).value().rsplit(' ') # dll = None # flag = False # for dll_tmp in DLLDEVICES: # for dev in DLLDEVICES[dll_tmp]: # for d in device: # if (d in dev or dev in d) and d != '': # res=self.check_dll_exist(dll_tmp) # if res[0]: # dll = res[1] # flag = True # break # if flag: # break # if flag: # break # # if dll is None: # raise Exception('No valid dll found for the given device') # dll_path = os.path.split(self.settings.child(('gcs_lib')).value())[0] # dll_path_tot = os.path.join(dll_path,dll) #dll_name = get_dll_name(self.settings.child(('devices')).value()) #dll_path_tot = get_dll_path(dll_name) #self.settings.child(('gcs_lib')).setValue(dll_path_tot) dll_path_tot = self.settings.child(('gcs_lib')).value() self.controller = GCSDevice(gcsdll=dll_path_tot) def check_dll_exist(self, dll_name): files = os.listdir( os.path.split(self.settings.child(('gcs_lib')).value())[0]) machine = '' if '64' in platform.machine(): machine = '64' res = (False, '') for file in files: if 'dll' in file and machine in file and dll_name in file: res = (True, file) return res def ini_stage(self, controller=None): """ Initialize the controller and stages (axes) with given parameters. =============== =========================================== ========================================================================================== **Parameters** **Type** **Description** *controller* instance of the specific controller object If defined this hardware will use it and will not initialize its own controller instance =============== =========================================== ========================================================================================== Returns ------- Easydict dictionnary containing keys: * *info* : string displaying various info * *controller*: instance of the controller object in order to control other axes without the need to init the same controller twice * *stage*: instance of the stage (axis or whatever) object * *initialized*: boolean indicating if initialization has been done corretly See Also -------- DAQ_Move_PI.set_referencing, daq_utils.ThreadCommand """ try: device = "" # initialize the stage and its controller status # controller is an object that may be passed to other instances of DAQ_Move_Mock in case # of one controller controlling multiaxes self.status.update( edict(info="", controller=None, initialized=False)) #check whether this stage is controlled by a multiaxe controller (to be defined for each plugin) # if mutliaxes then init the controller here if Master state otherwise use external controller if self.settings.child( 'multiaxes', 'ismultiaxes').value() and self.settings.child( 'multiaxes', 'multi_status').value() == "Slave": if controller is None: raise Exception( 'no controller has been defined externally while this axe is a slave one' ) else: self.controller = controller else: #Master stage self.ini_device( ) #create a fresh and new instance of GCS device (in case multiple instances of DAQ_MOVE_PI are opened) device = self.settings.child(('devices')).value() if not self.settings.child( 'dc_options', 'is_daisy').value(): #simple connection if self.settings.child(('connect_type')).value() == 'USB': self.controller.ConnectUSB(device) elif self.settings.child( ('connect_type')).value() == 'TCP/IP': self.controller.ConnectTCPIPByDescription(device) elif self.settings.child( ('connect_type')).value() == 'RS232': self.controller.ConnectRS232( int(device[3:]) ) #in this case device is a COM port, and one should use 1 for COM1 for instance else: #one use a daisy chain connection with a master device and slaves if self.settings.child( 'dc_options', 'is_daisy_master').value(): #init the master if self.settings.child( ('connect_type')).value() == 'USB': dev_ids = self.controller.OpenUSBDaisyChain(device) elif self.settings.child( ('connect_type')).value() == 'TCP/IP': dev_ids = self.controller.OpenTCPIPDaisyChain( device) elif self.settings.child( ('connect_type')).value() == 'RS232': dev_ids = self.controller.OpenRS232DaisyChain( int(device[3:]) ) #in this case device is a COM port, and one should use 1 for COM1 for instance self.settings.child('dc_options', 'daisy_devices').setLimits(dev_ids) self.settings.child('dc_options', 'daisy_id').setValue( self.controller.dcid) self.controller.ConnectDaisyChainDevice( self.settings.child('dc_options', 'index_in_chain').value() + 1, self.settings.child('dc_options', 'daisy_id').value()) self.settings.child( ('controller_id')).setValue(self.controller.qIDN()) self.settings.child( ('axis_address')).setLimits(self.controller.axes) self.set_referencing(self.controller.axes[0]) #check servo status: self.settings.child(('closed_loop')).setValue( self.controller.qSVO( self.controller.axes[0])[self.controller.axes[0]]) self.status.controller = self.controller self.status.info = "connected on device:{} /".format( device) + self.controller.qIDN() self.status.controller = self.controller self.status.initialized = True return self.status except Exception as e: self.emit_status( ThreadCommand('Update_Status', [getLineInfo() + str(e), 'log'])) self.status.info = getLineInfo() + str(e) self.status.initialized = False return self.status def is_referenced(self, axe): """ Return the referencement statement from the hardware device. ============== ========== ============================================ **Parameters** **Type** **Description** *axe* string Representing a connected axe on controller ============== ========== ============================================ Returns ------- ??? """ try: if self.controller.HasqFRF(): return self.controller.qFRF(axe)[axe] else: return False except: return False def set_referencing(self, axes): """ Set the referencement statement into the hardware device. ============== ============== =========================================== **Parameters** **Type** **Description** *axes* string list Representing connected axes on controller ============== ============== =========================================== """ try: if type(axes) is not list: axes = [axes] for axe in axes: #set referencing mode if type(axe) is str: if self.is_referenced(axe): if self.controller.HasRON(): self.controller.RON(axe, True) self.controller.FRF(axe) except Exception as e: self.emit_status( ThreadCommand('Update_Status', [ getLineInfo() + str(e) + " / Referencing not enabled with this dll", 'log' ])) def close(self): """ close the current instance of PI_GCS2 instrument. """ if not self.settings.child('dc_options', 'is_daisy').value(): #simple connection self.controller.CloseConnection() else: self.controller.CloseDaisyChain() def stop_motion(self): """ See Also -------- DAQ_Move_base.move_done """ self.controller.StopAll() self.move_done() def check_position(self): """ Get the current hardware position with scaling conversion of the PI_GCS2 instrument provided by get_position_with_scaling See Also -------- DAQ_Move_base.get_position_with_scaling, daq_utils.ThreadCommand """ self.set_referencing(self.settings.child(('axis_address')).value()) pos_dict = self.controller.qPOS( self.settings.child(('axis_address')).value()) pos = pos_dict[self.settings.child(('axis_address')).value()] pos = self.get_position_with_scaling(pos) self.current_position = pos self.emit_status(ThreadCommand('check_position', [pos])) return pos def move_Abs(self, position): """ Make the hardware absolute move of the PI_GCS2 instrument from the given position after thread command signal was received in DAQ_Move_main. =============== ========= ======================= **Parameters** **Type** **Description** *position* float The absolute position =============== ========= ======================= See Also -------- DAQ_Move_PI.set_referencing, DAQ_Move_base.set_position_with_scaling, DAQ_Move_base.poll_moving """ position = self.check_bound(position) self.target_position = position position = self.set_position_with_scaling(position) out = self.controller.MOV( self.settings.child(('axis_address')).value(), position) self.poll_moving() def move_Rel(self, position): """ Make the hardware relative move of the PI_GCS2 instrument from the given position after thread command signal was received in DAQ_Move_main. =============== ========= ======================= **Parameters** **Type** **Description** *position* float The absolute position =============== ========= ======================= See Also -------- DAQ_Move_base.set_position_with_scaling, DAQ_Move_PI.set_referencing, DAQ_Move_base.poll_moving """ position = self.check_bound(self.current_position + position) - self.current_position self.target_position = position + self.current_position position = self.set_position_relative_with_scaling(position) if self.controller.HasMVR(): out = self.controller.MVR( self.settings.child(('axis_address')).value(), position) else: self.move_Abs(self.target_position) self.poll_moving() def move_Home(self): """ See Also -------- DAQ_Move_PI.set_referencing, DAQ_Move_base.poll_moving """ self.set_referencing(self.settings.child(('axis_address')).value()) if self.controller.HasGOH(): self.controller.GOH(self.settings.child(('axis_address')).value()) elif self.controller.HasFRF(): self.controller.FRF(self.settings.child(('axis_address')).value()) else: self.move_Abs(0) self.poll_moving()
class PIPiezoController(Base, ConfocalScannerInterface): _modtype = 'PIPiezoController' _modclass = 'hardware' _ipaddress = ConfigOption("ipaddress", default='192.168.0.8', missing="error") _ipport = ConfigOption("ipport", default=50000, missing="warn") _stages = ConfigOption("stages", default=['S-330.8SH', 'S-330.8SH'], missing="error") _scanner_position_ranges = ConfigOption('scanner_position_ranges', missing='error') _x_scanner = ConfigOption("x_scanner", default='1', missing="warn") _y_scanner = ConfigOption("y_scanner", default='2', missing="warn") _z_scanner = ConfigOption("z_scanner", default=None) _controllername = ConfigOption("controllername", missing="error") _refmodes = None pidevice = None def on_activate(self): """ Initialise and activate the hardware module. @return: error code (0:OK, -1:error) """ try: self.pidevice = GCSDevice(self._controllername) self.pidevice.ConnectTCPIP(self._ipaddress, self._ipport) device_name = self.pidevice.qIDN().strip() self.log.info('PI controller {} connected'.format(device_name)) pitools.startup(self.pidevice, stages=self._stages) self._current_position = [0., 0., 0., 0.][0:len(self.get_scanner_axes())] return 0 except GCSError as error: self.log.error(error) return -1 def on_deactivate(self): """ Deinitialise and deactivate the hardware module. @return: error code (0:OK, -1:error) """ # self._set_servo_state(False) # If not shutdown, keep servo on to stay on target. try: self.pidevice.CloseConnection() self.log.info("PI Device has been closed connection !") return 0 except GCSError as error: self.log.error(error) return -1 def reset_hardware(self): """ Resets the hardware, so the connection is lost and other programs can access it. @return int: error code (0:OK, -1:error) """ try: self.pidevice.close() self.log.info("PI Device has been reset") return 0 except GCSError as error: self.log.error(error) return -1 def get_position_range(self): """ Returns the physical range of the scanner. @return float [4][2]: array of 4 ranges with an array containing lower and upper limit. The unit of the scan range is meters. """ return self._scanner_position_ranges def set_position_range(self, myrange=None): """ Sets the physical range of the scanner. @param float [4][2] myrange: array of 4 ranges with an array containing lower and upper limit. The unit of the scan range is meters. @return int: error code (0:OK, -1:error) """ if myrange is None: # myrange = [[0., 1.e-3], [0., 1.e-3], [0., 1e-4], [0., 1.]] myrange = self._scanner_position_ranges if not isinstance(myrange, ( frozenset, list, set, tuple, np.ndarray, )): self.log.error('Given range is no array type.') return -1 if len(myrange) != 4: self.log.error( 'Given range should have dimension 4, but has {0:d} instead.' ''.format(len(myrange))) return -1 for pos in myrange: if len(pos) != 2: self.log.error( 'Given range limit {1:d} should have dimension 2, but has {0:d} instead.' ''.format(len(pos), pos)) return -1 if pos[0] > pos[1]: self.log.error( 'Given range limit {0:d} has the wrong order.'.format(pos)) return -1 self._scanner_position_ranges = myrange return 0 def set_voltage_range(self, myrange=None): """ Sets the voltage range of the NI Card. @param float [2] myrange: array containing lower and upper limit @return int: error code (0:OK, -1:error) """ return 0 def get_scanner_axes(self): """ Find out how many axes the scanning device is using for confocal and their names. @return list(str): list of axis names Example: For 3D confocal microscopy in cartesian coordinates, ['x', 'y', 'z'] is a sensible value. For 2D, ['x', 'y'] would be typical. You could build a turntable microscope with ['r', 'phi', 'z']. Most callers of this function will only care about the number of axes, though. On error, return an empty list. """ return ['x', 'y', 'z'] def get_scanner_count_channels(self): """ Returns the list of channels that are recorded while scanning an image. @return list(str): channel names Most methods calling this might just care about the number of channels. """ pass def set_up_scanner_clock(self, clock_frequency=None, clock_channel=None): """ Configures the hardware clock of the NiDAQ card to give the timing. @param float clock_frequency: if defined, this sets the frequency of the clock @param str clock_channel: if defined, this is the physical channel of the clock @return int: error code (0:OK, -1:error) """ pass def set_up_scanner(self, counter_channels=None, sources=None, clock_channel=None, scanner_ao_channels=None): """ Configures the actual scanner with a given clock. @param str counter_channels: if defined, these are the physical conting devices @param str sources: if defined, these are the physical channels where the photons are to count from @param str clock_channel: if defined, this specifies the clock for the counter @param str scanner_ao_channels: if defined, this specifies the analoque output channels @return int: error code (0:OK, -1:error) """ pass def scanner_set_position(self, x=None, y=None, z=None, a=None): """Move stage to x, y, z, a (where a is the fourth voltage channel). @param float x: position in x-direction (m) @param float y: position in y-direction (m) @return int: error code (0:OK, -1:error) """ if self.module_state() == 'locked': self.log.error( 'Another scan_line is already running, close this one first.') return -1 if x is not None: if not (self._scanner_position_ranges[0][0] <= x <= self._scanner_position_ranges[0][1]): self.log.error( 'You want to set x out of range: {0:f}.'.format(x)) return -1 self._current_position[0] = np.float(x) if y is not None: if not (self._scanner_position_ranges[1][0] <= y <= self._scanner_position_ranges[1][1]): self.log.error( 'You want to set y out of range: {0:f}.'.format(y)) return -1 self._current_position[1] = np.float(y) if z is not None: if not (self._scanner_position_ranges[2][0] <= z <= self._scanner_position_ranges[2][1]): self.log.error( 'You want to set z out of range: {0:f}.'.format(z)) return -1 self._current_position[2] = np.float(z) if a is not None: if not (self._scanner_position_ranges[3][0] <= a <= self._scanner_position_ranges[3][1]): self.log.error( 'You want to set a out of range: {0:f}.'.format(a)) return -1 self._current_position[3] = np.float(a) try: if self._z_scanner is None: axes = [self._x_scanner, self._y_scanner] # Axes will start moving to the new positions if ALL given targets are within the allowed ranges and # ALL axes can move. All axes start moving simultaneously. # Servo must be enabled for all commanded axes prior to using this command. self.pidevice.MOV(axes=axes, values=[x * 1.e6, y * 1.e6]) else: axes = [self._x_scanner, self._y_scanner, self._z_scanner] # Axes will start moving to the new positions if ALL given targets are within the allowed ranges and # ALL axes can move. All axes start moving simultaneously. # Servo must be enabled for all commanded axes prior to using this command. self.pidevice.MOV(axes=axes, values=[x * 1.e6, y * 1.e6, z * 1.e6]) except Exception as e: return -1 # Takes longer but does more error checking # pitools.waitontarget(self.pidevice, axes=axes) # Check if axes have reached the target. while not all(list(self.pidevice.qONT(axes).values())): time.sleep(0.04) return 0 def get_scanner_position(self): """ Get the current position of the scanner hardware. @return float[n]: current position in (x, y, z, a). """ position = self.pidevice.qPOS() if self._z_scanner is None: return [position['1'] * 1e-6, position['2'] * 1e-6, 0., 0.] else: return [ position['1'] * 1e-6, position['2'] * 1e-6, position['3'] * 1e6, 0. ] def scan_line(self, line_path=None, pixel_clock=False): """ Scans a line and returns the counts on that line. @param float[k][n] line_path: array k of n-part tuples defining the pixel positions @param bool pixel_clock: whether we need to output a pixel clock for this line @return float[k][m]: the photon counts per second for k pixels with m channels """ pass def close_scanner(self): """ Closes the scanner and cleans up afterwards. @return int: error code (0:OK, -1:error) """ self.pidevice.HLT() return 0 def close_scanner_clock(self, power=0): """ Closes the clock and cleans up afterwards. @return int: error code (0:OK, -1:error) """ pass
class GalvoObjectivePiezo(Base, ConfocalScannerInterface): _modtype = 'GalvoObjectivePiezo' _modclass = 'hardware.interfuse' # Connectors galvo = Connector(interface='ConfocalScannerInterface') # Piezo _piezoSerial = ConfigOption('piezo_serial', missing='error') _piezoModel = 'E709' _piezoDllPath = os.path.abspath( os.path.join(os.path.dirname(__file__), "GCSTranslator", "PI_GCS2_DLL_x64.dll")) _piezo_voltage_ranges = ConfigOption('piezo_voltage_ranges', missing='error') _piezo_position_ranges = ConfigOption('piezo_position_ranges', missing='error') def on_activate(self): """Initialisation performed during activation of the module.""" self._galvo = self.galvo() self._piezo = GCSDevice(devname=self._piezoModel, gcsdll=self._piezoDllPath) self._piezo.ConnectUSB(self._piezoSerial) self._piezo_axis = self._piezo.axes[0] def on_deactivate(self): """Deactivate the module.""" self.reset_hardware() def reset_hardware(self): """Resets the hardware, so the connection is lost and other programs can access it. @return int: error code (0:OK, -1:error) """ galvoReturn = self._galvo.reset_hardware() self._piezo.CloseConnection() return galvoReturn def get_position_range(self): """Returns the physical range of the scanner. @return list(float): 3x2 list with low and high limits for x, y, z """ galvoRanges = self._galvo.get_position_range() piezoRanges = self._piezo_position_ranges return galvoRanges + piezoRanges def set_position_range(self, posRange=None): """Sets the physical range of the scanner. @param list(float) posRange: 3x2 list with low and high limits for x, y, z @return int: error code (0:OK, -1:error) """ galvoReturn = self._galvo.set_position_range(posRange[0:2][:]) self._piezo_position_ranges = posRange[2:3][:] return galvoReturn def set_voltage_range(self, volRange=None): """Sets the voltage range of the scanner. @param list(float) volRange: 3x2 list with low and high limits for x, y, z @return int: error code (0:OK, -1:error) """ galvoReturn = self._galvo.set_voltage_range(volRange[0:2][:]) self._piezo_voltage_ranges = volRange[2:3][:] return galvoReturn def get_scanner_axes(self): """Return the scan axes. @return list(str): list of axis names """ return ['x', 'y', 'z'] def get_scanner_count_channels(self): """Returns the list of counter channels for the scan. @return list(str): channel names """ return self._galvo.get_scanner_count_channels() def set_up_scanner_clock(self, clock_frequency=None, clock_channel=None): """Configures the hardware clock of the NiDAQ card to give the timing. @param float clock_frequency: if defined, this sets the frequency of the clock @param str clock_channel: if defined, this is the physical channel of the clock @return int: error code (0:OK, -1:error) """ return self._galvo.set_up_scanner_clock(clock_frequency, clock_channel) def set_up_scanner(self, counter_channels=None, sources=None, clock_channel=None, scanner_ao_channels=None): """Configures the actual scanner with a given clock. @param str counter_channels: if defined, these are the physical counting devices @param str sources: if defined, these are the physical channels where the photons are to count from @param str clock_channel: if defined, this specifies the clock for the counter @param str scanner_ao_channels: if defined, this specifies the analog output channels @return int: error code (0:OK, -1:error) """ return self._galvo.set_up_scanner(counter_channels=None, sources=None, clock_channel=None, scanner_ao_channels=None) def scanner_set_position(self, x, y, z): """Move stage to x, y, z, a (where a is the fourth voltage channel). @param float x: postion in x-direction (volts) @param float y: postion in y-direction (volts) @param float z: postion in z-direction (volts) @return int: error code (0:OK, -1:error) """ # Set the galvo galvoReturn = self._galvo.scanner_set_position(x, y) # Set the piezo self._set_piezo_position(z) return galvoReturn def get_scanner_position(self): """Get the current position of the scanner hardware. @return list(float): current position in [x, y, z] in meters """ galvoPos = self._galvo.get_scanner_position() piezoAxis = self._piezo.axes[0] piezoPos = self._piezo.qPOS()[piezoAxis] qudi_piezo_pos = self._piezo_pi_position_to_qudi_position(piezoPos) return galvoPos + [qudi_piezo_pos] def scan_line(self, line_path=None, pixel_clock=False): """Scans a line and returns the counts on that line. @param numpy.ndarray(float) line_path: k x n array defining the pixel positions, k is number of pixels, n is number of axes @param bool pixel_clock: whether we need to output a pixel clock for this line @return numpy.ndarray(float): k x m, the photon counts per second for k pixels with m channels """ if line_path is None: self.log.error('line_path must be specified.') return -1 # Figure out what axes we're scanning. # We support xy scans and z scans. # Assume we're scanning in x and y rather than z zScan = False # Get the values for each axis galvo_path = line_path[0:2, :] xVals = galvo_path[0, :].tolist() yVals = galvo_path[1, :].tolist() piezo_path = line_path[2, :] zVals = piezo_path.tolist() # Get the first value for each axis firstX = xVals[0] firstY = yVals[0] firstZ = zVals[0] # Check if the scan is constant along a given axis. If all the # values along the axis are the same, then it is constant. constantX = xVals.count(firstX) == len(xVals) constantY = yVals.count(firstY) == len(yVals) constantZ = zVals.count(firstZ) == len(zVals) # If this looks like a z scan, then x and y better be constant if not constantZ: if constantX and constantY: zScan = True else: self.log.error('This module only supports scans in xy or z.') return -1 if zScan: counts = self._scan_z_line(firstX, firstY, zVals, pixel_clock) else: counts = self._galvo.scan_line(galvo_path, pixel_clock) return counts def _scan_z_line(self, x, y, z_path, pixel_clock=False): """Scans a line in z and returns the counts along the line. @param numpy.ndarray(float) line_path: k x n list defining the pixel positions, k is number of pixels, n is number of axes @param bool pixel_clock: whether we need to output a pixel clock for this line @return numpy.ndarray(float): k x m, the photon counts per second for k pixels with m channels """ # Based on the sample clock, the AO write task expects more than one # sample per channel. We only really want to write one sample # per channel, but since all the samples are the same it doesn't # matter if we write two. galvo_vals = numpy.vstack(([x, x], [y, y])) for z_index in range(len(z_path)): z_pos = z_path[z_index] self._set_piezo_position(z_pos) # Take just the first sample since we actually collected two. new_count = [self._galvo.scan_line(galvo_vals, pixel_clock)[0, :]] if z_index == 0: counts = new_count else: counts = numpy.vstack((counts, new_count)) def close_scanner(self): """Closes the scanner and cleans up afterwards. @return int: error code (0:OK, -1:error) """ galvoReturn = self._galvo.close_scanner() return galvoReturn def close_scanner_clock(self): """Closes the clock and cleans up afterwards. @return int: error code (0:OK, -1:error) """ return self._galvo.close_scanner_clock() def _set_piezo_position(self, position): """Write a z voltage to the piezo. @param float position: The position according to qudi (meters) """ # The piezo range is 0-100 um according to PI, # but qudi expects ranges centered about 0 voltage = self._piezo_qudi_position_to_pi_voltage(position) piezo_vol_min = self._piezo_position_ranges[0] piezo_vol_max = self._piezo_position_ranges[1] if (voltage < piezo_vol_min) or (voltage > piezo_vol_max): return # Turn off closed loop feedback self._piezo.SVO(self._piezo_axis, False) # Write the value self._piezo.SVA(self._piezo_axis, voltage) def _piezo_qudi_position_to_pi_voltage(self, qudi_position): """Map the qudi position to the instrument voltage. @param float position: The position according to qudi (meters) @return float: The corresponding physical voltage of the piezo """ # Qudi passes the position in meters. micron_qudi_position = qudi_position * (10**6) # The piezo range is 0-100 um according to PI, # but qudi expects ranges centered about 0. # Besides this offset, the micron: voltage mapping is 1:1. voltage = micron_qudi_position + 50 return voltage def _piezo_pi_position_to_qudi_position(self, pi_position): """Map the pi position to the qudi position. @param float position: The position according to qudi (um) @return float: The corresponding position in qudi (meters) """ # The piezo range is 0-100 um according to PI, # but qudi expects ranges centered about 0 qudi_micron_position = pi_position - 50 # Qudi expects meters qudi_position = qudi_micron_position * (10**-6) return qudi_position
class PIFOCV2(Base, MotorInterface): """ Class representing the PIFOC z axis positioning stage Example config for copy-paste: pifoc: module.Class: 'motor.motor_pifoc.PIFOC' controllername: 'E816' serialnumber: '110059675' pos_min: 0 # in um pos_max: 100 # in um max_step: 5 # in um """ _controllername = ConfigOption('controllername', missing='error') # 'E-816' _serialnum = ConfigOption('serialnumber', missing='error') # 110059675' axes = None pidevice = None # private attributes read from config or from hardware and used for the constraints settings _axis_label = None _axis_ID = None _pos_min = ConfigOption('pos_min', 0, missing='warn') # in um _pos_max = ConfigOption('pos_max', 100, missing='warn') # in um _max_step = ConfigOption('max_step', 5, missing='warn') # in um # _vel_min = ConfigOption('vel_min', ??, missing='warn') # _vel_max = ConfigOption('vel_max', ??, missing='warn') def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self.pidevice = GCSDevice(self._controllername) def on_activate(self): """ Initialization """ # open the connection # with GCSDevice(self._controllername) as pidevice: with self.pidevice: # is it possible to use this as context manager and also to call the dll functions therein ?? self.pidevice.ConnectUSB(serialnum=self._serialnum) self.log.info('connected: {}'.format(self.pidevice.qIDN().strip())) self.axes = self.pidevice.axes # this returns a list self.log.info('available axes: {}'.format(self.axes)) # remove log entries later on self._axis_label = self.axes[0] self.log.info(self._axis_label) self._axis_ID = self.pidevice.GetID() self.log.info(self._axis_ID) def on_deactivate(self): """ """ with self.pidevice: self.pidevice.CloseConnection() def get_constraints(self): """ Retrieve the hardware constrains from the motor device. @return dict constraints """ constraints = {} axis0 = {'label': self._axis_label, 'ID': self._axis_ID, 'unit': 'um', 'pos_min': self._pos_min, 'pos_max': self._pos_max, 'max_step': self._max_step} # 'vel_min': self._vel_min, # 'vel_max': self._vel_max} # assign the parameter container for x to a name which will identify it constraints[axis0['label']] = axis0 return constraints def move_rel(self, param_dict): """ Moves stage in given direction (relative movement) @param dict param_dict: Dictionary with axis name and step (in um units) as key - value pairs @return bool: error code (True: ok, False: not ok) or modify to return position ?? """ constraints = self.get_constraints() position = self.get_pos() # returns an OrderedDict with one entry (one axis) err = False for i in range(len(param_dict)): # potentially a list of axes. real case: list of length 1 because pifoc only has one axis # in case a longer param_dict is given, only the entry with the right axis label will be considered (axis, step) = param_dict.popitem() if axis in self.axes: cur_pos = position[axis] # returns just the float value of the axis if abs(step) <= constraints[axis]['max_step'] and constraints[axis]['pos_min'] <= cur_pos + step <= constraints[axis]['pos_max']: with self.pidevice: self.pidevice.ConnectUSB(serialnum=self._serialnum) self.pidevice.MVR(axis, step) err = True if not err: error_code = self.pidevice.GetError() error_msg = self.pidevice.TranslateError(error_code) self.log.warning(f'Could not move axis {axis} by {step}: {error_msg}.') # it might be needed to print a pertinent error message in case the movement was not performed because the conditions above were not met, # that is, if the error does not come from the controller but due to the coded conditions return err # note that there is a different function available for simultaneous multi axes movement. def move_abs(self, param_dict): """ Moves stage to absolute position (absolute movement) @param dict param_dict: Dictionary with axis name and target position (in um units) as key - value pairs @return bool: error code (True: ok, False: error) - or modify to return the new position ?? """ constraints = self.get_constraints() err = False for i in range(len( param_dict)): # potentially a list of axes. real case: list of length 1 because pifoc only has one axis (axis, target) = param_dict.popitem() # self.log.info(f'axis: {axis}; target: {target}') if axis in self.axes and constraints[axis]['pos_min'] <= target <= constraints[axis][ 'pos_max']: # control if the right axis is addressed with self.pidevice: self.pidevice.MOV(axis, target) # MOV has no return value err = True if not err: error_code = self.pidevice.GetError() error_msg = self.pidevice.TranslateError(error_code) self.log.warning(f'Could not move axis {axis} to {target} : {error_msg}.') # it might be needed to print a pertinent error message in case the movement was not performed because the conditions above were not met, # that is, if the error does not come from the controller but due to the coded conditions return err # note that there is a different function available for simultaneous multi axes movement. (MVE) def abort(self): """ Stops movement of the stage @return bool: error code (True: ok, False: error) """ # err = self.pidevice.HLT() # needs eventually controller ID as argument # HLT or STP ? # errorcode = self.pidevice.GetError() # errormsg = self.pidevice.TranslateError(errorcode) # if not err: # self.log.warning(f'Error calling abort: {errormsg}') # return err pass def get_pos(self, param_list=None): """ Gets current position of the controller @param list param_list: optional, if a specific position of an axis is desired, then the labels of the needed axis should be passed in the param_list. If nothing is passed, then from each axis the position is asked. @return OrderedDict: with keys being the axis labels and item the current position. update docstring after tests ! """ with self.pidevice: self.pidevice.ConnectUSB(serialnum=self._serialnum) pos = self.pidevice.qPOS(self.axes) # this returns an OrderedDict return pos def get_status(self, param_list=None): """ Get the status of the position @param list param_list: optional, if a specific status of an axis is desired, then the labels of the needed axis should be passed in the param_list. If nothing is passed, then from each axis the status is asked. @return bool err """ with self.pidevice: err = self.pidevice.IsControllerReady() return err def calibrate(self, param_list=None): """ Calibrates the stage. @param dict param_list: param_list: optional, if a specific calibration of an axis is desired, then the labels of the needed axis should be passed in the param_list. If nothing is passed, then all connected axis will be calibrated. @return int: error code (0:OK, -1:error) After calibration the stage moves to home position which will be the zero point for the passed axis. The calibration procedure will be different for each stage. """ pass def get_velocity(self, param_list=None): """ Gets the current velocity for all connected axes. @param dict param_list: optional, if a specific velocity of an axis is desired, then the labels of the needed axis should be passed as the param_list. If nothing is passed, then from each axis the velocity is asked. @return dict : with the axis label as key and the velocity as item. """ # vel = self.pidevice.q???(self.axis) # test which query is the right one here.. # do some formatting if needed # return vel self.log.info('get_velocity not available') def set_velocity(self, param_dict): """ Write new value for velocity. @param dict param_dict: dictionary, which passes all the relevant parameters, which should be changed. Usage: {'axis_label': <the-velocity-value>}. 'axis_label' must correspond to a label given to one of the axis. @return int: error code (0:OK, -1:error) """ self.log.info('set velocity not available') # should it be possible to set the velocity else just send a message that this function is not available for the controller # not on the interface def wait_for_idle(self): """ first draft for a wait for idle function checks if on target problem: seems to return too fast, so that the position is not yet the right one.. although this function is actually meant to not wait until the target position is reached.. it works when the two log entries are activated. this seems to take some additional time, allowing the stage to reach the target """ # self.log.info('old position: {}'.format(self.get_pos())) pitools.waitontarget(self.pidevice) # self.log.info('new position: {}'.format(self.get_pos())) return
class PIMotorStage(Base, MotorInterface): """ Class representing the PI 3 axis positioning motor stage Example config for copy-paste: pi_stage: module.Class: 'motor.motor_pi_3axis_stage.PIMotorStage' serialnumber_master: '0019550121' first_axis_controllername: 'C-863' second_axis_controllername: 'C-863' third_axis_controllername: 'C-863' first_axis_label: 'x' second_axis_label: 'y' third_axis_label: 'z' first_axis_daisychain_id: 2 # number of the device in the daisy chain (sorted by increasing serial number of the controller) second_axis_daisychain_id: 3 third_axis_daisychain_id: 1 """ _serialnum_master = ConfigOption('serialnumber_master', missing='error') _first_axis_controllername = ConfigOption('first_axis_controllername', missing='error') _second_axis_controllername = ConfigOption('second_axis_controllername', missing='error') _third_axis_controllername = ConfigOption('third_axis_controllername', missing='error') _first_axis_label = ConfigOption('first_axis_label', missing='error') _second_axis_label = ConfigOption('second_axis_label', missing='error') _third_axis_label = ConfigOption('third_axis_label', missing='error') _first_axis_daisychain_id = ConfigOption('first_axis_daisychain_id', missing='error') _second_axis_daisychain_id = ConfigOption('second_axis_daisychain_id', missing='error') _third_axis_daisychain_id = ConfigOption('third_axis_daisychain_id', missing='error') def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) # def __init__(self, *args, **kwargs): # super().__init__() def on_activate(self): try: self.first_axis_label = self._first_axis_label self.second_axis_label = self._second_axis_label self.third_axis_label = self._third_axis_label # open the daisy chain connection self.pidevice_1st_axis = GCSDevice( self._first_axis_controllername ) # 1st axis controller # master device self.pidevice_2nd_axis = GCSDevice( self._second_axis_controllername) # 2nd axis controller self.pidevice_3rd_axis = GCSDevice( self._third_axis_controllername) # 3rd axis controller self.pidevice_1st_axis.OpenUSBDaisyChain( description=self._serialnum_master) self.daisychainid = self.pidevice_1st_axis.dcid print(f'Daisychainid: {self.daisychainid}') # controllers are ordered with increasing serial number in the daisy chain # this is why z is connected as first # do we need to programmatically sort by nth_axis_daisychain id ?? self.pidevice_3rd_axis.ConnectDaisyChainDevice( self._third_axis_daisychain_id, self.daisychainid) # SN 019550119 self.pidevice_1st_axis.ConnectDaisyChainDevice( self._first_axis_daisychain_id, self.daisychainid) # SN 019550121 self.pidevice_2nd_axis.ConnectDaisyChainDevice( self._second_axis_daisychain_id, self.daisychainid) # SN 019550124 print('\n{}:\n{}'.format( self.pidevice_1st_axis.GetInterfaceDescription(), self.pidevice_1st_axis.qIDN())) print('\n{}:\n{}'.format( self.pidevice_2nd_axis.GetInterfaceDescription(), self.pidevice_2nd_axis.qIDN())) print('\n{}:\n{}'.format( self.pidevice_3rd_axis.GetInterfaceDescription(), self.pidevice_3rd_axis.qIDN())) # initialization of all axes print('Initializing PI stage ...') # servo on pitools.startup(self.pidevice_1st_axis) pitools.startup(self.pidevice_2nd_axis) pitools.startup(self.pidevice_3rd_axis) print('Please wait ... ') # the IDs are needed to address the axes in the dll functions self.first_axis_ID = self.pidevice_1st_axis.axes[ 0] # each controller is connected to one stage; so just take the first element # print(self.first_axis_ID) self.second_axis_ID = self.pidevice_2nd_axis.axes[0] # print(self.second_axis_ID) self.third_axis_ID = self.pidevice_3rd_axis.axes[0] # print(self.third_axis_ID) self.calibrate() # RON: # FNL: fast move to negative limit # self.pidevice_1st_axis.RON(self.first_axis_ID, values=1) # self.pidevice_1st_axis.FNL(self.first_axis_ID) # self.pidevice_2nd_axis.RON(self.second_axis_ID, values=1) # self.pidevice_2nd_axis.FNL(self.second_axis_ID) # self.pidevice_3rd_axis.RON(self.third_axis_ID, values=1) # self.pidevice_3rd_axis.FNL(self.third_axis_ID) # pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID) # pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID) # pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID) except Exception as e: self.log.error( f'Physik Instrumente 3-axes stage: Connection failed: {e}. Check if device is switched on.' ) def on_deactivate(self): """ Required deactivation steps """ # set position (0, 0, 0) # first move z to default position and wait until reached self.pidevice_3rd_axis.MOV(self.third_axis_ID, 0.0) pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID) # when z is at safety position, xy move can be done self.pidevice_1st_axis.MOV(self.first_axis_ID, 0.0) self.pidevice_2nd_axis.MOV(self.second_axis_ID, 0.0) pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID) pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID) self.pidevice_1st_axis.CloseDaisyChain( ) # check if connection always done with controller corresponing to 1st axis self.pidevice_1st_axis.CloseConnection() def get_constraints(self): """ Retrieve the hardware constrains from the motor device. @return dict: dict with constraints for the motor hardware Provides all the constraints for each axis of a motorized stage (like total travel distance, velocity, ...) Each axis has its own dictionary, where the label is used as the identifier throughout the whole module. The dictionaries for each axis are again grouped together in a constraints dictionary in the form {'<label_axis0>': axis0 } where axis0 is again a dict with the possible values defined below. The possible keys in the constraint are defined here in the interface file. If the hardware does not support the values for the constraints, then insert just None. If you are not sure about the meaning, look in other hardware files to get an impression. """ constraints = {} # retrieve information from hardware pos_min_x = self.pidevice_1st_axis.qTMN()[self.first_axis_ID] pos_max_x = self.pidevice_1st_axis.qTMX()[self.first_axis_ID] vel_min_x = 0 # self.pidevice_c863_x.q vel_max_x = 20 # self.pidevice_c863_x.q # need to find the command axis0 = {} axis0[ 'label'] = self.first_axis_label # it is very crucial that this label coincides with the label set in the config. axis0['unit'] = 'm' # the SI units, only possible m or degree axis0['ramp'] = None # do we need this ? axis0['pos_min'] = pos_min_x axis0['pos_max'] = pos_max_x axis0['pos_step'] = pos_max_x axis0['vel_min'] = vel_min_x axis0['vel_max'] = vel_max_x axis0['vel_step'] = 0.01 # can this also be queried ? axis0['acc_min'] = None # do we need this ? axis0['acc_max'] = None axis0['acc_step'] = None # retrieve information from hardware pos_min_y = self.pidevice_2nd_axis.qTMN()[self.second_axis_ID] pos_max_y = self.pidevice_2nd_axis.qTMX()[self.second_axis_ID] vel_min_y = 0 # self.pidevice_c863_y.q vel_max_y = 20 # self.pidevice_c863_y.q # need to find the command axis1 = {} axis1[ 'label'] = self.second_axis_label # it is very crucial that this label coincides with the label set in the config. axis1['unit'] = 'm' # the SI units, only possible m or degree axis1['ramp'] = None # do we need this ? axis1['pos_min'] = pos_min_y axis1['pos_max'] = pos_max_y axis1[ 'pos_step'] = pos_max_y # allow to go directly from low limit to maximum axis1['vel_min'] = vel_min_y axis1['vel_max'] = vel_max_y axis1['vel_step'] = 0.01 # can this also be queried ? axis1['acc_min'] = None # do we need this ? axis1['acc_max'] = None axis1['acc_step'] = None # retrieve information from hardware pos_min_z = self.pidevice_3rd_axis.qTMN()[self.third_axis_ID] pos_max_z = self.pidevice_3rd_axis.qTMX()[self.third_axis_ID] vel_min_z = 0 # self.pidevice_c863_z.q vel_max_z = 20 # self.pidevice_c863_z.q # need to find the command axis2 = {} axis2[ 'label'] = self.third_axis_label # it is very crucial that this label coincides with the label set in the config. axis2['unit'] = 'm' # the SI units, only possible m or degree axis2['ramp'] = None # do we need this ? axis2['pos_min'] = pos_min_z axis2['pos_max'] = pos_max_z axis2[ 'pos_step'] = pos_max_z # can this also be queried from the hardware ? if not just set a reasonable value axis2['vel_min'] = vel_min_z axis2['vel_max'] = vel_max_z axis2['vel_step'] = 0.01 # can this also be queried ? axis2['acc_min'] = None # do we need this ? axis2['acc_max'] = None axis2['acc_step'] = None # assign the parameter container for each axis to a name which will identify it constraints[axis0['label']] = axis0 constraints[axis1['label']] = axis1 constraints[axis2['label']] = axis2 return constraints def move_rel(self, param_dict): """ Moves stage in given direction (relative movement) @param dict param_dict: dictionary, which passes all the relevant parameters, which should be changed. Usage: {'axis_label': <the-abs-pos-value>}. 'axis_label' must correspond to a label given to one of the axis. A smart idea would be to ask the position after the movement. @return int: error code (0:OK, -1:error) """ constraints = self.get_constraints() cur_pos = self.get_pos() for key, value in param_dict.items( ): # param_dict has the format {'x': 20, 'y': 0, 'z': 10} for example if key == self.first_axis_label: if value <= constraints[ self.first_axis_label]['pos_step'] and constraints[ self.first_axis_label]['pos_min'] <= cur_pos[ self.first_axis_label] + value <= constraints[ self.first_axis_label]['pos_max']: self.pidevice_1st_axis.MVR(self.first_axis_ID, value) # pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID) else: print( 'Target value not in allowed range. Relative movement not done.' ) elif key == self.second_axis_label: if value <= constraints[ self.second_axis_label]['pos_step'] and constraints[ self.second_axis_label]['pos_min'] <= cur_pos[ self.second_axis_label] + value <= constraints[ self.second_axis_label]['pos_max']: self.pidevice_2nd_axis.MVR(self.second_axis_ID, value) # pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID) else: print( 'Target value not in allowed range. Relative movement not done.' ) elif key == self.third_axis_label: if value <= constraints[ self.third_axis_label]['pos_step'] and constraints[ self.third_axis_label]['pos_min'] <= cur_pos[ self.third_axis_label] + value <= constraints[ self.third_axis_label]['pos_max']: self.pidevice_3rd_axis.MVR(self.third_axis_ID, value) # pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID) else: print( 'Target value not in allowed range. Relative movement not done.' ) else: print('Given axis not available.') # handle the return statement def move_abs(self, param_dict): """ Moves stage to absolute position (absolute movement) @param dict param_dict: dictionary, which passes all the relevant parameters, which should be changed. Usage: {'axis_label': <the-abs-pos-value>}. 'axis_label' must correspond to a label given to one of the axis. @return int: error code (0:OK, -1:error) """ constraints = self.get_constraints() for key, value in param_dict.items(): if key == self.first_axis_label: if constraints[self.first_axis_label][ 'pos_min'] <= value <= constraints[ self.first_axis_label]['pos_max']: self.pidevice_1st_axis.MOV(self.first_axis_ID, value) # pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID) else: print( 'Target value not in allowed range. Absolute movement not done.' ) elif key == self.second_axis_label: if constraints[self.second_axis_label][ 'pos_min'] <= value <= constraints[ self.second_axis_label]['pos_max']: self.pidevice_2nd_axis.MOV(self.second_axis_ID, value) # pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID) else: print( 'Target value not in allowed range. Absolute movement not done.' ) elif key == self.third_axis_label: if constraints[self.third_axis_label][ 'pos_min'] <= value <= constraints[ self.third_axis_label]['pos_max']: self.pidevice_3rd_axis.MOV(self.third_axis_ID, value) # pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID) else: print( 'Target value not in allowed range. Absolute movement not done.' ) else: print('Given axis not available.') # handle the return statement def abort(self): """ Stops movement of the stage @return int: error code (0:OK, -1:error) """ self.pidevice_1st_axis.HLT( noraise=True) # noraise option silences GCSerror 10 self.pidevice_2nd_axis.HLT(noraise=True) self.pidevice_3rd_axis.HLT(noraise=True) print('Movement aborted.') # handle return value def get_pos(self, param_list=None): """ Gets current position of the stage arms @param list param_list: optional, if a specific position of an axis is desired, then the labels of the needed axis should be passed in the param_list. If nothing is passed, then from each axis the position is asked. @return dict: with keys being the axis labels and item the current position. """ # # if stage is moving, wait until movement done before reading position # pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID) # pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID) # pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID) if not param_list: x_pos = self.pidevice_1st_axis.qPOS( )[self. first_axis_ID] # qPOS returns OrderedDict, we just need the value for the single axis y_pos = self.pidevice_2nd_axis.qPOS()[self.second_axis_ID] z_pos = self.pidevice_3rd_axis.qPOS()[self.third_axis_ID] positions = [x_pos, y_pos, z_pos] keys = [ self.first_axis_label, self.second_axis_label, self.third_axis_label ] pos_dict = dict(zip(keys, positions)) return pos_dict else: pos_dict = {} for item in param_list: if item == self.first_axis_label: x_pos = self.pidevice_1st_axis.qPOS()[self.first_axis_ID] pos_dict[item] = x_pos elif item == self.second_axis_label: y_pos = self.pidevice_2nd_axis.qPOS()[self.second_axis_ID] pos_dict[item] = y_pos elif item == self.third_axis_label: z_pos = self.pidevice_3rd_axis.qPOS()[self.third_axis_ID] pos_dict[item] = z_pos else: print('Given axis not available.') return pos_dict # IsControllerReady does eventually not give the right information. Better replace by an information if stage moving / not moving # use qONT query ? on target ? def get_status(self, param_list=None): """ Get the status of the position @param list param_list: optional, if a specific status of an axis is desired, then the labels of the needed axis should be passed in the param_list. If nothing is passed, then from each axis the status is asked. @return dict: with the axis label as key and the on target status as value. """ if not param_list: x_status = self.pidevice_1st_axis.qONT()[self.first_axis_ID] y_status = self.pidevice_2nd_axis.qONT()[self.second_axis_ID] z_status = self.pidevice_3rd_axis.qONT()[self.third_axis_ID] on_target = [x_status, y_status, z_status] keys = [ self.first_axis_label, self.second_axis_label, self.third_axis_label ] status_dict = dict(zip(keys, on_target)) return status_dict else: status_dict = {} for item in param_list: if item == self.first_axis_label: x_status = self.pidevice_1st_axis.qONT()[ self.first_axis_ID] status_dict[item] = x_status elif item == self.second_axis_label: y_status = self.pidevice_2nd_axis.qONT()[ self.second_axis_ID] status_dict[item] = y_status elif item == self.third_axis_label: z_status = self.pidevice_3rd_axis.qONT()[ self.third_axis_ID] status_dict[item] = z_status else: print('Given axis not available.') return status_dict def calibrate(self, param_list=None): """ Calibrates the stage. @param dict param_list: param_list: optional, if a specific calibration of an axis is desired, then the labels of the needed axis should be passed in the param_list. If nothing is passed, then all connected axis will be calibrated. @return int: error code (0:OK, -1:error) After calibration the stage moves to home position which will be the zero point for the passed axis. The calibration procedure will be different for each stage. """ if not param_list: # 3rd axis is typically z. Calibrate and move first z to negative limit self.pidevice_3rd_axis.RON(self.third_axis_ID, values=1) self.pidevice_3rd_axis.FNL(self.third_axis_ID) pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID) self.pidevice_1st_axis.RON(self.first_axis_ID, values=1) self.pidevice_1st_axis.FNL(self.first_axis_ID) self.pidevice_2nd_axis.RON(self.second_axis_ID, values=1) self.pidevice_2nd_axis.FNL(self.second_axis_ID) pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID) pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID) else: for item in param_list: if item == self.first_axis_label: self.pidevice_1st_axis.RON(self.first_axis_ID, values=1) self.pidevice_1st_axis.FNL(self.first_axis_ID) pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID) elif item == self.second_axis_label: self.pidevice_2nd_axis.RON(self.second_axis_ID, values=1) self.pidevice_2nd_axis.FNL(self.second_axis_ID) pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID) elif item == self.third_axis_label: self.pidevice_3rd_axis.RON(self.third_axis_ID, values=1) self.pidevice_3rd_axis.FNL(self.third_axis_ID) pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID) else: print('Given axis not available.') def get_velocity(self, param_list=None): """ Gets the current velocity for all connected axes. @param dict param_list: optional, if a specific velocity of an axis is desired, then the labels of the needed axis should be passed as the param_list. If nothing is passed, then from each axis the velocity is asked. @return dict : with the axis label as key and the velocity as item. """ if not param_list: x_vel = self.pidevice_1st_axis.qVEL( )[self. first_axis_ID] # qVEL returns OrderedDict, we just need the value for the single axis y_vel = self.pidevice_2nd_axis.qVEL()[self.second_axis_ID] z_vel = self.pidevice_3rd_axis.qVEL()[self.third_axis_ID] velocity = [x_vel, y_vel, z_vel] keys = [ self.first_axis_label, self.second_axis_label, self.third_axis_label ] vel_dict = dict(zip(keys, velocity)) return vel_dict else: vel_dict = {} for item in param_list: if item == self.first_axis_label: x_vel = self.pidevice_1st_axis.qVEL()[self.first_axis_ID] vel_dict[item] = x_vel elif item == self.second_axis_label: y_vel = self.pidevice_2nd_axis.qVEL()[self.second_axis_ID] vel_dict[item] = y_vel elif item == self.third_axis_label: z_vel = self.pidevice_3rd_axis.qVEL()[self.third_axis_ID] vel_dict[item] = z_vel else: print('Given axis not available.') return vel_dict def set_velocity(self, param_dict): """ Write new value for velocity. @param dict param_dict: dictionary, which passes all the relevant parameters, which should be changed. Usage: {'axis_label': <the-velocity-value>}. 'axis_label' must correspond to a label given to one of the axis. @return int: error code (0:OK, -1:error) """ constraints = self.get_constraints() for key, value in param_dict.items( ): # param_dict has the format {'x': 20, 'y': 0, 'z': 10} for example if key == self.first_axis_label: if constraints[self.first_axis_label][ 'vel_min'] <= value <= constraints[ self.first_axis_label]['vel_max']: self.pidevice_1st_axis.VEL(self.first_axis_ID, value) else: print( 'Target value not in allowed range. Velocity not set.') elif key == self.second_axis_label: if constraints[self.second_axis_label][ 'vel_min'] <= value <= constraints[ self.second_axis_label]['vel_max']: self.pidevice_2nd_axis.VEL(self.second_axis_ID, value) else: print( 'Target value not in allowed range. Velocity not set.') elif key == self.third_axis_label: if constraints[self.third_axis_label][ 'vel_min'] <= value <= constraints[ self.third_axis_label]['vel_max']: self.pidevice_3rd_axis.VEL(self.third_axis_ID, value) else: print( 'Target value not in allowed range. Velocity not set.') else: print('Given axis not available.')
print(time.time() - tic) #%% # una manera facil de esperar a que llegue al target pi_device.qONT() # checkea si ya se movio o se esta moviendo la platina axes = 'A' targets = 0 pi_device.MOV(axes, targets) tic = time.time() while not all(pi_device.qONT(axes).values()): time.sleep(0.01) print(pi_device.qPOS()) print(time.time() - tic) # %% # corta la coneccion. pi_device.CloseConnection() #pi_device.StopAll() #pi_device.SystemAbort() # %% servo_time = 0.000040 # seconds # tiempo del servo: 40µs. lo dice qGWD() axis = 'A' if axis == 'A': number = 1 elif axis == 'B': number = 2 elif axis == 'C': number = 3
class PIFOC(Base, MotorInterface): """ Class representing the PIFOC z axis positioning stage Example config for copy-paste: pifoc: module.Class: 'motor.motor_pifoc.PIFOC' controllername: 'E816' serialnumber: '110059675' pos_min: 0 # in um pos_max: 100 # in um max_step: 1 # in um """ _controllername = ConfigOption('controllername', missing='error') # 'E-816' _serialnum = ConfigOption('serialnumber', missing='error') # 110059675' axes = None pidevice = None # private attributes read from config or from hardware and used for the constraints settings _axis_label = None _axis_ID = None _pos_min = ConfigOption('pos_min', 0, missing='warn') # in um _pos_max = ConfigOption('pos_max', 100, missing='warn') # in um _max_step = ConfigOption('max_step', 5, missing='warn') # in um # _vel_min = ConfigOption('vel_min', ??, missing='warn') # _vel_max = ConfigOption('vel_max', ??, missing='warn') def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) def on_activate(self): """ Initialization """ try: # initalize the dll self.pidevice = GCSDevice(self._controllername) # open the connection self.pidevice.ConnectUSB(serialnum=self._serialnum) self.log.info('connected: {}'.format(self.pidevice.qIDN().strip())) # Initialize the axis label and axis ID (single axis controller) self.axes = self.pidevice.axes # this returns a list self._axis_label = self.axes[0] # axes is actuallly a list of length 1 self._axis_ID = self.pidevice.GetID() self.log.info(f'available axis: {self._axis_label}, ID: {self._axis_ID}') pitools.startup(self.pidevice) except Exception as e: self.log.error(f'Physik Instrumente PIFOC: Connection failed: {e}.') def on_deactivate(self): """ Required deactivation steps """ self.pidevice.CloseConnection() def get_constraints(self): """ Retrieve the hardware constrains from the motor device. @return dict constraints """ constraints = {} axis0 = {'label': self._axis_label, 'ID': self._axis_ID, 'unit': 'um', 'pos_min': self._pos_min, 'pos_max': self._pos_max, 'max_step': self._max_step } # 'vel_min': self._vel_min, # 'vel_max': self._vel_max} # assign the parameter container for x to a name which will identify it constraints[axis0['label']] = axis0 return constraints def move_rel(self, param_dict): """ Moves stage in given direction (relative movement) @param dict param_dict: Dictionary with axis name and step (in um units) as key-value pairs {axis_label: step} @return bool: error code (True: ok, False: not ok) - or modify to return new position instead ?? """ constraints = self.get_constraints() position = self.get_pos() # returns an OrderedDict with one entry (one axis) err = False for i in range(len(param_dict)): # potentially a list of axes. real case: list of length 1 because pifoc only has one axis # in case a longer param_dict is given, only the entry with the right axis label will be considered (axis, step) = param_dict.popitem() step = np.round(step, decimals=3) if axis in self.axes: cur_pos = position[axis] # returns just the float value of the axis # check if the position stays in allowed range after movement if abs(step) <= constraints[axis]['max_step'] and constraints[axis]['pos_min'] <= cur_pos + step <= constraints[axis]['pos_max']: self.pidevice.MVR(axis, step) err = True if not err: error_code = self.pidevice.GetError() error_msg = self.pidevice.TranslateError(error_code) self.log.warning(f'Could not move axis {axis} by {step}: {error_msg}.') else: self.log.warning('Movement not possible. Allowed range exceeded') return err ## to do: check why MVR and not MVE. MVE didnot work when tested but according to dll doc it is the right method for one axis rel movement def move_abs(self, param_dict): """ Moves stage to absolute position (absolute movement) @param dict param_dict: Dictionary with axis name and target position (in um units) as key-value pairs @return bool: error code (True: ok, False: error) - or modify to return the new position instead ?? """ constraints = self.get_constraints() err = False for i in range(len(param_dict)): # potentially a list of axes. real case: list of length 1 because pifoc only has one axis (axis, target) = param_dict.popitem() target = np.round(target, decimals=3) # self.log.info(f'axis: {axis}; target: {target}') if axis in self.axes and constraints[axis]['pos_min'] <= target <= constraints[axis]['pos_max']: # control if the right axis is addressed self.pidevice.MOV(axis, target) # MOV has no return value err = True if not err: error_code = self.pidevice.GetError() error_msg = self.pidevice.TranslateError(error_code) self.log.warning(f'Could not move axis {axis} to {target} : {error_msg}.') # it might be needed to print a pertinent error message in case the movement was not performed because the conditions above were not met, # that is, if the error does not come from the controller but due to the coded conditions return err # modify when move_rel in new version was tested def abort(self): """ Stops movement of the stage @return bool: error code (True: ok, False: error) """ # err = self.pidevice.HLT() # needs eventually controller ID as argument # HLT or STP ? # errorcode = self.pidevice.GetError() # errormsg = self.pidevice.TranslateError(errorcode) # if not err: # self.log.warning(f'Error calling abort: {errormsg}') # return err pass def get_pos(self): """ Gets current position of the controller @return OrderedDict: with keys being the axis labels and item the current position. """ pos = self.pidevice.qPOS(self.axes) # this returns an OrderedDict return pos def get_status(self): """ Get the status of the position @return bool err """ err = self.pidevice.IsControllerReady() return err def calibrate(self): """ Calibrates the stage. @return int: error code (0:OK, -1:error) """ pass # do we need this ? def get_velocity(self): """ Gets the current velocity for all connected axes. @return dict : with the axis label as key and the velocity as item. """ # vel = self.pidevice.q???(self.axis) # test which query is the right one here.. # return vel self.log.info('get_velocity not available') def set_velocity(self, param_dict): """ Write new value for velocity. @param dict param_dict: Dictionary with axis name and target velocity (in ?? units) as key-value pairs """ self.log.info('set velocity not available') # should it be possible to set the velocity else just send a message that this function is not available for the controller # not on the interface def _wait_for_idle(self): """ first draft for a wait for idle function checks if on target problem: seems to return too fast, so that the position is not yet the right one.. although this function is actually meant to not wait until the target position is reached.. it works when the two log entries are activated. this seems to take some additional time, allowing the stage to reach the target """ # self.log.info('old position: {}'.format(self.get_pos())) pitools.waitontarget(self.pidevice) # self.log.info('new position: {}'.format(self.get_pos())) return
class PIShortStageDelay: def __init__(self, t0): self.t0 = t0 self.stage = GCSDevice('E-873') self.stage.ConnectUSB(serialnum=119040925) self.axis = '1' self.timeout = 5000 self.pos_max = 13.0 self.pos_min = -13.0 self.set_max_min_times() self.stage.VEL(self.axis, 3.0) # set the velocity to some low value to avoid crashes! pitools.startup(self.stage) def initialise(self): self.stage.FRF(self.axis) # reference the axis self.wait(self.timeout) self.initialized = True def wait(self, timeout): pitools.waitontarget(self.stage, self.axis, timeout=timeout) return def home(self): self.stage.GOH(self.axis) self.wait(self.timeout) return def move_to(self, time_point_ps): new_pos_mm = self.convert_ps_to_mm(float(self.t0-time_point_ps)) self.stage.MOV(self.axis, new_pos_mm) self.wait(self.timeout) return False def convert_ps_to_mm(self, time_ps): pos_mm = (0.299792458*time_ps/2)-13.0 return pos_mm def convert_mm_to_ps(self, pos_mm): time_ps = 2*(pos_mm+13.0)/0.299792458 return time_ps def set_max_min_times(self): self.tmax = self.convert_mm_to_ps(self.pos_min)+self.t0 self.tmin = -self.convert_mm_to_ps(self.pos_max)+self.t0 def close(self): self.stage.CloseConnection() def check_times(self, times): all_on_stage = True for time in times: pos = self.convert_ps_to_mm(float(self.t0-time)) if (pos>self.pos_max) or (pos<self.pos_min): all_on_stage = False return all_on_stage def check_time(self, time): on_stage = True pos = self.convert_ps_to_mm(float(self.t0-time)) if (pos>self.pos_max) or (pos<self.pos_min): on_stage = False return on_stage
class PILongStageDelay: def __init__(self, t0): self.t0 = t0 self.stage = GCSDevice('HYDRA') # alternatively self.stage = GCSDevice(gcsdll='PI_HydraPollux_GCS2_DLL_x64.dll') for a fail safe option self.stage.ConnectTCPIP(ipaddress='192.168.0.2', ipport=400) self.axis = '1' self.timeout = 5000 self.pos_max = 610.0 self.pos_min = 0.0 self.set_max_min_times() self.stage.VEL(self.axis, 30.0) # set the velocity to some low value to avoid crashes! pitools.startup(self.stage) def initialise(self): self.stage.FRF(self.axis) # reference the axis self.wait(self.timeout) self.initialized = True def wait(self, timeout): pitools.waitontarget(self.stage, self.axis, timeout=timeout) return def home(self): self.stage.GOH(self.axis) self.wait(self.timeout) return def move_to(self, time_point_ps): new_pos_mm = self.convert_ps_to_mm(float(self.t0-time_point_ps)) self.stage.MOV(self.axis, new_pos_mm) self.wait(self.timeout) return False def convert_ps_to_mm(self, time_ps): pos_mm = 0.299792458*time_ps/2 return pos_mm def convert_mm_to_ps(self, pos_mm): time_ps = 2*pos_mm/0.299792458 return time_ps def set_max_min_times(self): self.tmax = self.convert_mm_to_ps(self.pos_min)+self.t0 self.tmin = -self.convert_mm_to_ps(self.pos_max)+self.t0 def close(self): self.stage.CloseConnection() def check_times(self, times): all_on_stage = True for time in times: pos = self.convert_ps_to_mm(float(self.t0-time)) if (pos>self.pos_max) or (pos<self.pos_min): all_on_stage = False return all_on_stage def check_time(self, time): on_stage = True pos = self.convert_ps_to_mm(float(self.t0-time)) if (pos>self.pos_max) or (pos<self.pos_min): on_stage = False return on_stage
from pipython import GCSDevice gcs = GCSDevice('C-863.10') gcs.ConnectUSB(serialnum='0135500849') print(gcs.qIDN()) gcs.CloseConnection()
class wrp_pistages: """ A class containing the main functions to use with the PI Stages """ def __init__(self): self.piXYstages = GCSDevice( 'Hydra') #select the right DLL to use with the controller self.piZstages = GCSDevice('C-863.11') return def wait_axis(self): return def init_controller(self, controller): if (controller == 'Hydra'): startuphydra('Hydra', ('68409121_U', '68409121_L'), ('FNL', 'FNL')) self.piXYstages.ConnectRS232(comport=3, baudrate=115200) elif (controller == 'MERCURY'): startupmercury('C-863.11', 'M-511K112', 'FRF') self.piZstages.ConnectUSB(serialnum='0175500008') return def home_axis(self, ax): if (ax == 1 or ax == 2): self.piXYstages.GOH(str(ax)) elif (ax == 3): self.piZstages.GOH('1') return def set_acc(self, ax, acc): if (ax == 1 or ax == 2): print 'Acceleration of axis %c set to %f' % (str(ax), acc) self.piXYstages.ACC(str(ax), acc) elif (ax == 3): print 'Acceleration of axis %c set to %f' % (str(ax), acc) self.piZstages.ACC('1', acc) return def set_vel(self, ax, vel): if (ax == 1 or ax == 2): print 'Closed loop velocity of axis %c set to %f' % (str(ax), vel) self.piXYstages.VEL(str(ax), vel) elif (ax == 3): print 'Closed loop velocity of axis %c set to %f' % (str(ax), vel) self.piZstages.VEL('1', vel) return def move_abs(self, ax, x): if (ax == 1 or ax == 2): self.piXYstages.MOV(str(ax), x) elif (ax == 3): self.piZstages.MOV('1', x) # waitontarget(self.piXYstages) # positions = self.piXYstages.qPOS(self.piXYstages.axes) # print('Axis {} moved to position {:.4f}'.format(ax, positions[ax])) return def move_rel(self, ax, dx): if (ax == 1 or ax == 2): print 'Relative move axis %c of: %f' % (str(ax), dx) self.piXYstages.MVR(str(ax), dx) elif (ax == 3): print 'Relative move axis %c of: %f' % (str(ax), dx) self.piZstages.MVR('1', dx) # waitontarget(self.piXYstages) return def close_axis(self, ax): if (ax == 1 or ax == 2): self.piXYstages.CloseConnection() self.piXYstages.unload() elif (ax == 3): self.piZstages.CloseConnection() self.piZstages.unload() return def get_pos(self, ax): if (ax == 1 or ax == 2): return self.piXYstages.qPOS(self.piXYstages.axes)[str(ax)] elif (ax == 3): return self.piZstages.qPOS('1')
class Worker_PI(QObject): PI_ishere_signal = pyqtSignal(bool) def __init__(self, queue_disconnections, jobs_window, progress_piezo_signal, piezoZ_changeDispValue_signal, PI_pack): super(Worker_PI, self).__init__() self.queue_disconnections = queue_disconnections self.jobs_window = jobs_window self.progress_piezo_signal = progress_piezo_signal self.piezoZ_changeDispValue_signal = piezoZ_changeDispValue_signal [ self.PI_conn_meth, self.PI_comport, self.PI_baud, self.PI_SERVOMODE, self.PI_CONTROLLERNAME, self.max_range_PI ] = PI_pack @pyqtSlot() def open_instr(self): from pipython import GCSDevice, pitools, GCSError self.pitools = pitools self.pi_device = GCSDevice( self.PI_CONTROLLERNAME) # Load PI Python Libraries try: if self.PI_conn_meth == 'usb': self.pi_device.ConnectUSB( self.PIezo_USB_ID) # Connect to the controller via USB else: self.pi_device.ConnectRS232( comport=self.PI_comport, baudrate=self.PI_baud ) # # the timeout is very long, 30 sec !! except Exception as err: if ( type(err) == GCSError and err.val == -9 ): # # 'There is no interface or DLL handle with the given ID (-9)' print('No PI device !!') else: print('PI device lead to UNKNOWN error!!') self.PI_is_here = False else: self.PI_is_here = True print('PI device here.') self.pi_device.SVO( self.pi_device.axes, self.PI_SERVOMODE ) # 1 = servo on (closed-loop operation) ; 0 = open-loop (servo OFF) # # open loop resolution (0.4nm) is a lower value than the closed loop resolution (0.7nm) due to the noise of the sensor signal. Open loop is subject to hysteresis, meaning the position could be off by up to 15%. When operated in closed loop, the hysteresis is compensated for, and virtually eliminated. self.getpos_motor_PI() self.PI_ishere_signal.emit(self.PI_is_here) @pyqtSlot(float) def step_motor_PI(self, stp): # function to move the PI motor self.pi_device.MVR(self.pi_device.axes, -stp) # Command axis "A" to step RELATIVE # um self.getpos_motor_PI() @pyqtSlot(float) def move_motor_PI(self, pos): # function to move the PI motor # # pos in mm self.pi_device.MOV(self.pi_device.axes, max((self.max_range_PI - pos) * 1000, 0)) # Command axis "A" to position pos ABSOLUTE # # moves the sample to the top, so converted to act as if the objective moves to the top (moving sample to bottom) # um self.pitools.waitontarget(self.pi_device) self.getpos_motor_PI() @pyqtSlot() def getpos_motor_PI(self): st = 0 while True: if (not self.pi_device.IsMoving( self.pi_device.axes)[self.pi_device.axes[0]] or st >= 20): # True if is moving break else: st += 1 # usually finish after only 1, or 2 for big moves position = self.max_range_PI * 1000 - self.pi_device.qPOS( self.pi_device.axes)[ self.pi_device.axes[0]] # Query current position of axis # # moves the sample to the top, so converted to act as if the objective moves to the top (moving sample to bottom) # um self.progress_piezo_signal.emit( int(round(position / 1000 / self.max_range_PI * 100))) self.piezoZ_changeDispValue_signal.emit( position / 1000) # has to be emitted in mm @pyqtSlot(bool) def close_instr(self, flag_end): if self.PI_is_here: print('closing PI...') self.pi_device.CloseConnection() if flag_end: # end of program self.queue_disconnections.put( 7) # tell the GUI can kill this QThread : PI's signature is 7