Beispiel #1
0
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']
Beispiel #2
0
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()
Beispiel #3
0
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
Beispiel #6
0
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
Beispiel #7
0
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.')
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #12
0
from pipython import GCSDevice
gcs = GCSDevice('C-863.10')
gcs.ConnectUSB(serialnum='0135500849')
print(gcs.qIDN())
gcs.CloseConnection()
Beispiel #13
0
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