# coding=utf-8
import pickle
import os
import numpy as np
from PyQt5 import QtCore
from holypipette.interface import TaskInterface, command, blocking_command
from holypipette.devices.manipulator.calibratedunit import CalibratedUnit, CalibratedStage, CalibrationConfig
import time
[docs]class PipetteInterface(TaskInterface):
'''
Controller for the stage, the microscope, and several pipettes.
'''
manipulator_switched = QtCore.pyqtSignal('QString', 'QString')
def __init__(self, stage, microscope, camera, units,
config_filename=None):
super(PipetteInterface, self).__init__()
self.microscope = microscope
self.camera = camera
# Create a common calibration configuration for all stages/manipulators
self.calibration_config = CalibrationConfig(name='Calibration')
self.calibrated_stage = CalibratedStage(stage, None, microscope, camera,
config=self.calibration_config)
self.calibrated_units = [CalibratedUnit(unit,
self.calibrated_stage,
microscope,
camera,
config=self.calibration_config)
for unit in units]
# This should be refactored (in TaskInterface?)
config_folder = os.path.join(os.path.expanduser('~'),'holypipette')
if not os.path.exists(config_folder):
os.mkdir(config_folder)
if config_filename is None:
config_filename = 'config_manipulator.cfg'
config_filename = os.path.join(config_folder,config_filename)
self.config_filename = config_filename
self.current_unit = 0
self.calibrated_unit = None
self.cleaning_bath_position = None
self.contact_position = None
self.rinsing_bath_position = None
self.paramecium_tank_position = None
self.timer_t0 = time.time()
[docs] def connect(self, main_gui):
self.manipulator_switched.connect(main_gui.set_status_message)
self.switch_manipulator(1)
# We call this via command_received to catch errors automatically
self.command_received(self.load_configuration, None)
[docs] @command(category='Manipulators',
description='Measure manipulator ranges')
def measure_ranges(self):
'''
This is called every 500 ms when measuring ranges.
It updates the min and max on each axis.
'''
for i,calibrated_unit in enumerate(self.calibrated_units):
position = calibrated_unit.position()
calibrated_unit.min = np.array([position,calibrated_unit.min]).min(axis=0)
calibrated_unit.max = np.array([position,calibrated_unit.max]).max(axis=0)
self.info('Unit {} min = {}, max={}'.format(i,list(calibrated_unit.min),list(calibrated_unit.max)))
position = self.calibrated_stage.position()
self.calibrated_stage.min = np.array([position, self.calibrated_stage.min]).min(axis=0)
self.calibrated_stage.max = np.array([position, self.calibrated_stage.max]).max(axis=0)
self.info('Stage min = {}, max={}'.format(list(self.calibrated_stage.min), list(self.calibrated_stage.max)))
position = self.microscope.position()
self.microscope.min = min(self.microscope.min, position)
self.microscope.max = max(self.microscope.max, position)
self.info('Microscope min = {}, max={}'.format(self.microscope.min, self.microscope.max))
[docs] @command(category='Manipulators',
description='Reset manipulator ranges')
def reset_ranges(self):
for calibrated_unit in self.calibrated_units:
calibrated_unit.min = np.ones(len(calibrated_unit.min))*1e6
calibrated_unit.max = -np.ones(len(calibrated_unit.max))*1e6
self.calibrated_stage.min = np.ones(len(self.calibrated_stage.min))*1e6
self.calibrated_stage.max = -np.ones(len(self.calibrated_stage.max))*1e6
self.microscope.min = 1e6
self.microscope.max = -1e6
[docs] @command(category='Manipulators',
description='Check manipulator ranges')
def check_ranges(self):
for i,calibrated_unit in enumerate(self.calibrated_units):
print(calibrated_unit.min,calibrated_unit.max)
if ((calibrated_unit.max-calibrated_unit.min)<500.).any():
self.debug("Ranges of unit "+str(i)+" might not have been updated")
print(self.calibrated_stage.min,self.calibrated_stage.max)
if ((self.calibrated_stage.max - self.calibrated_stage.min) < 500.).any():
self.debug("Ranges of the stage might not have been updated")
print(self.microscope.min,self.microscope.max)
if self.microscope.max-self.microscope.min<500.:
self.debug("Ranges of the microscope might not have been updated")
[docs] @command(category='Manipulators',
description='Move pipette in x direction by {:.0f}μm',
default_arg=10)
def move_pipette_x(self, distance):
self.calibrated_unit.relative_move(distance, axis=0)
[docs] @command(category='Manipulators',
description='Move pipette in y direction by {:.0f}μm',
default_arg=10)
def move_pipette_y(self, distance):
self.calibrated_unit.relative_move(distance, axis=1)
[docs] @command(category='Manipulators',
description='Move pipette in z direction by {:.0f}μm',
default_arg=10)
def move_pipette_z(self, distance):
self.calibrated_unit.relative_move(distance, axis=2)
[docs] @command(category='Microscope',
description='Move microscope by {:.0f}μm',
default_arg=10)
def move_microscope(self, distance):
self.microscope.relative_move(distance)
[docs] @command(category='Microscope',
description='Set the position of the floor (cover slip)',
success_message='Cover slip position stored')
def set_floor(self):
self.microscope.floor_Z = self.microscope.position()
[docs] @command(category='Stage',
description='Move stage vertically by {:.0f}μm',
default_arg=10)
def move_stage_vertical(self, distance):
self.calibrated_stage.relative_move(distance, axis=1)
[docs] @command(category='Stage',
description='Move stage horizontally by {:.0f}μm',
default_arg=10)
def move_stage_horizontal(self, distance):
self.calibrated_stage.relative_move(distance, axis=0)
[docs] @command(category='Manipulators',
description='Switch to manipulator {}',
default_arg=1)
def switch_manipulator(self, unit_number):
'''
Switch the currently active manipulator
Parameters
----------
unit_number : int
The number of the manipulator (using 1-based indexing, whereas the
code internally uses 0-based indexing).
'''
self.current_unit = unit_number - 1
self.calibrated_unit = self.calibrated_units[self.current_unit]
#self.manipulator_switched.emit('Manipulators',
# 'Manipulator: %d' % unit_number)
[docs] @blocking_command(category='Stage',
description='Calibrate stage only',
task_description='Calibrating stage')
def calibrate_stage(self):
self.execute([self.calibrated_stage.calibrate,
self.calibrated_unit.analyze_calibration])
[docs] @blocking_command(category='Manipulators',
description='Calibrate manipulator',
task_description='Calibrating manipulator')
def calibrate_manipulator(self):
self.execute([self.calibrated_unit.calibrate,
self.calibrated_unit.analyze_calibration])
[docs] @blocking_command(category='Manipulators',
description='Calibrate stage and manipulator (2nd Method)',
task_description='Calibrating stage and manipulator (2nd Method)')
def calibrate_manipulator2(self):
self.execute([self.calibrated_unit.calibrate,
self.calibrated_unit.analyze_calibration],
argument=[2, None])
[docs] @blocking_command(category='Manipulators',
description='Recalibrate manipulator',
task_description='Recalibrating manipulator')
def recalibrate_manipulator(self):
self.execute(self.calibrated_unit.recalibrate)
[docs] @blocking_command(category='Manipulators',
description='Recalibrate manipulator',
task_description='Recalibrate manipulator at click position')
def recalibrate_manipulator_on_click(self, xy_position):
self.debug('asking for recalibration at {}'.format(xy_position))
self.execute(self.calibrated_unit.recalibrate, argument=xy_position)
[docs] @blocking_command(category='Manipulators',
description='Move pipette to position',
task_description='Moving to position with safe approach')
def move_pipette(self, xy_position):
x, y = xy_position
position = np.array([x, y, self.microscope.position()])
self.debug('asking for safe move to {}'.format(position))
self.execute(self.calibrated_unit.safe_move, argument=position)
[docs] @blocking_command(category='Manipulators',
description='Move stage to position',
task_description='Moving stage to position')
def move_stage(self, xy_position):
x, y = xy_position
position = np.array([x, y])
self.debug('asking for reference move to {}'.format(position))
self.execute(self.calibrated_stage.reference_relative_move, argument=-position) # compensatory move
[docs] @blocking_command(category='Microscope',
description='Go to the floor (cover slip)',
task_description='Go to the floor (cover slip)')
def go_to_floor(self):
self.execute(self.microscope.absolute_move,
argument=self.microscope.floor_Z)
# TODO: Make the configuration system more general/clean
[docs] @command(category='Manipulators',
description='Save the calibration information',
success_message='Calibration information stored')
def save_configuration(self):
# Saves configuration
self.info("Saving configuration")
cfg = {'stage': self.calibrated_stage.save_configuration(),
'units': [u.save_configuration() for u in self.calibrated_units],
'microscope': self.microscope.save_configuration()}
with open(self.config_filename, "wb") as f:
pickle.dump(cfg, f)
[docs] @command(category='Manipulators',
description='Load the calibration information',
success_message='Calibration information loaded')
def load_configuration(self):
# Loads configuration
self.info("Loading configuration")
if os.path.exists(self.config_filename):
with open(self.config_filename, "rb") as f:
cfg = pickle.load(f)
self.microscope.load_configuration(cfg['microscope'])
self.calibrated_stage.load_configuration(cfg['stage'])
cfg_units = cfg['units']
for i, cfg_unit in enumerate(cfg_units):
self.calibrated_units[i].load_configuration(cfg_unit)
self.calibrated_unit.analyze_calibration()
else:
self.debug('Configuration file {} not found'.format(self.config_filename))
[docs] @command(category='Manipulators',
description='Reset timer')
def reset_timer(self):
self.timer_t0 = time.time()