# coding=utf-8
from types import MethodType
import time
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
import numpy as np
from holypipette.controller import TaskController
from holypipette.gui import CameraGui
from holypipette.interface import command, blocking_command
from holypipette.devices.manipulator.calibratedunit import CalibrationError
import datetime
[docs]class ManipulatorGui(CameraGui):
pipette_command_signal = QtCore.pyqtSignal(MethodType, object)
pipette_reset_signal = QtCore.pyqtSignal(TaskController)
def __init__(self, camera, pipette_interface, with_tracking=False):
super(ManipulatorGui, self).__init__(camera, with_tracking=with_tracking)
self.setWindowTitle("Pipette GUI")
self.interface = pipette_interface
self.control_thread = QtCore.QThread()
self.control_thread.setObjectName('PipetteControlThread')
self.interface.moveToThread(self.control_thread)
self.control_thread.start()
self.interface_signals[self.interface] = (self.pipette_command_signal,
self.pipette_reset_signal)
self.display_edit_funcs.append(self.draw_scale_bar)
self.display_edit_funcs.append(self.display_manipulator)
self.display_edit_funcs.append(self.show_tip)
self.add_config_gui(self.interface.calibration_config)
self.show_tip_on = False
self.tip_x, self.tip_y = None, None
self.tip_t0 = None
# Measure manipulator positions (range measurement)
self.position_timer = QtCore.QTimer()
self.position_timer.timeout.connect(self.interface.measure_ranges)
self.position_measurement = False
# Stage position for display
self._last_stage_measurement = None
self._stage_position = (None, None, None)
[docs] @command(category='Manipulators',
description='Measure manipulator ranges')
def measure_ranges(self):
if self.position_measurement is False:
self.interface.info('Measuring manipulator ranges')
self.position_measurement = True
# Reset ranges
self.interface.reset_ranges()
self.position_timer.start(500)
else:
self.interface.info('Stopped measuring manipulator ranges')
self.position_measurement = False
self.position_timer.stop()
# Check whether all positions have been updated
self.interface.check_ranges()
[docs] def display_manipulator(self, pixmap):
'''
Displays the number of the selected manipulator.
'''
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen(QtGui.QColor(200, 0, 0, 125))
painter.setPen(pen)
painter.setFont(QFont("Arial", int(pixmap.height()/20)))
c_x, c_y = pixmap.width() *19.0 / 20, pixmap.height() * 19.0 / 20
painter.drawText(c_x, c_y, str(self.interface.current_unit+1))
[docs] def draw_scale_bar(self, pixmap, text=True, autoscale=True,
position=True):
if autoscale and not text:
raise ValueError('Automatic scaling of the bar without showing text '
'will not be very helpful...')
stage = self.interface.calibrated_stage
camera_pixel_per_um = getattr(self.camera, 'pixel_per_um', None)
if stage.calibrated or camera_pixel_per_um:
pen_width = 4
if camera_pixel_per_um is not None:
bar_length = camera_pixel_per_um
else:
bar_length = stage.pixel_per_um()[0]
scale = 1.0 * self.camera.width / pixmap.size().width()
scaled_length = bar_length/scale
if autoscale:
lengths = np.array([1, 2, 5, 10, 20, 50, 100])
if scaled_length*lengths[-1] < pen_width:
# even the longest bar is not long enough -- don't show
# any scale bar
return
elif scaled_length*lengths[0] > 20*pen_width:
# the shortest bar is not short enough (>20x the width)
length_in_um = lengths[0]
else:
# Use the length that gives a bar of about 10x its width
length_in_um = lengths[np.argmin(np.abs(scaled_length*lengths - 10*pen_width))]
else:
length_in_um = 10
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen(QtGui.QColor(200, 0, 0, 125))
pen.setWidth(pen_width)
painter.setPen(pen)
c_x, c_y = pixmap.width() / 20, pixmap.height() * 19.0 / 20
painter.drawLine(c_x, c_y,
int(c_x + round(length_in_um*scaled_length)), c_y)
if text:
painter.drawText(c_x, c_y - 10, '{}µm'.format(length_in_um))
if position and not self.running_task:
# Only ask for positions if last measurement has been made a
# sufficiently long time ago
update_time = self.interface.calibration_config.position_update/1000.
if (self._last_stage_measurement is None or
time.time() - self._last_stage_measurement > update_time):
self._stage_position = (stage.position(axis=0),
stage.position(axis=1),
self.interface.microscope.position())
self._last_stage_measurement = time.time()
x, y, z = self._stage_position
# If floor position is set, display Z relative to floor position, positive being above
if (self.interface.microscope.floor_Z is not None) and (self.interface.microscope.up_direction is not None):
z= (z-self.interface.microscope.floor_Z) * self.interface.microscope.up_direction
position_text = 'x: {:.0f}µm, y: {:.0f}µm, z: {:.0f}µm'
painter.drawText(c_x, c_y + 20, position_text.format(x, y, z))
painter.end()
[docs] def register_commands(self, manipulator_keys = True):
super(ManipulatorGui, self).register_commands()
if manipulator_keys:
# Commands to move the stage
# Note that we do not use the automatic documentation mechanism here,
# as we one entry for every possible keypress
modifiers = [Qt.NoModifier, Qt.AltModifier, Qt.ShiftModifier]
distances = [10., 2.5, 50.]
self.help_window.register_custom_action('Stage', 'Arrows',
'Move stage')
self.help_window.register_custom_action('Stage',
'/'.join(QtGui.QKeySequence(mod).toString()
if mod is not Qt.NoModifier else 'No modifier'
for mod in modifiers),
'Move stage by ' + '/'.join(str(x) for x in distances) + ' µm')
self.help_window.register_custom_action('Manipulators', 'A/S/W/D',
'Move pipette by in x/y direction')
self.help_window.register_custom_action('Manipulators', 'Q/E',
'Move pipette by in z direction')
self.help_window.register_custom_action('Manipulators',
'/'.join(QtGui.QKeySequence(mod).toString()
if mod is not Qt.NoModifier else 'No modifier'
for mod in modifiers),
'Move pipette by ' + '/'.join(str(x) for x in distances) + ' µm')
for modifier, distance in zip(modifiers, distances):
self.register_key_action(Qt.Key_Up, modifier,
self.interface.move_stage_vertical,
argument=-distance, default_doc=False)
self.register_key_action(Qt.Key_Down, modifier,
self.interface.move_stage_vertical,
argument=distance, default_doc=False)
self.register_key_action(Qt.Key_Left, modifier,
self.interface.move_stage_horizontal,
argument=-distance, default_doc=False)
self.register_key_action(Qt.Key_Right, modifier,
self.interface.move_stage_horizontal,
argument=distance, default_doc=False)
self.register_key_action(Qt.Key_W, modifier,
self.interface.move_pipette_y,
argument=distance, default_doc=False)
self.register_key_action(Qt.Key_S, modifier,
self.interface.move_pipette_y,
argument=-distance, default_doc=False)
self.register_key_action(Qt.Key_A, modifier,
self.interface.move_pipette_x,
argument=distance, default_doc=False)
self.register_key_action(Qt.Key_D, modifier,
self.interface.move_pipette_x,
argument=-distance, default_doc=False)
self.register_key_action(Qt.Key_Q, modifier,
self.interface.move_pipette_z,
argument=distance, default_doc=False)
self.register_key_action(Qt.Key_E, modifier,
self.interface.move_pipette_z,
argument=-distance, default_doc=False)
# Show the tip
self.register_key_action(Qt.Key_T, Qt.NoModifier,
self.show_tip_switch)
# Calibration commands
self.register_key_action(Qt.Key_C, Qt.ControlModifier,
self.interface.calibrate_stage)
self.register_key_action(Qt.Key_C, Qt.NoModifier,
self.interface.calibrate_manipulator)
self.register_key_action(Qt.Key_C, Qt.AltModifier,
self.interface.calibrate_manipulator2)
self.register_key_action(Qt.Key_R, Qt.NoModifier,
self.interface.recalibrate_manipulator)
self.register_mouse_action(Qt.RightButton, Qt.NoModifier,
self.interface.recalibrate_manipulator_on_click)
self.register_key_action(Qt.Key_M, Qt.NoModifier,
self.measure_ranges)
# Pipette selection
number_of_units = len(self.interface.calibrated_units)
for unit_number in range(number_of_units):
key = QtGui.QKeySequence("%d" % (unit_number + 1))[0]
self.register_key_action(key, None,
self.interface.switch_manipulator,
argument=unit_number + 1,
default_doc=False)
options = '/'.join(str(x+1) for x in range(number_of_units))
self.help_window.register_custom_action('Manipulators', options,
'Switch to manipulator ' + options)
self.register_key_action(Qt.Key_S, Qt.ControlModifier,
self.interface.save_configuration)
# Move pipette by clicking
self.register_mouse_action(Qt.LeftButton, Qt.NoModifier,
self.interface.move_pipette)
# Move stage by clicking
self.register_mouse_action(Qt.RightButton, Qt.ShiftModifier,
self.interface.move_stage)
# Microscope control
self.register_key_action(Qt.Key_PageUp, None,
self.interface.move_microscope,
argument=10, default_doc=False)
self.register_key_action(Qt.Key_PageDown, None,
self.interface.move_microscope,
argument=-10, default_doc=False)
key_string = (QtGui.QKeySequence(Qt.Key_PageUp).toString() + '/' +
QtGui.QKeySequence(Qt.Key_PageDown).toString())
self.help_window.register_custom_action('Microscope', key_string,
'Move microscope up/down by 10µm')
self.register_key_action(Qt.Key_F, None,
self.interface.set_floor)
self.register_key_action(Qt.Key_G, None,
self.interface.go_to_floor)
# Show configuration pane
self.register_key_action(Qt.Key_P, None,
self.configuration_keypress)
# Toggle overlays
self.register_key_action(Qt.Key_O, None,
self.toggle_overlay)
[docs] @command(category='Manipulators',
description='Show the tip of selected manipulator')
def show_tip_switch(self):
try:
self.tip_x, self.tip_y, _ = self.interface.calibrated_unit.reference_position()
self.tip_t0 = time.time()
self.show_tip_on = True
except CalibrationError: # not yet calibrated
return
[docs] def show_tip(self, pixmap):
# Show the tip of the electrode
if self.show_tip_on:
interface = self.interface
scale = 1.0 * self.camera.width / pixmap.size().width()
pixel_per_um = getattr(self.camera, 'pixel_per_um', None)
if pixel_per_um is None:
pixel_per_um = interface.calibrated_unit.stage.pixel_per_um()[0]
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen(QtGui.QColor(0, 0, 200, 125))
pen.setWidth(3)
painter.setPen(pen)
x, y = self.tip_x, self.tip_y
if x is not None:
x+=self.camera.width/2
y+=self.camera.height/2
width = 20 * pixel_per_um / scale
height = 20 * pixel_per_um / scale
painter.translate(x / scale, y / scale)
painter.drawRect(-width / 2, -height / 2, width, height)
painter.end()
# Display for just one second
if time.time()>self.tip_t0+1.:
self.show_tip_on = False
[docs] def display_timer(self, pixmap):
interface = self.interface
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen(QtGui.QColor(200, 0, 0, 125))
pen.setWidth(1)
painter.setPen(pen)
c_x, c_y = pixmap.width() / 20, pixmap.height() / 20
t = int(time.time() - interface.timer_t0)
hours = t//3600
minutes = (t-hours*3600)//60
seconds = t-hours*3600-minutes*60
painter.drawText(c_x, c_y, '{}'.format(datetime.time(hours,minutes,seconds)))
painter.end()