'''
mesoSPIM Stage classes
======================
'''
import time
import logging
from PyQt5 import QtCore
#from .mesoSPIM_State import mesoSPIM_StateSingleton
logger = logging.getLogger(__name__)
[docs]
class mesoSPIM_Stage(QtCore.QObject):
'''
DemoStage for a mesoSPIM microscope
It is expected that the parent class has the following signals:
sig_move_relative = pyqtSignal(dict)
sig_move_relative_and_wait_until_done = pyqtSignal(dict)
sig_move_absolute = pyqtSignal(dict)
sig_move_absolute_and_wait_until_done = pyqtSignal(dict)
sig_zero = pyqtSignal(list)
sig_unzero = pyqtSignal(list)
sig_stop_movement = pyqtSignal()
Also contains a QTimer that regularily sends position updates, e.g
during the execution of movements.
'''
sig_position = QtCore.pyqtSignal(dict)
sig_status_message = QtCore.pyqtSignal(str)
sig_pause = QtCore.pyqtSignal(bool)
def __init__(self, parent=None):
super().__init__()
self.parent = parent #
self.cfg = parent.cfg
self.state = self.parent.state # the mesoSPIM_StateSingleton() instance
''' The movement signals are emitted by the mesoSPIM_Core, which in turn
instantiates the mesoSPIM_Serial object, both (must be) running in the same Core thread.
Therefore, the signals are emitted by the parent of the parent, which
is slightly confusing and dirty.
'''
self.parent.sig_stop_movement.connect(self.stop)
self.parent.sig_zero_axes.connect(self.zero_axes)
self.parent.sig_unzero_axes.connect(self.unzero_axes)
self.parent.sig_load_sample.connect(self.load_sample)
self.parent.sig_unload_sample.connect(self.unload_sample)
self.parent.sig_center_sample.connect(self.center_sample)
self.pos_timer = QtCore.QTimer(self)
self.pos_timer.timeout.connect(self.report_position)
self.pos_timer.start(100) # previously 50, which is unnecessary fast
'''Initial setting of all positions
self.x_pos, self.y_pos etc are the true axis positions, no matter whether
the stages are zeroed or not.
'''
self.x_pos = 0
self.y_pos = 0
self.z_pos = 0
self.f_pos = 2500 # for testing purposes
self.theta_pos = 0
'''Internal (software) positions'''
self.int_x_pos = 0
self.int_y_pos = 0
self.int_z_pos = 0
self.int_f_pos = 0
self.int_theta_pos = 0
'''Create offsets
It should be:
int_x_pos = x_pos + int_x_pos_offset
self.int_x_pos = self.x_pos + self.int_x_pos_offset
OR
x_pos = int_x_pos - int_x_pos_offset
self.x_pos = self.int_x_pos - self.int_x_pos_offset
'''
self.int_x_pos_offset = 0
self.int_y_pos_offset = 0
self.int_z_pos_offset = 0
self.int_f_pos_offset = 0
self.int_theta_pos_offset = 0
'''
Setting movement limits: currently hardcoded
Open question: Should these be in mm or microns?
Answer: Microns for now....
'''
self.x_max = self.cfg.stage_parameters['x_max']
self.x_min = self.cfg.stage_parameters['x_min']
self.y_max = self.cfg.stage_parameters['y_max']
self.y_min = self.cfg.stage_parameters['y_min']
self.z_max = self.cfg.stage_parameters['z_max']
self.z_min = self.cfg.stage_parameters['z_min']
self.f_max = self.cfg.stage_parameters['f_max']
self.f_min = self.cfg.stage_parameters['f_min']
self.theta_max = self.cfg.stage_parameters['theta_max']
self.theta_min = self.cfg.stage_parameters['theta_min']
for deprecated_param in ('x_rot_position', 'y_rot_position', 'z_rot_position'):
if deprecated_param in self.cfg.stage_parameters.keys():
print(f"INFO: '{deprecated_param}' in 'stage_parameters' dictionary is deprecated and ignored. "
f"Update your config file to suppress these messages.")
[docs]
def create_position_dict(self):
self.position_dict = {'x_pos': self.x_pos,
'y_pos': self.y_pos,
'z_pos': self.z_pos,
'f_pos': self.f_pos,
'theta_pos': self.theta_pos,
}
self.state['position_absolute'] = self.position_dict
[docs]
def create_internal_position_dict(self):
self.int_position_dict = {'x_pos': self.int_x_pos,
'y_pos': self.int_y_pos,
'z_pos': self.int_z_pos,
'f_pos': self.int_f_pos,
'theta_pos': self.int_theta_pos,
}
@QtCore.pyqtSlot()
def report_position(self):
self.create_position_dict()
self.int_x_pos = self.x_pos + self.int_x_pos_offset
self.int_y_pos = self.y_pos + self.int_y_pos_offset
self.int_z_pos = self.z_pos + self.int_z_pos_offset
self.int_f_pos = self.f_pos + self.int_f_pos_offset
self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
self.create_internal_position_dict()
self.state['position'] = self.int_position_dict
self.sig_position.emit(self.int_position_dict)
@QtCore.pyqtSlot(dict)
def move_relative(self, sdict, wait_until_done=False):
if 'x_rel' in sdict:
self.x_pos = self.x_pos + sdict['x_rel']
print(f"INFO: x_pos = {self.x_pos}")
if 'y_rel' in sdict:
self.y_pos = self.y_pos + sdict['y_rel']
print(f"INFO: y_pos = {self.y_pos}")
if 'z_rel' in sdict:
self.z_pos = self.z_pos + sdict['z_rel']
print(f"INFO: z_pos = {self.z_pos}")
if 'theta_rel' in sdict:
self.theta_pos = self.theta_pos + sdict['theta_rel']
print(f"INFO: theta_pos = {self.theta_pos}")
if 'f_rel' in sdict:
self.f_pos = self.f_pos + sdict['f_rel']
print(f"INFO: f_pos = {self.f_pos}")
if wait_until_done is True:
self.state['moving_to_target'] = True
time.sleep(0.1)
self.report_position()
self.state['moving_to_target'] = False
@QtCore.pyqtSlot(dict)
def move_absolute(self, dict, wait_until_done=False, use_internal_position=True):
if use_internal_position is True:
x_offset = self.int_x_pos_offset
y_offset = self.int_y_pos_offset
z_offset = self.int_z_pos_offset
f_offset = self.int_f_pos_offset
theta_offset = self.int_theta_pos_offset
else:
x_offset = 0
y_offset = 0
z_offset = 0
f_offset = 0
theta_offset = 0
if 'x_abs' in dict:
x_abs = dict['x_abs'] - x_offset
self.x_pos = x_abs
print(f"INFO: x_pos = {self.x_pos}")
if 'y_abs' in dict:
y_abs = dict['y_abs'] - y_offset
self.y_pos = y_abs
print(f"INFO: y_pos = {self.y_pos}")
if 'z_abs' in dict:
z_abs = dict['z_abs'] - z_offset
self.z_pos = z_abs
print(f"INFO: z_pos = {self.z_pos}")
if 'f_abs' in dict:
f_abs = dict['f_abs'] - f_offset
self.f_pos = f_abs
if self.f_min < f_abs < self.f_max:
logger.debug('Moving to f_abs: %s' % f_abs)
time.sleep(0.2)
else:
msg = f' f_abs={f_abs} absolute movement stopped: F motion limits ({self.f_min},{self.f_max}) would be reached!'
self.sig_status_message.emit(msg)
logger.debug(msg)
if 'theta_abs' in dict:
theta_abs = dict['theta_abs'] - theta_offset
self.theta_pos = theta_abs
print(f"INFO: theta_pos = {self.theta_pos}")
if wait_until_done is True:
self.state['moving_to_target'] = True
time.sleep(1)
self.report_position()
self.state['moving_to_target'] = False
msg = 'Demo stage move (wait_until_done is True) complete'
print(msg); logger.debug(msg)
@QtCore.pyqtSlot()
def stop(self):
self.sig_status_message.emit('Stopped')
[docs]
def zero_axes(self, list):
for axis in list:
try:
exec('self.int_' + axis + '_pos_offset = -self.' + axis + '_pos') # update the position offset
except:
logger.info('Zeroing of axis: ', axis, 'failed')
[docs]
def unzero_axes(self, list):
for axis in list:
try:
exec('self.int_' + axis + '_pos_offset = 0') # zero the position offset
except:
logger.info('Unzeroing of axis: ', axis, 'failed')
[docs]
def load_sample(self):
self.y_pos = self.cfg.stage_parameters['y_load_position']
[docs]
def unload_sample(self):
self.y_pos = self.cfg.stage_parameters['y_unload_position']
[docs]
def center_sample(self):
if 'x_center_position' in self.cfg.stage_parameters.keys():
self.x_center = self.cfg.stage_parameters['x_center_position']
self.move_absolute({'x_abs': self.x_center}, wait_until_done=False, use_internal_position=False)
else:
self.x_center = self.x_pos
msg = 'Centering X position not defined in config file'
logger.info(msg); print(msg)
if 'z_center_position' in self.cfg.stage_parameters.keys():
self.z_center = self.cfg.stage_parameters['z_center_position']
self.move_absolute({'z_abs': self.z_center}, wait_until_done=False, use_internal_position=False)
else:
self.z_center = self.z_pos
msg = 'Centering Z position not defined in config file'
logger.info(msg); print(msg)
[docs]
class mesoSPIM_DemoStage(mesoSPIM_Stage):
def __init__(self, parent=None):
super().__init__(parent)
[docs]
class mesoSPIM_PI_1toN(mesoSPIM_Stage):
'''
Configuration with 1 controller connected to N stages, (e.g. C-884, default mesoSPIM V5 setup).
Note:
configs as declared in mesoSPIM_config.py:
stage_parameters = {'stage_type' : 'PI_1controllerNstages',
...
}
pi_parameters = {'controllername' : 'C-884',
'stages' : ('L-509.20DG10','L-509.40DG10','L-509.20DG10','M-060.DG','M-406.4PD','NOSTAGE'),
'refmode' : ('FRF',),
'serialnum' : ('118075764'),
}
'''
def __init__(self, parent=None):
super().__init__(parent)
from pipython import GCSDevice, pitools
self.pitools = pitools
''' Setting up the PI stages '''
self.pi = self.cfg.pi_parameters
self.controllername = self.cfg.pi_parameters['controllername']
self.pi_stages = list(self.cfg.pi_parameters['stages'])
self.refmode = self.cfg.pi_parameters['refmode']
self.serialnum = self.cfg.pi_parameters['serialnum']
self.pidevice = GCSDevice(self.controllername)
self.pidevice.ConnectUSB(serialnum=self.serialnum)
''' PI startup '''
''' with refmode enabled: pretty dangerous
pitools.startup(self.pidevice, stages=self.pi_stages, refmode=self.refmode)
'''
pitools.startup(self.pidevice, stages=self.pi_stages)
''' Report reference status of all stages '''
for ii in range(1, len(self.pi_stages) + 1):
tStage = self.pi_stages[ii - 1]
if tStage == 'NOSTAGE':
continue
tState = self.pidevice.qFRF(ii)
if tState[ii]:
msg = 'referenced'
else:
msg = '*UNREFERENCED*'
logger.info("Axis %d (%s) reference status: %s" % (ii, tStage, msg))
self.report_position()
''' Stage 5 referencing hack '''
# self.pidevice.FRF(5)
# logger.info('M-406 Emergency referencing hack: Waiting for referencing move')
# self.block_till_controller_is_ready()
# logger.info('M-406 Emergency referencing hack done')
def __del__(self):
try:
self.pidevice.unload()
except:
pass
[docs]
def report_position(self):
positions = self.pidevice.qPOS(self.pidevice.axes)
self.x_pos = round(positions['1'] * 1000, 2)
self.y_pos = round(positions['2'] * 1000, 2)
self.z_pos = round(positions['3'] * 1000, 2)
self.f_pos = round(positions['5'] * 1000, 2)
self.theta_pos = positions['4']
self.create_position_dict()
# relative positions, with the offset
self.int_x_pos = self.x_pos + self.int_x_pos_offset
self.int_y_pos = self.y_pos + self.int_y_pos_offset
self.int_z_pos = self.z_pos + self.int_z_pos_offset
self.int_f_pos = self.f_pos + self.int_f_pos_offset
self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
self.create_internal_position_dict()
self.state['position'] = self.int_position_dict
self.sig_position.emit(self.int_position_dict)
@QtCore.pyqtSlot(dict)
def move_relative(self, sdict, wait_until_done=False):
''' PI move relative method
Lots of implementation details in here, should be replaced by a facade
'''
if 'x_rel' in sdict:
x_rel = sdict['x_rel'] / 1000
self.pidevice.MVR({1: x_rel})
if 'y_rel' in sdict:
y_rel = sdict['y_rel'] / 1000
self.pidevice.MVR({2: y_rel})
if 'z_rel' in sdict:
z_rel = sdict['z_rel'] / 1000
self.pidevice.MVR({3: z_rel})
if 'theta_rel' in sdict:
theta_rel = sdict['theta_rel']
self.pidevice.MVR({4: theta_rel})
if 'f_rel' in sdict:
f_rel = sdict['f_rel'] / 1000
self.pidevice.MVR({5: f_rel})
if wait_until_done:
self.state['moving_to_target'] = True
self.pitools.waitontarget(self.pidevice)
self.report_position()
self.state['moving_to_target'] = False
@QtCore.pyqtSlot(dict)
def move_absolute(self, sdict, wait_until_done=False, use_internal_position=True):
'''
Lots of implementation details in here, should be replaced by a facade
TODO: Also lots of repeating code.
TODO: DRY principle violated
'''
if use_internal_position is True:
x_offset = self.int_x_pos_offset
y_offset = self.int_y_pos_offset
z_offset = self.int_z_pos_offset
f_offset = self.int_f_pos_offset
theta_offset = self.int_theta_pos_offset
else:
x_offset = 0
y_offset = 0
z_offset = 0
f_offset = 0
theta_offset = 0
if 'x_abs' in sdict:
x_abs = sdict['x_abs'] - x_offset
if self.x_min < x_abs < self.x_max:
''' Conversion to mm and command emission'''
x_abs = x_abs / 1000
self.pidevice.MOV({1: x_abs})
else:
self.sig_status_message.emit('Absolute movement stopped: X Motion limit would be reached!')
if 'y_abs' in sdict:
y_abs = sdict['y_abs'] - y_offset
if self.y_min < y_abs < self.y_max:
''' Conversion to mm and command emission'''
y_abs = y_abs / 1000
self.pidevice.MOV({2: y_abs})
else:
self.sig_status_message.emit('Absolute movement stopped: Y Motion limit would be reached!')
if 'z_abs' in sdict:
z_abs = sdict['z_abs'] - z_offset
if self.z_min < z_abs < self.z_max:
''' Conversion to mm and command emission'''
z_abs = z_abs / 1000
self.pidevice.MOV({3: z_abs})
else:
self.sig_status_message.emit('Absolute movement stopped: Z Motion limit would be reached!')
if 'f_abs' in sdict:
f_abs = sdict['f_abs'] - f_offset
if self.f_min < f_abs < self.f_max:
logger.debug('Moving to f_abs: %s' % f_abs)
''' Conversion to mm and command emission'''
f_abs_mm = f_abs / 1000
self.pidevice.MOV({5: f_abs_mm})
else:
msg = f' f_abs={f_abs} absolute movement stopped: F motion limits ({self.f_min},{self.f_max}) would be reached!'
self.sig_status_message.emit(msg)
logger.debug(msg)
if 'theta_abs' in sdict:
theta_abs = sdict['theta_abs'] - theta_offset
if self.theta_min < theta_abs < self.theta_max:
''' No Conversion to mm !!!! and command emission'''
self.pidevice.MOV({4: theta_abs})
else:
self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!')
if wait_until_done:
self.state['moving_to_target'] = True
logger.debug('Waiting for target (wait_until_done=True)')
self.pitools.waitontarget(self.pidevice)
logger.debug('Target reached (wait_until_done=True)')
self.report_position()
self.state['moving_to_target'] = False
@QtCore.pyqtSlot()
def stop(self):
self.pidevice.STP(noraise=True)
[docs]
def load_sample(self):
y_abs = self.cfg.stage_parameters['y_load_position'] / 1000
self.pidevice.MOV({2: y_abs})
[docs]
def unload_sample(self):
y_abs = self.cfg.stage_parameters['y_unload_position'] / 1000
self.pidevice.MOV({2: y_abs})
[docs]
def block_till_controller_is_ready(self):
'''
Blocks further execution (especially during referencing moves)
till the PI controller returns ready
'''
blockflag = True
while blockflag:
if self.pidevice.IsControllerReady():
blockflag = False
else:
time.sleep(0.1)
[docs]
class mesoSPIM_PI_NtoN(mesoSPIM_Stage):
'''
Expects following microscope configuration:
Sample XYZ movement: Physik Instrumente stage with three L-509-type stepper motor stages and individual C-663 controller.
F movement: Physik Instrumente C-663 controller and custom stage with stepper motor
Rotation: not implemented
All stage controller are of same type and the sample stages work with reference setting.
Focus stage has reference mode set to off.
Note:
configs as declared in mesoSPIM_config.py:
stage_parameters = {'stage_type' : 'PI_NcontrollersNstages',
...
}
pi_parameters = {'axes_names': ('x', 'y', 'z', 'theta', 'f'),
'stages': ('L-509.20SD00', 'L-509.40SD00', 'L-509.20SD00', None, 'MESOSPIM_FOCUS'),
'controllername': ('C-663', 'C-663', 'C-663', None, 'C-663'),
'serialnum': ('**********', '**********', '**********', None, '**********'),
'refmode': ('FRF', 'FRF', 'FRF', None, 'RON')
}
make sure that reference points are not in conflict with general microscope setup
and will not hurt optics under referencing at startup
'''
def __init__(self, parent=None):
super().__init__(parent)
from pipython import GCSDevice, pitools
self.pitools = pitools
self.pi = self.cfg.pi_parameters
print("Connecting stage drive...")
# Setting up the stages with separate PI controller.
# Explicitly set referencing status and get position
# gather stage devices in VirtualStages class
class VirtualStages:
pass
assert len(self.pi['axes_names']) == len(self.pi['stages']) == len(self.pi['controllername']) \
== len(self.pi['serialnum']) == len(self.pi['refmode']), \
"Config file, pi_parameters dictionary: numbers of axes_names, stages, controllername, serialnum, refmode must match "
self.pi_stages = VirtualStages()
for axis_name, stage, controller, serialnum, refmode in zip(self.pi['axes_names'], self.pi['stages'],
self.pi['controllername'], self.pi['serialnum'],
self.pi['refmode']):
# run stage startup procedure for each axis
if stage:
print(f'starting stage {stage}')
pidevice_ = GCSDevice(controller)
pidevice_.ConnectUSB(serialnum=serialnum)
if refmode is None:
pitools.startup(pidevice_, stages=stage)
elif refmode == 'FRF':
pitools.startup(pidevice_, stages=stage, refmodes=refmode)
pidevice_.FRF(1)
elif refmode == 'RON':
pitools.startup(pidevice_, stages=stage)
pidevice_.RON({1: 0}) # set reference mode
# activate servo
pidevice_.SVO(pidevice_.axes, [True] * len(pidevice_.axes))
# print('servo state: {}'.format(pidevice_.qSVO()))
# set/get actual position as home position
# assumes that starting position is within reasonable distance from optimal focus
pidevice_.POS({1: 0.0})
pidevice_.DFH(1)
else:
raise ValueError(f"refmode {refmode} is not supported, PI stage {stage} initialization failed")
print(f'stage {stage} started')
print('axis {}, referencing mode: {}'.format(axis_name, pidevice_.qRON()))
self.wait_for_controller(pidevice_)
print('axis {}, stage {} ready'.format(axis_name, stage))
setattr(self.pi_stages, ('pidevice_' + axis_name), pidevice_)
else:
setattr(self.pi_stages, axis_name, None)
logger.info('mesoSPIM_PI_NtoN: started')
[docs]
def wait_for_controller(self, controller):
# function used during stage setup
blockflag = True
while blockflag:
if controller.IsControllerReady():
blockflag = False
else:
time.sleep(0.1)
def __del__(self):
'''Close the PI connection'''
try:
[(getattr(self.pi_stages, ('pidevice_' + axis_name))).unload() for axis_name in self.pi['axes_names'] if
(hasattr(self.pi_stages, ('pidevice_' + axis_name)))]
except:
pass
[docs]
def report_position(self):
'''report stage position'''
for axis_name in self.pi['axes_names']:
pidevice_name = 'pidevice_' + str(axis_name)
if hasattr(self.pi_stages, pidevice_name):
try:
if axis_name is None:
pos = 0
elif axis_name == 'theta':
pos = (getattr(self.pi_stages, pidevice_name)).qPOS(1)[1]
else:
pos = round((getattr(self.pi_stages, pidevice_name)).qPOS(1)[1] * 1000, 2)
except:
print(f"Failed to report_position for axis_name {axis_name}, pidevice_name {pidevice_name}.")
else:
pos = 0
setattr(self, (axis_name + '_pos'), pos)
int_pos = pos + getattr(self, ('int_' + axis_name + '_pos_offset'))
setattr(self, ('int_' + axis_name + '_pos'), int_pos)
self.create_position_dict()
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
[docs]
def move_relative(self, move_dict, wait_until_done=False):
''' PI move relative method '''
for axis_move in move_dict.keys():
axis_name = axis_move.split('_')[0]
move_value = move_dict[axis_move]
if (hasattr(self.pi_stages, ('pidevice_' + axis_name))):
if (getattr(self, (axis_name + '_min')) < getattr(self, (axis_name + '_pos')) + move_value) and \
(getattr(self, (axis_name + '_max')) > getattr(self, (axis_name + '_pos')) + move_value):
if not axis_name=='theta':
move_value = move_value/1000
(getattr(self.pi_stages, ('pidevice_' + axis_name))).MVR({1 : move_value})
else:
self.sig_status_message.emit('Relative movement stopped: {} Motion limit would be reached!'.format(axis_name))
if (axis_name == 'f') or (wait_until_done == True):
self.pitools.waitontarget(getattr(self.pi_stages, ('pidevice_' + axis_name))) # focus may be slower than expected
[docs]
def move_absolute(self, move_dict, wait_until_done=False, use_internal_position=True):
if use_internal_position is True:
x_offset = self.int_x_pos_offset
y_offset = self.int_y_pos_offset
z_offset = self.int_z_pos_offset
f_offset = self.int_f_pos_offset
theta_offset = self.int_theta_pos_offset
else:
x_offset = 0
y_offset = 0
z_offset = 0
f_offset = 0
theta_offset = 0
for axis_move in move_dict.keys():
axis_name = axis_move.split('_')[0]
move_value = move_dict[axis_move] - locals()[axis_name + '_offset']
if (hasattr(self.pi_stages, ('pidevice_' + axis_name))):
if (getattr(self, (axis_name + '_min')) < move_value) and \
(getattr(self, (axis_name + '_max')) > move_value):
if not axis_name == 'theta':
move_value = move_value / 1000
(getattr(self.pi_stages, ('pidevice_' + axis_name))).MOV({1: move_value})
else:
self.sig_status_message.emit(
'Absolute movement stopped: {} Motion limit would be reached!'.format(axis_name))
if (axis_name == 'f') or (wait_until_done == True):
self.pitools.waitontarget(getattr(self.pi_stages, ('pidevice_' + axis_name))) # focus may be slower than expected
[docs]
def stop(self):
'''stop stage movement'''
[(getattr(self.pi_stages, ('pidevice_' + axis_name))).STP(noraise=True) for axis_name in self.pi['axes_names']
if (hasattr(self.pi_stages, ('pidevice_' + axis_name)))]
[docs]
def load_sample(self):
'''bring sample to imaging position'''
axis_name = 'y'
y_abs = self.cfg.stage_parameters['y_load_position'] / 1000
(getattr(self.pi_stages, ('pidevice_' + axis_name))).MOV({1: y_abs})
[docs]
def unload_sample(self):
'''lift sample to sample handling position'''
axis_name = 'y'
y_abs = self.cfg.stage_parameters['y_unload_position'] / 1000
(getattr(self.pi_stages, ('pidevice_' + axis_name))).MOV({1: y_abs})
# class mesoSPIM_GalilStages(mesoSPIM_Stage):
# '''
# It is expected that the parent class has the following signals:
# sig_move_relative = pyqtSignal(dict)
# sig_move_relative_and_wait_until_done = pyqtSignal(dict)
# sig_move_absolute = pyqtSignal(dict)
# sig_move_absolute_and_wait_until_done = pyqtSignal(dict)
# sig_zero = pyqtSignal(list)
# sig_unzero = pyqtSignal(list)
# sig_stop_movement = pyqtSignal()
# Also contains a QTimer that regularily sends position updates, e.g
# during the execution of movements.
# Todo: Rotation handling not implemented!
# '''
# def __init__(self, parent=None):
# super().__init__(parent)
# '''
# Galil-specific code
# '''
# from .devices.stages.galil.galilcontrol import StageControlGalil
# self.x_encodercounts_per_um = self.cfg.xyz_galil_parameters['x_encodercounts_per_um']
# self.y_encodercounts_per_um = self.cfg.xyz_galil_parameters['y_encodercounts_per_um']
# self.z_encodercounts_per_um = self.cfg.xyz_galil_parameters['z_encodercounts_per_um']
# self.f_encodercounts_per_um = self.cfg.f_galil_parameters['z_encodercounts_per_um']
# ''' Setting up the Galil stages '''
# self.xyz_stage = StageControlGalil(COMport=self.cfg.xyz_galil_parameters['COMport'],
# x_encodercounts_per_um=self.x_encodercounts_per_um,
# y_encodercounts_per_um=self.y_encodercounts_per_um,
# z_encodercounts_per_um=self.z_encodercounts_per_um)
# self.f_stage = StageControlGalil(COMport=self.cfg.f_galil_parameters['COMport'],
# x_encodercounts_per_um=0,
# y_encodercounts_per_um=0,
# z_encodercounts_per_um=self.f_encodercounts_per_um)
# '''
# print('Galil: ', self.xyz_stage.read_position('x'))
# print('Galil: ', self.xyz_stage.read_position('y'))
# print('Galil: ', self.xyz_stage.read_position('z'))
# '''
# def __del__(self):
# try:
# '''Close the Galil connection'''
# self.xyz_stage.close_stage()
# self.f_stage.close_stage()
# except:
# pass
# def report_position(self):
# self.x_pos = self.xyz_stage.read_position('x')
# self.y_pos = self.xyz_stage.read_position('y')
# self.z_pos = self.xyz_stage.read_position('z')
# self.f_pos = self.f_stage.read_position('z')
# self.theta_pos = 0
# self.create_position_dict()
# self.int_x_pos = self.x_pos + self.int_x_pos_offset
# self.int_y_pos = self.y_pos + self.int_y_pos_offset
# self.int_z_pos = self.z_pos + self.int_z_pos_offset
# self.int_f_pos = self.f_pos + self.int_f_pos_offset
# self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
# self.create_internal_position_dict()
# self.sig_position.emit(self.int_position_dict)
# def move_relative(self, sdict, wait_until_done=False):
# ''' Galil move relative method
# Lots of implementation details in here, should be replaced by a facade
# '''
# if 'x_rel' in sdict:
# x_rel = sdict['x_rel']
# if self.x_min < self.x_pos + x_rel and self.x_max > self.x_pos + x_rel:
# self.xyz_stage.move_relative(xrel=int(x_rel))
# else:
# self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!')
# if 'y_rel' in sdict:
# y_rel = sdict['y_rel']
# if self.y_min < self.y_pos + y_rel and self.y_max > self.y_pos + y_rel:
# self.xyz_stage.move_relative(yrel=int(y_rel))
# else:
# self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!')
# if 'z_rel' in sdict:
# z_rel = sdict['z_rel']
# if self.z_min < self.z_pos + z_rel and self.z_max > self.z_pos + z_rel:
# self.xyz_stage.move_relative(zrel=int(z_rel))
# else:
# self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!')
# if 'theta_rel' in sdict:
# theta_rel = sdict['theta_rel']
# if self.theta_min < self.theta_pos + theta_rel and self.theta_max > self.theta_pos + theta_rel:
# print('No rotation stage attached')
# else:
# self.sig_status_message.emit('Relative movement stopped: theta Motion limit would be reached!')
# if 'f_rel' in sdict:
# f_rel = sdict['f_rel']
# if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
# self.f_stage.move_relative(zrel=f_rel)
# else:
# self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!')
# if wait_until_done == True:
# pass
# def move_absolute(self, dict, wait_until_done=False):
# '''
# Galil move absolute method
# Lots of implementation details in here, should be replaced by a facade
# '''
# # print(dict)
# # if ('x_abs', 'y_abs', 'z_abs', 'f_abs') in dict:
# x_abs = dict['x_abs']
# x_abs = x_abs - self.int_x_pos_offset
# y_abs = dict['y_abs']
# y_abs = y_abs - self.int_y_pos_offset
# z_abs = dict['z_abs']
# z_abs = z_abs - self.int_z_pos_offset
# f_abs = dict['f_abs']
# f_abs = f_abs - self.int_f_pos_offset
# self.xyz_stage.move_absolute(xabs=x_abs, yabs=y_abs, zabs=z_abs)
# self.f_stage.move_absolute(zabs=f_abs)
# if wait_until_done == True:
# self.xyz_stage.wait_until_done('XYZ')
# # def stop(self):
# # # self.pidevice.STP(noraise=True)
# # def load_sample(self):
# # y_abs = self.cfg.stage_parameters['y_load_position']/1000
# # # self.pidevice.MOV({2 : y_abs})
# # def unload_sample(self):
# # y_abs = self.cfg.stage_parameters['y_unload_position']/1000
# # # self.pidevice.MOV({2 : y_abs})
# class mesoSPIM_PI_f_rot_and_Galil_xyz_Stages(mesoSPIM_Stage):
# '''
# Deprecated?
# Todo: Rotation handling not implemented!
# Todo: Rotation axes are hardcoded! (M-605: #5, M-061.PD: #6)
# '''
# def __init__(self, parent=None):
# super().__init__(parent)
# self.state = mesoSPIM_StateSingleton()
# self.pos_timer = QtCore.QTimer(self)
# self.pos_timer.timeout.connect(self.report_position)
# self.pos_timer.start(50)
# '''
# Galil-specific code
# '''
# from .devices.stages.galil.galilcontrol import StageControlGalil
# self.x_encodercounts_per_um = self.cfg.xyz_galil_parameters['x_encodercounts_per_um']
# self.y_encodercounts_per_um = self.cfg.xyz_galil_parameters['y_encodercounts_per_um']
# self.z_encodercounts_per_um = self.cfg.xyz_galil_parameters['z_encodercounts_per_um']
# ''' Setting up the Galil stages '''
# self.xyz_stage = StageControlGalil(self.cfg.xyz_galil_parameters['port'], [self.x_encodercounts_per_um,
# self.y_encodercounts_per_um,
# self.z_encodercounts_per_um])
# ''' PI-specific code '''
# from pipython import GCSDevice, pitools
# self.pitools = pitools
# ''' Setting up the PI stages '''
# self.pi = self.cfg.pi_parameters
# self.controllername = self.cfg.pi_parameters['controllername']
# self.pi_stages = list(self.cfg.pi_parameters['stages'])
# # ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE')
# self.refmode = self.cfg.pi_parameters['refmode']
# # self.serialnum = ('118015439') # Wyss Geneva
# self.serialnum = self.cfg.pi_parameters['serialnum'] # UZH Irchel H45
# self.pidevice = GCSDevice(self.controllername)
# self.pidevice.ConnectUSB(serialnum=self.serialnum)
# ''' PI startup '''
# ''' with refmode enabled: pretty dangerous
# pitools.startup(self.pidevice, stages=self.pi_stages, refmode=self.refmode)
# '''
# pitools.startup(self.pidevice, stages=self.pi_stages)
# ''' Setting PI velocities '''
# self.pidevice.VEL(self.cfg.pi_parameters['velocity'])
# ''' Stage 5 referencing hack '''
# # print('Referencing status 3: ', self.pidevice.qFRF(3))
# # print('Referencing status 5: ', self.pidevice.qFRF(5))
# self.pidevice.FRF(5)
# print('M-406 Emergency referencing hack: Waiting for referencing move')
# logger.info('M-406 Emergency referencing hack: Waiting for referencing move')
# self.block_till_controller_is_ready()
# print('M-406 Emergency referencing hack done')
# logger.info('M-406 Emergency referencing hack done')
# # print('Again: Referencing status 3: ', self.pidevice.qFRF(3))
# # print('Again: Referencing status 5: ', self.pidevice.qFRF(5))
# def __del__(self):
# try:
# '''Close the Galil connection'''
# self.xyz_stage.close()
# self.f_stage.close_stage()
# except:
# pass
# def report_position(self):
# positions = self.pidevice.qPOS(self.pidevice.axes)
# '''
# Ugly workaround to deal with non-responding stage
# position reports: Do not update positions in
# exceptional circumstances.
# '''
# self.x_pos, self.y_pos, self.z_pos = self.xyz_stage.read_position()
# self.f_pos = round(positions['5'] * 1000, 2)
# self.theta_pos = positions['6']
# self.create_position_dict()
# self.int_x_pos = self.x_pos + self.int_x_pos_offset
# self.int_y_pos = self.y_pos + self.int_y_pos_offset
# self.int_z_pos = self.z_pos + self.int_z_pos_offset
# self.int_f_pos = self.f_pos + self.int_f_pos_offset
# self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
# self.create_internal_position_dict()
# self.state['position'] = self.int_position_dict
# self.sig_position.emit(self.int_position_dict)
# # print(self.int_position_dict)
# def move_relative(self, sdict, wait_until_done=False):
# ''' Galil move relative method
# Lots of implementation details in here, should be replaced by a facade
# '''
# xyz_motion_dict = {}
# if 'x_rel' in sdict:
# x_rel = sdict['x_rel']
# if self.x_min < self.x_pos + x_rel and self.x_max > self.x_pos + x_rel:
# xyz_motion_dict.update({1: int(x_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!')
# if 'y_rel' in sdict:
# y_rel = sdict['y_rel']
# if self.y_min < self.y_pos + y_rel and self.y_max > self.y_pos + y_rel:
# xyz_motion_dict.update({2: int(y_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!')
# if 'z_rel' in sdict:
# z_rel = sdict['z_rel']
# if self.z_min < self.z_pos + z_rel and self.z_max > self.z_pos + z_rel:
# xyz_motion_dict.update({3: int(z_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!')
# if xyz_motion_dict != {}:
# self.xyz_stage.move_relative(xyz_motion_dict)
# if 'theta_rel' in sdict:
# theta_rel = sdict['theta_rel']
# if self.theta_min < self.theta_pos + theta_rel and self.theta_max > self.theta_pos + theta_rel:
# self.pidevice.MVR({6: theta_rel})
# else:
# self.sig_status_message.emit('Relative movement stopped: theta Motion limit would be reached!')
# if 'f_rel' in sdict:
# f_rel = sdict['f_rel']
# if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
# f_rel = f_rel / 1000
# self.pidevice.MVR({5: f_rel})
# else:
# self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!')
# if wait_until_done == True:
# self.xyz_stage.wait_until_done('XYZ')
# self.pitools.waitontarget(self.pidevice)
# def move_absolute(self, dict, wait_until_done=False):
# '''
# Galil move absolute method
# Lots of implementation details in here, should be replaced by a facade
# '''
# xyz_motion_dict = {}
# if 'x_abs' or 'y_abs' or 'z_abs' in dict:
# if 'x_abs' in dict:
# x_abs = dict['x_abs']
# x_abs = x_abs - self.int_x_pos_offset
# xyz_motion_dict.update({1: x_abs})
# if 'y_abs' in dict:
# y_abs = dict['y_abs']
# y_abs = y_abs - self.int_y_pos_offset
# xyz_motion_dict.update({2: y_abs})
# if 'z_abs' in dict:
# z_abs = dict['z_abs']
# z_abs = z_abs - self.int_z_pos_offset
# xyz_motion_dict.update({3: z_abs})
# if xyz_motion_dict != {}:
# self.xyz_stage.move_absolute(xyz_motion_dict)
# if wait_until_done == True:
# self.xyz_stage.wait_until_done('XYZ')
# if 'f_abs' in dict:
# f_abs = dict['f_abs']
# f_abs = f_abs - self.int_f_pos_offset
# if self.f_min < f_abs and self.f_max > f_abs:
# ''' Conversion to mm and command emission'''
# f_abs = f_abs / 1000
# self.pidevice.MOV({5: f_abs})
# else:
# self.sig_status_message.emit('Absolute movement stopped: F Motion limit would be reached!')
# if 'theta_abs' in dict:
# theta_abs = dict['theta_abs']
# theta_abs = theta_abs - self.int_theta_pos_offset
# if self.theta_min < theta_abs and self.theta_max > theta_abs:
# ''' No Conversion to mm !!!! and command emission'''
# self.pidevice.MOV({6: theta_abs})
# else:
# self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!')
# if wait_until_done == True:
# self.pitools.waitontarget(self.pidevice)
# def stop(self):
# self.xyz_stage.stop(restart_programs=True)
# self.pidevice.STP(noraise=True)
# def load_sample(self):
# self.xyz_stage.move_absolute(
# {1: self.int_x_pos, 2: self.cfg.stage_parameters['y_load_position'], 3: self.int_z_pos})
# def unload_sample(self):
# self.xyz_stage.move_absolute(
# {1: self.int_x_pos, 2: self.cfg.stage_parameters['y_unload_position'], 3: self.int_z_pos})
# def block_till_controller_is_ready(self):
# '''
# Blocks further execution (especially during referencing moves)
# till the PI controller returns ready
# '''
# blockflag = True
# while blockflag:
# if self.pidevice.IsControllerReady():
# blockflag = False
# else:
# time.sleep(0.1)
# def execute_program(self):
# '''Executes program stored on the Galil controller'''
# self.xyz_stage.execute_program()
# class mesoSPIM_PI_rot_and_Galil_xyzf_Stages(mesoSPIM_Stage):
# '''
# Expects following microscope configuration:
# Sample XYZ movement: Galil controller with 3 axes
# F movement: Second Galil controller with a single axis
# Rotation: PI C-863 mercury controller
# It is expected that the parent class has the following signals:
# sig_move_relative = pyqtSignal(dict)
# sig_move_relative_and_wait_until_done = pyqtSignal(dict)
# sig_move_absolute = pyqtSignal(dict)
# sig_move_absolute_and_wait_until_done = pyqtSignal(dict)
# sig_zero = pyqtSignal(list)
# sig_unzero = pyqtSignal(list)
# sig_stop_movement = pyqtSignal()
# Also contains a QTimer that regularily sends position updates, e.g
# during the execution of movements.
# '''
# def __init__(self, parent=None):
# super().__init__(parent)
# self.state = mesoSPIM_StateSingleton()
# self.pos_timer = QtCore.QTimer(self)
# self.pos_timer.timeout.connect(self.report_position)
# self.pos_timer.start(50)
# '''
# Galil-specific code
# '''
# from .devices.stages.galil.galilcontrol import StageControlGalil
# self.x_encodercounts_per_um = self.cfg.xyz_galil_parameters['x_encodercounts_per_um']
# self.y_encodercounts_per_um = self.cfg.xyz_galil_parameters['y_encodercounts_per_um']
# self.z_encodercounts_per_um = self.cfg.xyz_galil_parameters['z_encodercounts_per_um']
# self.f_encodercounts_per_um = self.cfg.f_galil_parameters['f_encodercounts_per_um']
# ''' Setting up the Galil stages: XYZ '''
# self.xyz_stage = StageControlGalil(self.cfg.xyz_galil_parameters['port'], [self.x_encodercounts_per_um,
# self.y_encodercounts_per_um,
# self.z_encodercounts_per_um])
# ''' Setting up the Galil stages: F with two dummy axes.'''
# self.f_stage = StageControlGalil(self.cfg.f_galil_parameters['port'], [self.x_encodercounts_per_um,
# self.y_encodercounts_per_um,
# self.f_encodercounts_per_um])
# '''
# self.f_stage = StageControlGalil(COMport = self.cfg.f_galil_parameters['COMport'],
# x_encodercounts_per_um = 0,
# y_encodercounts_per_um = 0,
# z_encodercounts_per_um = self.f_encodercounts_per_um)
# '''
# '''
# print('Galil: ', self.xyz_stage.read_position('x'))
# print('Galil: ', self.xyz_stage.read_position('y'))
# print('Galil: ', self.xyz_stage.read_position('z'))
# '''
# ''' PI-specific code '''
# from pipython import GCSDevice, pitools
# self.pitools = pitools
# ''' Setting up the PI stages '''
# self.pi = self.cfg.pi_parameters
# self.controllername = self.cfg.pi_parameters['controllername']
# self.pi_stages = self.cfg.pi_parameters['stages']
# # ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE')
# self.refmode = self.cfg.pi_parameters['refmode']
# # self.serialnum = ('118015439') # Wyss Geneva
# self.serialnum = self.cfg.pi_parameters['serialnum'] # UZH Irchel H45
# self.pidevice = GCSDevice(self.controllername)
# self.pidevice.ConnectUSB(serialnum=self.serialnum)
# ''' PI startup '''
# ''' with refmode enabled: pretty dangerous
# pitools.startup(self.pidevice, stages=self.pi_stages, refmode=self.refmode)
# '''
# pitools.startup(self.pidevice, stages=self.pi_stages)
# ''' Setting PI velocities '''
# self.pidevice.VEL(self.cfg.pi_parameters['velocity'])
# self.pidevice.FRF(1)
# print('M-061 Emergency referencing hack: Waiting for referencing move')
# logger.info('M-061 Emergency referencing hack: Waiting for referencing move')
# self.block_till_controller_is_ready()
# print('M-061 Emergency referencing hack done')
# logger.info('M-061 Emergency referencing hack done')
# def __del__(self):
# try:
# '''Close the Galil connection'''
# self.xyz_stage.close()
# self.f_stage.close_stage()
# except:
# pass
# def report_position(self):
# positions = self.pidevice.qPOS(self.pidevice.axes)
# '''
# Ugly workaround to deal with non-responding stage
# position reports: Do not update positions in
# exceptional circumstances.
# '''
# try:
# self.x_pos, self.y_pos, self.z_pos = self.xyz_stage.read_position()
# _, _, self.f_pos = self.f_stage.read_position()
# except:
# logger.info('Error while unpacking Galil stage position values')
# self.theta_pos = positions['1']
# self.create_position_dict()
# self.int_x_pos = self.x_pos + self.int_x_pos_offset
# self.int_y_pos = self.y_pos + self.int_y_pos_offset
# self.int_z_pos = self.z_pos + self.int_z_pos_offset
# self.int_f_pos = self.f_pos + self.int_f_pos_offset
# self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
# self.create_internal_position_dict()
# self.sig_position.emit(self.int_position_dict)
# # print(self.int_position_dict)
# def move_relative(self, sdict, wait_until_done=False):
# ''' Galil move relative method
# Lots of implementation details in here, should be replaced by a facade
# '''
# xyz_motion_dict = {}
# if 'x_rel' in sdict:
# x_rel = sdict['x_rel']
# if self.x_min < self.x_pos + x_rel and self.x_max > self.x_pos + x_rel:
# xyz_motion_dict.update({1: int(x_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!')
# if 'y_rel' in sdict:
# y_rel = sdict['y_rel']
# if self.y_min < self.y_pos + y_rel and self.y_max > self.y_pos + y_rel:
# xyz_motion_dict.update({2: int(y_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!')
# if 'z_rel' in sdict:
# z_rel = sdict['z_rel']
# if self.z_min < self.z_pos + z_rel and self.z_max > self.z_pos + z_rel:
# xyz_motion_dict.update({3: int(z_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!')
# if xyz_motion_dict != {}:
# self.xyz_stage.move_relative(xyz_motion_dict)
# if 'theta_rel' in sdict:
# theta_rel = sdict['theta_rel']
# if self.theta_min < self.theta_pos + theta_rel and self.theta_max > self.theta_pos + theta_rel:
# self.pidevice.MVR({1: theta_rel})
# else:
# self.sig_status_message.emit('Relative movement stopped: theta Motion limit would be reached!')
# if 'f_rel' in sdict:
# f_rel = sdict['f_rel']
# if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
# self.f_stage.move_relative({3: int(f_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!')
# if wait_until_done == True:
# self.f_stage.wait_until_done('Z')
# self.xyz_stage.wait_until_done('XYZ')
# self.pitools.waitontarget(self.pidevice)
# def move_absolute(self, dict, wait_until_done=False):
# '''
# Galil move absolute method
# Lots of implementation details in here, should be replaced by a facade
# '''
# xyz_motion_dict = {}
# if 'x_abs' or 'y_abs' or 'z_abs' in dict:
# if 'x_abs' in dict:
# x_abs = dict['x_abs']
# x_abs = x_abs - self.int_x_pos_offset
# xyz_motion_dict.update({1: x_abs})
# if 'y_abs' in dict:
# y_abs = dict['y_abs']
# y_abs = y_abs - self.int_y_pos_offset
# xyz_motion_dict.update({2: y_abs})
# if 'z_abs' in dict:
# z_abs = dict['z_abs']
# z_abs = z_abs - self.int_z_pos_offset
# xyz_motion_dict.update({3: z_abs})
# if xyz_motion_dict != {}:
# self.xyz_stage.move_absolute(xyz_motion_dict)
# if wait_until_done == True:
# self.xyz_stage.wait_until_done('XYZ')
# if 'f_abs' in dict:
# f_abs = dict['f_abs']
# f_abs = f_abs - self.int_f_pos_offset
# if self.f_min < f_abs and self.f_max > f_abs:
# ''' Conversion to mm and command emission'''
# self.f_stage.move_absolute({3: int(f_abs)})
# else:
# self.sig_status_message.emit('Absolute movement stopped: F Motion limit would be reached!')
# if 'theta_abs' in dict:
# theta_abs = dict['theta_abs']
# theta_abs = theta_abs - self.int_theta_pos_offset
# if self.theta_min < theta_abs and self.theta_max > theta_abs:
# ''' No Conversion to mm !!!! and command emission'''
# self.pidevice.MOV({1: theta_abs})
# else:
# self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!')
# if wait_until_done == True:
# self.pitools.waitontarget(self.pidevice)
# def stop(self):
# self.f_stage.stop(restart_programs=True)
# self.xyz_stage.stop(restart_programs=True)
# self.pidevice.STP(noraise=True)
# def load_sample(self):
# self.move_absolute({'y_abs': self.cfg.stage_parameters['y_load_position']})
# def unload_sample(self):
# self.move_absolute({'y_abs': self.cfg.stage_parameters['y_unload_position']})
# def block_till_controller_is_ready(self):
# '''
# Blocks further execution (especially during referencing moves)
# till the PI controller returns ready
# '''
# blockflag = True
# while blockflag:
# if self.pidevice.IsControllerReady():
# blockflag = False
# else:
# time.sleep(0.1)
# def execute_program(self):
# '''Executes program stored on the Galil controller'''
# self.f_stage.execute_program()
# self.xyz_stage.execute_program()
[docs]
class mesoSPIM_PI_rotz_and_Galil_xyf_Stages(mesoSPIM_Stage):
'''
Deprecated?
Expects following microscope configuration:
Sample XYF movement: Galil controller with 3 axes
Z-Movement and Rotation: PI C-884 mercury controller
'''
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
self.state = self.parent.state # mesoSPIM_StateSingleton()
self.pos_timer = QtCore.QTimer(self)
self.pos_timer.timeout.connect(self.report_position)
self.pos_timer.start(100) # previously 50, but this was too fast, a lot of unnecessary updates
'''
Galil-specific code
'''
from .devices.stages.galil.galilcontrol import StageControlGalil
self.x_encodercounts_per_um = self.cfg.xyf_galil_parameters['x_encodercounts_per_um']
self.y_encodercounts_per_um = self.cfg.xyf_galil_parameters['y_encodercounts_per_um']
self.f_encodercounts_per_um = self.cfg.xyf_galil_parameters['f_encodercounts_per_um']
''' Setting up the Galil stages: XYZ '''
self.xyf_stage = StageControlGalil(self.cfg.xyf_galil_parameters['port'], [self.x_encodercounts_per_um,
self.y_encodercounts_per_um,
self.f_encodercounts_per_um])
''' PI-specific code '''
from pipython import GCSDevice, pitools
self.pitools = pitools
''' Setting up the PI stages '''
self.pi = self.cfg.pi_parameters
self.controllername = self.cfg.pi_parameters['controllername']
self.pi_stages = list(self.cfg.pi_parameters['stages'])
# ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE')
self.refmode = self.cfg.pi_parameters['refmode']
# self.serialnum = ('118015439') # Wyss Geneva
self.serialnum = self.cfg.pi_parameters['serialnum'] # UZH Irchel H45
self.pidevice = GCSDevice(self.controllername)
self.pidevice.ConnectUSB(serialnum=self.serialnum)
''' PI startup '''
''' with refmode enabled: pretty dangerous
pitools.startup(self.pidevice, stages=self.pi_stages, refmode=self.refmode)
'''
pitools.startup(self.pidevice, stages=self.pi_stages)
''' Setting PI velocities '''
self.pidevice.VEL(self.cfg.pi_parameters['velocity'])
''' Reference movements '''
print('M-406 Emergency referencing hack: Waiting for referencing move')
logger.info('M-406 Emergency referencing hack: Waiting for referencing move')
self.pidevice.FRF(2)
print('M-406 Emergency referencing hack done')
logger.info('M-406 Emergency referencing hack done')
def __del__(self):
try:
'''Close the Galil connection'''
self.xyf_stage.close()
except:
pass
[docs]
def report_position(self):
positions = self.pidevice.qPOS(self.pidevice.axes)
'''
Ugly workaround to deal with non-responding stage
position reports: Do not update positions in
exceptional circumstances.
'''
try:
self.x_pos, self.y_pos, self.f_pos = self.xyf_stage.read_position()
except:
logger.info('Error while unpacking Galil stage position values')
self.z_pos = round(positions['2'] * 1000, 2)
self.theta_pos = positions['1']
self.create_position_dict()
self.int_x_pos = self.x_pos + self.int_x_pos_offset
self.int_y_pos = self.y_pos + self.int_y_pos_offset
self.int_z_pos = self.z_pos + self.int_z_pos_offset
self.int_f_pos = self.f_pos + self.int_f_pos_offset
self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
# print(self.int_position_dict)
[docs]
def move_relative(self, sdict, wait_until_done=False):
''' Galil move relative method
Lots of implementation details in here, should be replaced by a facade
'''
xyf_motion_dict = {}
if 'x_rel' in sdict:
x_rel = sdict['x_rel']
if self.x_min < self.x_pos + x_rel and self.x_max > self.x_pos + x_rel:
xyf_motion_dict.update({1: int(x_rel)})
else:
self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!')
if 'y_rel' in sdict:
y_rel = sdict['y_rel']
if self.y_min < self.y_pos + y_rel and self.y_max > self.y_pos + y_rel:
xyf_motion_dict.update({2: int(y_rel)})
else:
self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!')
if 'z_rel' in sdict:
z_rel = sdict['z_rel']
if self.z_min < self.z_pos + z_rel and self.z_max > self.z_pos + z_rel:
z_rel = z_rel / 1000
self.pidevice.MVR({2: z_rel})
else:
self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!')
if 'theta_rel' in sdict:
theta_rel = sdict['theta_rel']
if self.theta_min < self.theta_pos + theta_rel and self.theta_max > self.theta_pos + theta_rel:
self.pidevice.MVR({1: theta_rel})
else:
self.sig_status_message.emit('Relative movement stopped: theta Motion limit would be reached!')
if 'f_rel' in sdict:
f_rel = sdict['f_rel']
if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
xyf_motion_dict.update({3: int(f_rel)})
else:
self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!')
if xyf_motion_dict != {}:
self.xyf_stage.move_relative(xyf_motion_dict)
if wait_until_done == True:
self.xyf_stage.wait_until_done('XYZ')
self.pitools.waitontarget(self.pidevice)
[docs]
def move_absolute(self, dict, wait_until_done=False, use_internal_position=True):
'''
Galil move absolute method
Lots of implementation details in here, should be replaced by a facade
'''
if use_internal_position is True:
x_offset = self.int_x_pos_offset
y_offset = self.int_y_pos_offset
z_offset = self.int_z_pos_offset
f_offset = self.int_f_pos_offset
theta_offset = self.int_theta_pos_offset
else:
x_offset = 0
y_offset = 0
z_offset = 0
f_offset = 0
theta_offset = 0
xyf_motion_dict = {}
if 'x_abs' or 'y_abs' or 'f_abs' in dict:
if 'x_abs' in dict:
x_abs = dict['x_abs'] - x_offset
xyf_motion_dict.update({1: x_abs})
if 'y_abs' in dict:
y_abs = dict['y_abs'] - y_offset
xyf_motion_dict.update({2: y_abs})
if 'f_abs' in dict:
f_abs = dict['f_abs'] - f_offset
xyf_motion_dict.update({3: f_abs})
if xyf_motion_dict != {}:
self.xyf_stage.move_absolute(xyf_motion_dict)
if wait_until_done == True:
self.xyf_stage.wait_until_done('XYZ')
if 'z_abs' in dict:
z_abs = dict['z_abs'] - z_offset
if self.z_min < z_abs and self.z_max > z_abs:
''' Conversion to mm and command emission'''
z_abs = z_abs / 1000
self.pidevice.MOV({2: z_abs})
else:
self.sig_status_message.emit('Absolute movement stopped: Z Motion limit would be reached!')
if 'theta_abs' in dict:
theta_abs = dict['theta_abs'] - theta_offset
if self.theta_min < theta_abs and self.theta_max > theta_abs:
''' No Conversion to mm !!!! and command emission'''
self.pidevice.MOV({1: theta_abs})
else:
self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!')
if wait_until_done == True:
self.xyf_stage.wait_until_done('XYZ')
self.pitools.waitontarget(self.pidevice)
[docs]
def stop(self):
self.xyf_stage.stop(restart_programs=True)
self.pidevice.STP(noraise=True)
[docs]
def load_sample(self):
self.xyf_stage.move_absolute({2: self.cfg.stage_parameters['y_load_position']})
[docs]
def unload_sample(self):
self.xyf_stage.move_absolute({2: self.cfg.stage_parameters['y_unload_position']})
[docs]
def block_till_controller_is_ready(self):
'''
Blocks further execution (especially during referencing moves)
till the PI controller returns ready
'''
blockflag = True
while blockflag:
if self.pidevice.IsControllerReady():
blockflag = False
else:
time.sleep(0.1)
[docs]
def execute_program(self):
'''Executes program stored on the Galil controller'''
self.xyf_stage.execute_program()
# class mesoSPIM_PI_rotzf_and_Galil_xy_Stages(mesoSPIM_Stage):
# '''
# Deprecated?
# Expects following microscope configuration:
# Sample XY movement: Galil controller with 2 axes
# Z-Movement, F-Movement and Rotation: PI C-884 mercury controller
# '''
# def __init__(self, parent=None):
# super().__init__(parent)
# self.pos_timer = QtCore.QTimer(self)
# self.pos_timer.timeout.connect(self.report_position)
# self.pos_timer.start(50)
# '''
# Galil-specific code
# '''
# from .devices.stages.galil.galilcontrol import StageControlGalil
# self.x_encodercounts_per_um = self.cfg.xy_galil_parameters['x_encodercounts_per_um']
# self.y_encodercounts_per_um = self.cfg.xy_galil_parameters['y_encodercounts_per_um']
# ''' Setting up the Galil stages: XYZ '''
# self.xy_stage = StageControlGalil(self.cfg.xy_galil_parameters['port'], [self.x_encodercounts_per_um,
# self.y_encodercounts_per_um])
# ''' PI-specific code '''
# from pipython import GCSDevice, pitools
# self.pitools = pitools
# ''' Setting up the PI stages '''
# self.pi = self.cfg.pi_parameters
# self.controllername = self.cfg.pi_parameters['controllername']
# self.pi_stages = list(self.cfg.pi_parameters['stages'])
# # ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','NOSTAGE')
# self.refmode = self.cfg.pi_parameters['refmode']
# self.serialnum = self.cfg.pi_parameters['serialnum']
# self.pidevice = GCSDevice(self.controllername)
# self.pidevice.ConnectUSB(serialnum=self.serialnum)
# ''' PI startup '''
# ''' with refmode enabled: pretty dangerous
# pitools.startup(self.pidevice, stages=self.pi_stages, refmode=self.refmode)
# '''
# pitools.startup(self.pidevice, stages=self.pi_stages)
# ''' Setting PI velocities '''
# self.pidevice.VEL(self.cfg.pi_parameters['velocity'])
# print('M-406 Emergency referencing hack: Waiting for referencing move')
# logger.info('M-406 Emergency referencing hack: Waiting for referencing move')
# self.pidevice.FRF(2)
# print('M-406 Emergency referencing hack done')
# logger.info('M-406 Emergency referencing hack done')
# print('M-605.2DD Emergency referencing hack: Waiting for referencing move')
# logger.info('M-605.2DD Emergency referencing hack: Waiting for referencing move')
# self.pidevice.FRF(3)
# print('M-605.2DD Emergency referencing hack done')
# logger.info('M-605.2DD Emergency referencing hack done')
# self.block_till_controller_is_ready()
# def __del__(self):
# try:
# '''Close the Galil connection'''
# self.xy_stage.close()
# except:
# pass
# def report_position(self):
# positions = self.pidevice.qPOS(self.pidevice.axes)
# '''
# Ugly workaround to deal with non-responding stage
# position reports: Do not update positions in
# exceptional circumstances.
# '''
# try:
# self.x_pos, self.y_pos = self.xy_stage.read_position()
# except:
# logger.info('Error while unpacking Galil stage position values')
# self.f_pos = round(positions['3'] * 1000, 2)
# self.z_pos = round(positions['2'] * 1000, 2)
# self.theta_pos = positions['1']
# self.create_position_dict()
# self.int_x_pos = self.x_pos + self.int_x_pos_offset
# self.int_y_pos = self.y_pos + self.int_y_pos_offset
# self.int_z_pos = self.z_pos + self.int_z_pos_offset
# self.int_f_pos = self.f_pos + self.int_f_pos_offset
# self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
# self.create_internal_position_dict()
# self.sig_position.emit(self.int_position_dict)
# # print(self.int_position_dict)
# def move_relative(self, sdict, wait_until_done=False):
# ''' Galil move relative method
# Lots of implementation details in here, should be replaced by a facade
# '''
# xy_motion_dict = {}
# if 'x_rel' in sdict:
# x_rel = sdict['x_rel']
# if self.x_min < self.x_pos + x_rel and self.x_max > self.x_pos + x_rel:
# xy_motion_dict.update({1: int(x_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!')
# if 'y_rel' in sdict:
# y_rel = sdict['y_rel']
# if self.y_min < self.y_pos + y_rel and self.y_max > self.y_pos + y_rel:
# xy_motion_dict.update({2: int(y_rel)})
# else:
# self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!')
# if 'z_rel' in sdict:
# z_rel = sdict['z_rel']
# if self.z_min < self.z_pos + z_rel and self.z_max > self.z_pos + z_rel:
# z_rel = z_rel / 1000
# self.pidevice.MVR({2: z_rel})
# else:
# self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!')
# if 'theta_rel' in sdict:
# theta_rel = sdict['theta_rel']
# if self.theta_min < self.theta_pos + theta_rel and self.theta_max > self.theta_pos + theta_rel:
# self.pidevice.MVR({1: theta_rel})
# else:
# self.sig_status_message.emit('Relative movement stopped: theta Motion limit would be reached!')
# if 'f_rel' in sdict:
# f_rel = sdict['f_rel']
# if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
# f_rel = f_rel / 1000
# self.pidevice.MVR({3: f_rel})
# else:
# self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!')
# if xy_motion_dict != {}:
# self.xy_stage.move_relative(xy_motion_dict)
# if wait_until_done == True:
# self.xy_stage.wait_until_done('XY')
# self.pitools.waitontarget(self.pidevice)
# def move_absolute(self, dict, wait_until_done=False):
# '''
# Galil move absolute method
# Lots of implementation details in here, should be replaced by a facade
# '''
# xy_motion_dict = {}
# if 'x_abs' or 'y_abs' in dict:
# if 'x_abs' in dict:
# x_abs = dict['x_abs']
# x_abs = x_abs - self.int_x_pos_offset
# xy_motion_dict.update({1: x_abs})
# if 'y_abs' in dict:
# y_abs = dict['y_abs']
# y_abs = y_abs - self.int_y_pos_offset
# xy_motion_dict.update({2: y_abs})
# if xy_motion_dict != {}:
# self.xy_stage.move_absolute(xy_motion_dict)
# if wait_until_done == True:
# self.xy_stage.wait_until_done('XYZ')
# if 'f_abs' in dict:
# f_abs = dict['f_abs']
# f_abs = f_abs - self.int_f_pos_offset
# if self.f_min < f_abs and self.f_max > f_abs:
# ''' Conversion to mm and command emission'''
# f_abs = f_abs / 1000
# self.pidevice.MOV({3: f_abs})
# else:
# self.sig_status_message.emit('Absolute movement stopped: F Motion limit would be reached!')
# if 'z_abs' in dict:
# z_abs = dict['z_abs']
# z_abs = z_abs - self.int_z_pos_offset
# if self.z_min < z_abs and self.z_max > z_abs:
# ''' Conversion to mm and command emission'''
# z_abs = z_abs / 1000
# self.pidevice.MOV({2: z_abs})
# else:
# self.sig_status_message.emit('Absolute movement stopped: Z Motion limit would be reached!')
# if 'theta_abs' in dict:
# theta_abs = dict['theta_abs']
# theta_abs = theta_abs - self.int_theta_pos_offset
# if self.theta_min < theta_abs and self.theta_max > theta_abs:
# ''' No Conversion to mm !!!! and command emission'''
# self.pidevice.MOV({1: theta_abs})
# else:
# self.sig_status_message.emit('Absolute movement stopped: Theta Motion limit would be reached!')
# if wait_until_done == True:
# self.xy_stage.wait_until_done('XY')
# self.pitools.waitontarget(self.pidevice)
# def stop(self):
# self.xy_stage.stop(restart_programs=True)
# self.pidevice.STP(noraise=True)
# def load_sample(self):
# self.xy_stage.move_absolute({2: self.cfg.stage_parameters['y_load_position']})
# def unload_sample(self):
# self.xy_stage.move_absolute({2: self.cfg.stage_parameters['y_unload_position']})
# def block_till_controller_is_ready(self):
# '''
# Blocks further execution (especially during referencing moves)
# till the PI controller returns ready
# '''
# blockflag = True
# while blockflag:
# if self.pidevice.IsControllerReady():
# blockflag = False
# else:
# time.sleep(0.1)
# def execute_program(self):
# '''Executes program stored on the Galil controller'''
# self.xy_stage.execute_program()
[docs]
class mesoSPIM_ASI_Tiger_Stage(mesoSPIM_Stage):
'''
It is expected that the parent class has the following signals:
sig_move_relative = pyqtSignal(dict)
sig_move_relative_and_wait_until_done = pyqtSignal(dict)
sig_move_absolute = pyqtSignal(dict)
sig_move_absolute_and_wait_until_done = pyqtSignal(dict)
sig_zero = pyqtSignal(list)
sig_unzero = pyqtSignal(list)
sig_stop_movement = pyqtSignal()
Also contains a QTimer that regularily sends position updates, e.g
during the execution of movements.
'''
sig_pause = QtCore.pyqtSignal(bool)
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
self.state = self.parent.state # mesoSPIM_StateSingleton()
from .devices.stages.asi.asicontrol import StageControlASITiger
''' Setting up the ASI stages '''
self.asi_parameters = self.cfg.asi_parameters
self.mesoSPIM2ASIdict = self.asi_parameters['stage_assignment'] # converts mesoSPIM stage to ASI stage designation
# self.ASI2mesoSPIMdict = {self.mesoSPIM2ASIdict[item] : item for item in self.mesoSPIM2ASIdict} # converts ASI stage designation to mesoSPIM
self.ttl_cards = self.asi_parameters['ttl_cards']
self.asi_stages = StageControlASITiger(self.asi_parameters)
self.asi_stages.sig_pause.connect(self.pause)
assert hasattr(self.cfg, 'asi_parameters'), "Config file with stage 'TigerASI' must have 'asi_parameters' dict."
self.ttl_motion_enabled_during_acq = self.cfg.asi_parameters['ttl_motion_enabled']
self.ttl_motion_currently_enabled = False
self.set_speed_from_config()
self.pos_timer.setInterval(250)
logger.info('ASI stages initialized')
def __del__(self):
try:
self.asi_stages.close()
except:
pass
[docs]
def set_speed_from_config(self):
if hasattr(self.cfg, 'asi_parameters') and 'speed' in self.cfg.asi_parameters.keys():
command = 'S'
for axis, speed in self.cfg.asi_parameters['speed'].items():
if self.asi_stages.axis_in_config_check(axis):
command += ' ' + axis + '=' + str(speed)
else:
logger.error(f'Axis {axis} not in the axes list, check config file for ASI stages')
command += '\r'
self.asi_stages._send_command(command.encode('ascii'))
else:
print("INFO: 'speed' not found in config file, 'asi_parameters' dictionary, using default values.")
@QtCore.pyqtSlot(bool)
def pause(self, boolean):
state = self.state['state']
if state == 'run_selected_acquisition' or state == 'run_acquisition_list':
self.sig_pause.emit(boolean)
@QtCore.pyqtSlot(dict)
def log_slice(self, dictionary):
slice = dictionary['current_image_in_acq']
self.asi_stages.current_z_slice = slice
[docs]
def report_position(self):
position_dict = self.asi_stages.read_position()
if position_dict is not None:
self.x_pos = position_dict[self.mesoSPIM2ASIdict['x']]
self.y_pos = position_dict[self.mesoSPIM2ASIdict['y']]
self.z_pos = position_dict[self.mesoSPIM2ASIdict['z']]
self.f_pos = position_dict[self.mesoSPIM2ASIdict['f']]
self.theta_pos = position_dict[self.mesoSPIM2ASIdict['theta']]
self.create_position_dict()
self.int_x_pos = self.x_pos + self.int_x_pos_offset
self.int_y_pos = self.y_pos + self.int_y_pos_offset
self.int_z_pos = self.z_pos + self.int_z_pos_offset
self.int_f_pos = self.f_pos + self.int_f_pos_offset
self.int_theta_pos = self.theta_pos + self.int_theta_pos_offset
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
[docs]
def move_relative(self, sdict, wait_until_done=False):
''' ASI move relative method
Lots of implementation details in here, should be replaced by a facade
'''
motion_dict = {}
if not self.ttl_motion_currently_enabled:
if 'x_rel' in sdict:
x_rel = sdict['x_rel']
if self.x_min < self.x_pos + x_rel < self.x_max:
motion_dict.update({self.mesoSPIM2ASIdict['x'] : round(x_rel, 1)})
else:
self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!')
if 'y_rel' in sdict:
y_rel = sdict['y_rel']
if self.y_min < self.y_pos + y_rel < self.y_max:
motion_dict.update({self.mesoSPIM2ASIdict['y'] : round(y_rel, 1)})
else:
self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!')
if 'z_rel' in sdict:
z_rel = sdict['z_rel']
if self.z_min < self.z_pos + z_rel < self.z_max:
motion_dict.update({self.mesoSPIM2ASIdict['z'] : round(z_rel, 1)})
else:
self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!')
if 'theta_rel' in sdict:
theta_rel = sdict['theta_rel']
if self.theta_min < self.theta_pos + theta_rel < self.theta_max:
''' 1° equals 1000 cts'''
motion_dict.update({self.mesoSPIM2ASIdict['theta'] : int(theta_rel)})
else:
self.sig_status_message.emit('Relative movement stopped: theta Motion limit would be reached!')
if 'f_rel' in sdict:
f_rel = sdict['f_rel']
if self.f_min < self.f_pos + f_rel < self.f_max:
motion_dict.update({self.mesoSPIM2ASIdict['f'] : round(f_rel, 1)})
else:
self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!')
if motion_dict != {}:
self.asi_stages.move_relative(motion_dict)
if wait_until_done:
self.asi_stages.wait_until_done()
[docs]
def move_absolute(self, dict, wait_until_done=False, use_internal_position=True):
'''
ASI move absolute method
Lots of implementation details in here, should be replaced by a facade
'''
if use_internal_position is True:
x_offset = self.int_x_pos_offset
y_offset = self.int_y_pos_offset
z_offset = self.int_z_pos_offset
f_offset = self.int_f_pos_offset
theta_offset = self.int_theta_pos_offset
else:
x_offset = 0
y_offset = 0
z_offset = 0
f_offset = 0
theta_offset = 0
motion_dict = {}
if 'x_abs' in dict:
x_abs = dict['x_abs'] - x_offset
if self.x_min < x_abs < self.x_max:
motion_dict.update({self.mesoSPIM2ASIdict['x'] : round(x_abs, 1)})
else:
logger.error(f"The x-move is outside of min-max range, check your config file, 'x_min' and 'x_max'.")
if 'y_abs' in dict:
y_abs = dict['y_abs'] - y_offset
if self.y_min < y_abs < self.y_max:
motion_dict.update({self.mesoSPIM2ASIdict['y'] : round(y_abs, 1)})
else:
logger.error(f"The y-move is outside of min-max range, check your config file, 'y_min' and 'y_max'.")
if 'z_abs' in dict:
z_abs = dict['z_abs'] - z_offset
if self.z_min < z_abs < self.z_max:
motion_dict.update({self.mesoSPIM2ASIdict['z'] : round(z_abs, 1)})
else:
logger.error(f"The z-move is outside of min-max range, check your config file, 'z_min' and 'z_max'.")
if 'f_abs' in dict:
f_abs = dict['f_abs'] - f_offset
if self.f_min < f_abs < self.f_max:
motion_dict.update({self.mesoSPIM2ASIdict['f'] : round(f_abs, 1)})
else:
logger.error(f"The f-move is outside of min-max range, check your config file, 'f_min' and 'f_max'.")
if 'theta_abs' in dict:
theta_abs = dict['theta_abs'] - theta_offset
if self.theta_min < theta_abs < self.theta_max:
''' 1° equals 1000 cts'''
motion_dict.update({self.mesoSPIM2ASIdict['theta'] : int(theta_abs)})
else:
logger.error(f"The theta-move is outside of min-max range, check your config file, 'theta_min' and 'theta_max'.")
if motion_dict:
self.asi_stages.move_absolute(motion_dict)
if wait_until_done is True:
self.asi_stages.wait_until_done()
[docs]
def stop(self):
self.asi_stages.stop()
[docs]
def load_sample(self):
y_abs = self.cfg.stage_parameters['y_load_position']
self.move_absolute({'y_abs':round(y_abs)})
[docs]
def unload_sample(self):
y_abs = self.cfg.stage_parameters['y_unload_position']
self.move_absolute({'y_abs':round(y_abs)})
[docs]
def enable_ttl_motion(self, boolean):
if self.ttl_motion_enabled_during_acq:
self.asi_stages.enable_ttl_mode(self.ttl_cards, boolean)
self.ttl_motion_currently_enabled = boolean
logger.info('TTL Motion currently enabled: '+str(boolean))
self.state['ttl_movement_enabled_during_acq'] = boolean
[docs]
class mesoSPIM_ASI_MS2000_Stage(mesoSPIM_Stage):
'''
It is expected that the parent class has the following signals:
sig_move_relative = pyqtSignal(dict)
sig_move_relative_and_wait_until_done = pyqtSignal(dict)
sig_move_absolute = pyqtSignal(dict)
sig_move_absolute_and_wait_until_done = pyqtSignal(dict)
sig_zero = pyqtSignal(list)
sig_unzero = pyqtSignal(list)
sig_stop_movement = pyqtSignal()
Also contains a QTimer that regularily sends position updates, e.g
during the execution of movements.
This implements an ASI MS2000 controller for a setup with the following configuration
* ASI X Stage is equivalent to the mesoSPIM z-stage (moved during stacks direction)
* ASI Y Stage is equivalent to the mesoSPIM y-stage
* ASI Z-Stage is equivalent to the mesoSPIM f-stage (focus)
'''
sig_pause = QtCore.pyqtSignal(bool)
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.state = self.parent.state # mesoSPIM_StateSingleton()
'''
ASI-specific code
'''
from devices.stages.asi.asicontrol import StageControlASITiger
''' Setting up the ASI stages '''
self.asi_parameters = self.cfg.asi_parameters
self.mesoSPIM2ASIdict = self.asi_parameters['stage_assignment'] # converts mesoSPIM stage to ASI stage designation
# self.ASI2mesoSPIMdict = {self.mesoSPIM2ASIdict[item] : item for item in self.mesoSPIM2ASIdict} # converts ASI stage designation to mesoSPIM
self.asi_stages = StageControlASITiger(self.asi_parameters)
self.asi_stages.sig_pause.connect(self.pause)
self.pos_timer.setInterval(100)
logger.info('mesoSPIM_Stages: ASI stages initialized')
def __del__(self):
try:
'''Close the ASI connection'''
self.asi_stages.close()
except:
pass
@QtCore.pyqtSlot(bool)
def pause(self,boolean):
state = self.state['state']
if state == 'run_selected_acquisition' or state == 'run_acquisition_list':
self.sig_pause.emit(boolean)
@QtCore.pyqtSlot(dict)
def log_slice(self, dictionary):
slice = dictionary['current_image_in_acq']
self.asi_stages.current_z_slice = slice
[docs]
def report_position(self):
position_dict = self.asi_stages.read_position()
if position_dict is not None:
self.y_pos = position_dict[self.mesoSPIM2ASIdict['y']]
self.z_pos = position_dict[self.mesoSPIM2ASIdict['z']]
self.f_pos = position_dict[self.mesoSPIM2ASIdict['f']]
self.create_position_dict()
self.int_y_pos = self.y_pos + self.int_y_pos_offset
self.int_z_pos = self.z_pos + self.int_z_pos_offset
self.int_f_pos = self.f_pos + self.int_f_pos_offset
self.create_internal_position_dict()
self.sig_position.emit(self.int_position_dict)
[docs]
def move_relative(self, sdict, wait_until_done=False):
''' ASI move relative method
Lots of implementation details in here, should be replaced by a facade
'''
'''
Report position
'''
#self.adapt_position_polling_interval_to_state()
motion_dict = {}
if 'y_rel' in sdict:
y_rel = sdict['y_rel']
if self.y_min < self.y_pos + y_rel and self.y_max > self.y_pos + y_rel:
motion_dict.update({self.mesoSPIM2ASIdict['y'] : round(y_rel, 1)})
else:
self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!')
if 'z_rel' in sdict:
z_rel = sdict['z_rel']
if self.z_min < self.z_pos + z_rel and self.z_max > self.z_pos + z_rel:
motion_dict.update({self.mesoSPIM2ASIdict['z'] : round(z_rel, 1)})
else:
self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!')
if 'f_rel' in sdict:
f_rel = sdict['f_rel']
if self.f_min < self.f_pos + f_rel and self.f_max > self.f_pos + f_rel:
motion_dict.update({self.mesoSPIM2ASIdict['f'] : round(f_rel, 1)})
else:
self.sig_status_message.emit('Relative movement stopped: f Motion limit would be reached!')
if motion_dict != {}:
self.asi_stages.move_relative(motion_dict)
if wait_until_done is True:
self.asi_stages.wait_until_done()
[docs]
def move_absolute(self, dict, wait_until_done=False, use_internal_position=True):
'''
ASI move absolute method
Lots of implementation details in here, should be replaced by a facade
'''
if use_internal_position is True:
x_offset = self.int_x_pos_offset
y_offset = self.int_y_pos_offset
z_offset = self.int_z_pos_offset
f_offset = self.int_f_pos_offset
theta_offset = self.int_theta_pos_offset
else:
x_offset = 0
y_offset = 0
z_offset = 0
f_offset = 0
theta_offset = 0
motion_dict = {}
if 'y_abs' in dict:
y_abs = dict['y_abs'] - y_offset
if self.y_min < y_abs and self.y_max > y_abs:
motion_dict.update({self.mesoSPIM2ASIdict['y'] : round(y_abs, 1)})
if 'z_abs' in dict:
z_abs = dict['z_abs'] - z_offset
if self.z_min < z_abs and self.z_max > z_abs:
motion_dict.update({self.mesoSPIM2ASIdict['z'] : round(z_abs, 1)})
if 'f_abs' in dict:
f_abs = dict['f_abs'] - f_offset
if self.f_min < f_abs and self.f_max > f_abs:
motion_dict.update({self.mesoSPIM2ASIdict['f'] : round(f_abs, 1)})
if motion_dict != {}:
self.asi_stages.move_absolute(motion_dict)
if wait_until_done is True:
self.asi_stages.wait_until_done()
[docs]
def stop(self):
self.asi_stages.stop()
[docs]
def load_sample(self):
message = 'ASI MS-2000 Stage: Sample loading not implemented!'
print(message)
logger.info(message)
[docs]
def unload_sample(self):
message = 'ASI MS-2000 Stage: Sample unloading not implemented!'
print(message)
logger.info(message)