diff --git a/.gitignore b/.gitignore index f77c867..4dfd571 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,19 @@ -__pycache__ -/**/__pycache__ -.ipynb_checkpoints -/**/.ipynb_checkpoints -**/*.ipynb_checkpoints/ -log/* -mesoSPIM/legacy/* -/log/* -tmp/* -/tmp/* -mesoSPIM/src/devices/zoom/dynamixel/* -mesoSPIM/PI_GCS2_DLL_x64.dll -.idea -prototypes/orcas/*.tif -*.bin - -prototypes/python-essentials/temp\.tif +__pycache__ +/**/__pycache__ +.ipynb_checkpoints +/**/.ipynb_checkpoints +**/*.ipynb_checkpoints/ +mesoSPIM/legacy/* +tmp/* +/tmp/* +*.log +mesoSPIM/src/devices/zoom/dynamixel/* +mesoSPIM/PI_GCS2_DLL_x64.dll +.idea +prototypes/orcas/*.tif +*.bin + +prototypes/python-essentials/temp\.tif mesoSPIM/src/devices/stages/galil/gclib/gclib\.py @@ -24,6 +23,26 @@ mesoSPIM/src/devices/stages/galil/gclib/__init__\.py mesoSPIM/config/etl_parameters/ETL-parameters - Fabian - DBE - H45\.csv -*.log +mesoSPIM/src/devices/stages/galil/gclib/gclib\.py + +mesoSPIM/src/devices/stages/galil/gclib\.py + +mesoSPIM/src/devices/stages/galil/gclib/__init__\.py + +mesoSPIM/config/etl_parameters/ETL-parameters - Fabian - DBE - H45\.csv + + +mesoSPIM/src/mesoSPIM_DemoSerial\.py -mesoSPIM/src/devices/zoom/dynamixel\.zip + +mesoSPIM/src/devices/stages/galil/gclib/gclib\.py + +mesoSPIM/src/devices/stages/galil/gclib\.py + +mesoSPIM/src/devices/stages/galil/gclib/__init__\.py + +mesoSPIM/config/etl_parameters/ETL-parameters - Fabian - DBE - H45\.csv + +*.log + +mesoSPIM/src/devices/zoom/dynamixel\.zip \ No newline at end of file diff --git a/mesoSPIM/config/config_H45.py b/mesoSPIM/config/config_H45.py index 44c86ef..7ab4c51 100644 --- a/mesoSPIM/config/config_H45.py +++ b/mesoSPIM/config/config_H45.py @@ -72,6 +72,7 @@ 'y_pixels' : 2048, 'x_pixel_size_in_microns' : 6.5, 'y_pixel_size_in_microns' : 6.5, + 'subsampling' : [1,2,4], 'camera_id' : 0, 'sensor_mode' : 12, # 12 for progressive 'defect_correct_mode': 2, @@ -86,35 +87,49 @@ ''' Stage configuration ''' -stage_parameters = {'stage_type' : 'PI_f_rot_and_Galil_xyz', # 'PI' or 'Debug' - 'startfocus' : 95000, - 'y_load_position': -40000, - 'y_unload_position': -90000, +stage_parameters = {'stage_type' : 'PI_rotz_and_Galil_xyf', # 'PI' or 'Debug' + 'startfocus' : -10000, + 'y_load_position': -86000, + 'y_unload_position': -120000, 'x_max' : 51000, 'x_min' : -46000, 'y_max' : 0, 'y_min' : -160000, - 'z_max' : -2000, - 'z_min' : -97000, + 'z_max' : 99000, + 'z_min' : -99000, 'f_max' : 99000, - 'f_min' : 0, + 'f_min' : -99000, 'theta_max' : 999, 'theta_min' : -999, 'x_rot_position': 0, - 'y_rot_position': -58000, - 'z_rot_position': -35000, + 'y_rot_position': -121000, + 'z_rot_position': 66000, } -xyz_galil_parameters = {'COMport' : 'COM48', - 'x_encodercounts_per_um' : 2, - 'y_encodercounts_per_um' : 2, - 'z_encodercounts_per_um' : 2} +'''Sample XYZ controller''' +xyf_galil_parameters = {'port' : '192.168.1.43',#'or COM48' + 'x_encodercounts_per_um' : 2, + 'y_encodercounts_per_um' : 2, + 'f_encodercounts_per_um' : 2 + } +'''PI Rotation + z controller''' +pi_parameters = {'controllername' : 'C-884', + 'stages' : ('M-061.PD','M-406.4PD'), + 'refmode' : ('FRF',), + 'serialnum' : ('118015799'), #0185500834 + 'velocity': {0: 22.5, 1: 2}, # in mm/s or °/s + } + +''' pi_parameters = {'controllername' : 'C-884', 'stages' : ('M-112K033','L-406.40DG10','M-112K033','M-116.DG','M-406.4PD','M-061.PD'), 'refmode' : ('FRF',), 'serialnum' : ('118015799'), } +''' + + ''' Filterwheel configuration @@ -225,4 +240,7 @@ 'camera_pulse_%' : 1, 'camera_exposure_time':0.02, 'camera_line_interval':0.000075, +'camera_display_live_subsampling': 1, +'camera_display_snap_subsampling': 1, +'camera_display_acquisition_subsampling': 2, } diff --git a/mesoSPIM/config/config_H45_demo.py b/mesoSPIM/config/config_H45_demo.py index 8b7fd6f..b3b6dc4 100644 --- a/mesoSPIM/config/config_H45_demo.py +++ b/mesoSPIM/config/config_H45_demo.py @@ -72,6 +72,7 @@ 'y_pixels' : 2048, 'x_pixel_size_in_microns' : 6.5, 'y_pixel_size_in_microns' : 6.5, + 'subsampling' : [1,2,4], 'camera_id' : 0, 'sensor_mode' : 12, # 12 for progressive 'defect_correct_mode': 2, @@ -225,4 +226,7 @@ 'camera_pulse_%' : 1, 'camera_exposure_time':0.02, 'camera_line_interval':0.000075, +'camera_display_live_subsampling': 1, +'camera_display_snap_subsampling': 1, +'camera_display_acquisition_subsampling': 2, } diff --git a/mesoSPIM/config/etl_parameters/ETL-parameters - Fabian - BABB - H45.csv b/mesoSPIM/config/etl_parameters/ETL-parameters - Fabian - BABB - H45.csv index f2c4fff..935b6b6 100644 --- a/mesoSPIM/config/etl_parameters/ETL-parameters - Fabian - BABB - H45.csv +++ b/mesoSPIM/config/etl_parameters/ETL-parameters - Fabian - BABB - H45.csv @@ -34,7 +34,7 @@ Objective;Wavelength;Zoom;ETL-Left-Offset;ETL-Left-Amp;ETL-Right-Offset;ETL-Righ 1x;515 nm;6.3x;2.389000000000008;0.12300000000000003;2.59299999999999;0.09399999999999992 1x;561 nm;0.63x;2.3609999999999935;1.086;2.51;1.06 1x;561 nm;0.8x;2.641999999999996;0.6;2.6609999999999983;0.75 -1x;561 nm;1x;2.6339999999999932;0.5820000000000001;2.6609999999999996;0.618 +1x;561 nm;1x;2.22;0.582;2.7249999999999996;0.642 1x;561 nm;1.25x;2.5940000000000003;0.632;2.691;0.42700000000000005 1x;561 nm;1.6x;2.5819999999999963;0.47999999999999987;2.6650000000000014;0.3639999999999999 1x;561 nm;2x;2.5219999999999865;0.388;2.599;0.296 diff --git a/mesoSPIM/config/etl_parameters/ETL-parameters - Fabian.csv b/mesoSPIM/config/etl_parameters/ETL-parameters - Fabian.csv index 00c53ae..253ce44 100644 --- a/mesoSPIM/config/etl_parameters/ETL-parameters - Fabian.csv +++ b/mesoSPIM/config/etl_parameters/ETL-parameters - Fabian.csv @@ -10,15 +10,15 @@ Objective;Wavelength;Zoom;ETL-Left-Offset;ETL-Left-Amp;ETL-Right-Offset;ETL-Righ 1x;405 nm;4x;2.486999999999993;0.197;2.67;0.25 1x;405 nm;5x;2.486999999999993;0.17800000000000007;2.6850000000000005;0.2 1x;405 nm;6.3x;2.67;0.25;2.677999999999998;0.058999999999999886 -1x;488 nm;0.63x;2.148;1.146;2.4099999999999997;0.966 -1x;488 nm;0.8x;2.4160000000000004;1.052;2.462;0.868 -1x;488 nm;1x;2.3419999999999996;0.842;2.6120000000000005;0.633 +1x;488 nm;0.63x;2.148;1.146;2.382;1.054 +1x;488 nm;0.8x;2.4160000000000004;1.052;2.4000000000000004;0.846 +1x;488 nm;1x;2.3419999999999996;0.842;2.514;0.597 1x;488 nm;1.25x;2.3960000000000017;0.692;2.5759999999999974;0.6260000000000001 1x;488 nm;1.6x;2.4000000000000004;0.422;2.5780000000000003;0.458 1x;488 nm;2x;2.397000000000001;0.4610000000000001;2.524;0.386 1x;488 nm;2.5x;2.386999999999998;0.3990000000000001;2.5870000000000006;0.26999999999999996 1x;488 nm;3.2x;2.389999999999997;0.25700000000000006;2.5910000000000006;0.20499999999999996 -1x;488 nm;4x;2.3420000000000054;0.158;2.622;0.167 +1x;488 nm;4x;2.3420000000000054;0.158;2.426;0.137 1x;488 nm;5x;2.3949999999999987;0.21100000000000008;2.593000000000001;0.13299999999999998 1x;488 nm;6.3x;2.3919999999999972;0.14100000000000001;2.590000000000002;0.11099999999999996 1x;515 nm;0.63x;2.3970000000000007;0.9629999999999999;2.580999999999999;0.9540000000000004 @@ -33,14 +33,14 @@ Objective;Wavelength;Zoom;ETL-Left-Offset;ETL-Left-Amp;ETL-Right-Offset;ETL-Righ 1x;515 nm;5x;2.3920000000000075;0.16799999999999998;2.59499999999999;0.12599999999999997 1x;515 nm;6.3x;2.389000000000008;0.12300000000000003;2.59299999999999;0.09399999999999992 1x;561 nm;0.63x;2.361;1.086;2.579000000000002;1.107999999999997 -1x;561 nm;0.8x;2.4040000000000026;0.9460000000000002;2.580999999999999;0.704 +1x;561 nm;0.8x;2.4040000000000026;0.9460000000000002;2.3899999999999997;0.786 1x;561 nm;1x;2.399999999999999;0.744;2.586999999999997;0.5939999999999999 1x;561 nm;1.25x;2.3959999999999995;0.688;2.5409999999999955;0.586 1x;561 nm;1.6x;2.3320000000000003;0.515;2.5929999999999995;0.472 1x;561 nm;2x;2.3800000000000012;0.504;2.5990000000000024;0.296 1x;561 nm;2.5x;2.39;0.37600000000000017;2.593;0.324 1x;561 nm;3.2x;2.39;0.2760000000000001;2.593;0.26800000000000007 -1x;561 nm;4x;2.389;0.216;2.611999999999997;0.158 +1x;561 nm;4x;2.389;0.216;2.408000000000001;0.126 1x;561 nm;5x;2.387;0.13599999999999995;2.598;0.18600000000000003 1x;561 nm;6.3x;2.391;0.10400000000000001;2.5939999999999994;0.12800000000000003 1x;594 nm;0.63x;2.4080000000000017;1.2600000000000002;2.583999999999999;0.8499999999999996 @@ -55,13 +55,13 @@ Objective;Wavelength;Zoom;ETL-Left-Offset;ETL-Left-Amp;ETL-Right-Offset;ETL-Righ 1x;594 nm;5x;2.4010000000000065;0.14399999999999996;2.6039999999999934;0.12999999999999998 1x;594 nm;6.3x;2.401000000000008;0.13800000000000004;2.5999999999999908;0.09199999999999992 1x;647 nm;0.63x;2.379000000000006;1.344;2.5710000000000024;1.224 -1x;647 nm;0.8x;2.423000000000008;0.9560000000000002;2.6049999999999938;1.018 +1x;647 nm;0.8x;2.423000000000008;0.9560000000000002;2.4;0.78 1x;647 nm;1x;2.495;0.87;2.602999999999993;0.7479999999999999 1x;647 nm;1.25x;2.423000000000008;0.6479999999999999;2.6029999999999927;0.582 1x;647 nm;1.6x;2.4210000000000083;0.5359999999999999;2.6129999999999955;0.4299999999999998 1x;647 nm;2x;2.3930000000000025;0.432;2.6189999999999993;0.366 1x;647 nm;2.5x;2.417000000000007;0.3799999999999999;2.606999999999996;0.27999999999999997 1x;647 nm;3.2x;2.415000000000008;0.23199999999999998;2.608999999999992;0.24199999999999977 -1x;647 nm;4x;2.4190000000000085;0.2;2.6129999999999924;0.20800000000000002 +1x;647 nm;4x;2.4190000000000085;0.2;2.4349999999999996;0.164 1x;647 nm;5x;2.415000000000002;0.14500000000000002;2.6099999999999923;0.12499999999999994 1x;647 nm;6.3x;2.495;0.2;2.535;0.19 diff --git a/mesoSPIM/gui/mesoSPIM_MainWindow.ui b/mesoSPIM/gui/mesoSPIM_MainWindow.ui index f494229..c5907f4 100644 --- a/mesoSPIM/gui/mesoSPIM_MainWindow.ui +++ b/mesoSPIM/gui/mesoSPIM_MainWindow.ui @@ -1439,9 +1439,9 @@ Position 10 - 550 + 430 431 - 121 + 111 @@ -1451,7 +1451,7 @@ Position 10 - 40 + 30 131 31 @@ -1464,7 +1464,7 @@ Position 10 - 80 + 70 131 31 @@ -1480,7 +1480,7 @@ Position 310 - 40 + 30 111 31 @@ -1505,7 +1505,7 @@ Position 310 - 80 + 70 111 31 @@ -1530,7 +1530,7 @@ Position 10 200 431 - 351 + 221 @@ -1761,7 +1761,7 @@ Position 450 320 431 - 231 + 221 @@ -1812,7 +1812,7 @@ Position 190 - 70 + 60 91 31 @@ -1825,7 +1825,7 @@ Position 310 - 70 + 60 91 31 @@ -1838,7 +1838,7 @@ Position 10 - 110 + 100 131 31 @@ -1851,7 +1851,7 @@ Position 10 - 150 + 140 141 31 @@ -1867,7 +1867,7 @@ Position 240 - 150 + 140 131 31 @@ -1892,7 +1892,7 @@ Position 310 - 110 + 100 111 31 @@ -1920,7 +1920,7 @@ Position 190 - 110 + 100 111 31 @@ -1945,7 +1945,7 @@ Position 10 - 190 + 180 131 31 @@ -1961,7 +1961,7 @@ Position 190 - 190 + 180 111 31 @@ -1983,7 +1983,7 @@ Position 310 - 190 + 180 111 31 @@ -2244,6 +2244,91 @@ ETL tab + + + + 10 + 550 + 431 + 151 + + + + Display Subsampling + + + + + 310 + 30 + 111 + 31 + + + + + + + 10 + 30 + 191 + 31 + + + + During Live display: + + + + + + 10 + 70 + 221 + 31 + + + + When Snapping Images: + + + + + + 10 + 110 + 201 + 31 + + + + During Acquisitions: + + + + + + 310 + 70 + 111 + 31 + + + + + + + 310 + 110 + 111 + 31 + + + + -1 + + + diff --git a/mesoSPIM/log/log_files_go_here.txt b/mesoSPIM/log/log_files_go_here.txt new file mode 100644 index 0000000..e69de29 diff --git a/mesoSPIM/mesoSPIM_Control.py b/mesoSPIM/mesoSPIM_Control.py index d03b012..11ed42e 100644 --- a/mesoSPIM/mesoSPIM_Control.py +++ b/mesoSPIM/mesoSPIM_Control.py @@ -4,6 +4,15 @@ The core module of the mesoSPIM software ''' +''' Configuring the logging module before doing anything else''' +import time +import logging +timestr = time.strftime("%Y%m%d-%H%M%S") +logging_filename = timestr + '.log' +logging.basicConfig(filename='log/'+logging_filename, level=logging.INFO, format='%(asctime)-8s:%(levelname)s:%(threadName)s:%(thread)d:%(module)s:%(name)s:%(message)s') +logger = logging.getLogger(__name__) +logger.info('mesoSPIM-control started') + import os import sys import importlib.util @@ -12,6 +21,8 @@ from src.mesoSPIM_MainWindow import mesoSPIM_MainWindow +logger.info('Modules loaded') + def load_config(): ''' Import microscope configuration at startup @@ -30,6 +41,7 @@ def load_config(): spec = importlib.util.spec_from_file_location('module.name', global_config_path) config = importlib.util.module_from_spec(spec) spec.loader.exec_module(config) + logger.info(f'Configuration file loaded: {global_config_path}') return config else: ''' Application shutdown ''' @@ -40,12 +52,37 @@ def load_config(): sys.exit(cfg_app.exec_()) +def stage_referencing_check(cfg): + ''' + Due to problems with some PI stages loosing reference information + after restarting the mesoSPIM software, some stage configurations require + a reference movement to be carried out before starting the rest of the softwareself. + + As reference movements can damage the instrument, this function warns users + about this problem by message boxes and asks them to reach a safe state. + ''' + if cfg.stage_parameters['stage_type'] == 'PI_rotz_and_Galil_xyf': + warning = QtWidgets.QMessageBox.warning(None,'Sample z reference movement necessary!', + 'Please move the XYZ stage to position where a reference z movement (to the midpoint of the movement range) is safe!', + QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok) + if warning == QtWidgets.QMessageBox.Cancel: + shutdown_message = QtWidgets.QMessageBox.warning(None,'Shutdown warning', + 'No reference movement - shutting down!', + QtWidgets.QMessageBox.Ok) + sys.exit() + else: + return True + else: + return True + def main(): """ Main function """ + logging.info('mesoSPIM Program started.') cfg = load_config() app = QtWidgets.QApplication(sys.argv) + stage_referencing_check(cfg) ex = mesoSPIM_MainWindow(cfg) ex.show() diff --git a/mesoSPIM/scripts/z_motiontest.py b/mesoSPIM/scripts/z_motiontest.py new file mode 100644 index 0000000..f86d627 --- /dev/null +++ b/mesoSPIM/scripts/z_motiontest.py @@ -0,0 +1,9 @@ +for i in range(10): + print('Starting movement') + self.serial_worker.move_relative({'z_rel':1000}, wait_until_done=True) + print('Done with the first movement') + time.sleep(1) + print('Starting second movement') + self.serial_worker.move_relative({'z_rel':-1000}, wait_until_done=True) + time.sleep(1) + print('Done with the second movement') \ No newline at end of file diff --git a/mesoSPIM/src/devices/filter_wheels/ludlcontrol.py b/mesoSPIM/src/devices/filter_wheels/ludlcontrol.py index c97ac63..5921572 100644 --- a/mesoSPIM/src/devices/filter_wheels/ludlcontrol.py +++ b/mesoSPIM/src/devices/filter_wheels/ludlcontrol.py @@ -10,8 +10,10 @@ import io as Io import time +'''PyQt5 Imports''' +from PyQt5 import QtWidgets, QtCore, QtGui -class LudlFilterwheel: +class LudlFilterwheel(QtCore.QObject): """ Class to control a 10-position Ludl filterwheel @@ -35,13 +37,15 @@ class LudlFilterwheel: """ def __init__(self, COMport, filterdict, baudrate=9600): + super().__init__() + self.COMport = COMport self.baudrate = baudrate self.filterdict = filterdict self.double_wheel = False ''' Delay in s for the wait until done function ''' - self.wait_until_done_delay = 1 + self.wait_until_done_delay = 0.5 """ If the first entry of the filterdict has a tuple diff --git a/mesoSPIM/src/devices/filter_wheels/mesoSPIM_FilterWheel.py b/mesoSPIM/src/devices/filter_wheels/mesoSPIM_FilterWheel.py index 8250d4a..be30db8 100644 --- a/mesoSPIM/src/devices/filter_wheels/mesoSPIM_FilterWheel.py +++ b/mesoSPIM/src/devices/filter_wheels/mesoSPIM_FilterWheel.py @@ -3,10 +3,13 @@ ============================ ''' import time -from PyQt5 import QtCore -class mesoSPIM_DemoFilterWheel(): +'''PyQt5 Imports''' +from PyQt5 import QtWidgets, QtCore, QtGui + +class mesoSPIM_DemoFilterWheel(QtCore.QObject): def __init__(self, filterdict): + super().__init__() self.filterdict = filterdict def _check_if_filter_in_filterdict(self, filter): diff --git a/mesoSPIM/src/devices/stages/galil/galilcontrol.py b/mesoSPIM/src/devices/stages/galil/galilcontrol.py index cf4110f..85669da 100644 --- a/mesoSPIM/src/devices/stages/galil/galilcontrol.py +++ b/mesoSPIM/src/devices/stages/galil/galilcontrol.py @@ -9,237 +9,299 @@ import time import traceback -import gclib +from PyQt5 import QtWidgets, QtCore, QtGui -class StageControlGalil: - ''' - Class to control the Galil stage with 3 axes +import gclib - TODO - * correct tracking of the internal and external absolute positions: Do everything via the Galil controller - * is it truly a good idea to set the stage to zero for each new instantiation? +import logging +logger = logging.getLogger(__name__) +class StageControlGalil(QtCore.QObject): + ''' + Class to control a Galil mechanical stage controller + + Inherits from QtCore.QObject so it can be moved to a QThread. + + Note: + The encodercounts should be specfified such that: Position in um = encoderposition * encodercounts + or Position in degrees = encoderposition * encodercounts + + Args: + port (str): Either a COMport designation ("COM42") or a IP address "192.168.1.42" + encodercounts (list of int): Conversion factor from unit of interest + + Examples: + my_stage = StageControlGalil('COM19', [2,2,2]) + my_stage2 = StageControlGalil('192.168.1.45', [2,2,2]) + + Todo + * correct tracking of the internal and external absolute positions: Do everything via the Galil controller + * is it truly a good idea to set the stage to zero for each new instantiation? + ''' - def __init__(self, - COMport, - baudrate='19200', - timeout='60000', - x_encodercounts_per_um = 0, - y_encodercounts_per_um = 2, - z_encodercounts_per_um = 2): - - self.xpos = 0 - self.ypos = 0 - self.zpos = 0 - self.x_encodercounts_per_um = x_encodercounts_per_um - self.y_encodercounts_per_um = y_encodercounts_per_um - self.z_encodercounts_per_um = z_encodercounts_per_um - self.unit = 'micron' - self.COMport = COMport - self.baudrate = baudrate - self.timeout = timeout # in ms - + def __init__(self,port,encodercounts = [2,2,2]): + super().__init__() + + self.port = port + self.encodercounts = encodercounts + '''The number of ''' + self.num_axes = len(encodercounts) + self.g = gclib.py() - #Typical connectionstring: 'COM1 --baud 19200 --subscribe ALL --timeout 60000' - self.connectionstring = self.COMport + ' --baud ' + self.baudrate + ' --subscribe ALL --timeout ' + self.timeout - self.g.GOpen(self.connectionstring) - - # Set absolute position to zero - risky at the limits of the movement range... - #self.g.GCommand('DPX=0') - #self.g.GCommand('DPY=0') - #self.g.GCommand('DPZ=0') + + '''Opens connection to the stage controller''' + if port[0:3] == 'COM': + self.port = port + self.baudrate = '19200' + self.timeout = '60000' + self.connectionstring = self.COMport + ' --baud ' + self.baudrate + ' --subscribe ALL --timeout ' + self.timeout + self.g.GOpen(self.connectionstring) + else: + '''Assumes Ethernet connection''' + self.connectionstring = self.port + ' --direct' + self.g.GOpen(self.connectionstring) + + '''To address the stages for commands, the list of axes designations needs + to be truncated.''' + _axisstring = 'ABCDEFGH' + self._axistring = _axisstring[0:self.num_axes] + + '''Create an default position list with the right length''' + self.positions = [0 for i in range(self.num_axes)] + self.read_position() + + self.initflag = True + self.unit = 'micron' + self.speed = 5000 + self.slow_speed = 5000 + self.fast_speed = 20000 + self.set_speed(self.slow_speed) + ''' + self.read_position('x') + self.read_position('y') + self.read_position('z') + ''' + self.initflag = False - def close_stage(self): + def close(self): + '''Closes connection to the stage''' self.g.GClose() - def stage_info(self): - self.message = self.g.GInfo() - return self.message - - def read_position(self, axis): - self.axisstring = copy.copy(axis) - self.axis = axis.capitalize() - self.position = self.g.GCommand('RP'+self.axis) - '''Float and try & except added here to solve weird bug with - - "ValueError: invalid literal for int() with base 10: '' " - + def controller_info(self): + '''Returns COM port/IP adress, DMC version and serial number of the controller''' + return self.g.GInfo() + + def _send_command(self, command): + '''Sends a command to the controller + + Try-except block included to catch errors - this is dangerous - no checking of success + + Args: + command (str): Command string to be sent + + Returns: + answer (str): Answer by the controller ''' - if self.axisstring == 'x': - try: - return int(float(self.position)) / self.x_encodercounts_per_um - except: - return 0 - elif self.axisstring == 'y': - try: - return int(float(self.position)) / self.y_encodercounts_per_um - except: - return 0 - else: - try: - return int(float(self.position)) / self.z_encodercounts_per_um - except: - return 0 - - def read_x_position_um(self): - return self.read_position('x') - - def read_y_position_um(self): - return self.read_position('y') - - def read_z_position_um(self): - return self.read_position('z') - - def set_axis_to_zero(self, axis): - if axis == 'x': - self.xpos = 0 - elif axis == 'y': - self.ypos = 0 - else: - self.zpos = 0 - - self.axis = axis.capitalize() - self.g.GCommand('DP'+self.axis+'=0') - - def move_relative(self, xrel = 0, yrel = 0, zrel = 0): - '''Move relative method - - Movements larger than 250 microns should be slower - - Values are taken from the default (delivery) settings of the Galil controller. + try: + return self.g.GCommand(command) + except Exception as error: + logger.exception(error) + + def stop(self, restart_programs=False): + '''Stops movement on all axes by sending ST to the controller + + After ST, also program execution stops -- using the restart_programs + flag, they can be restarted this is risky, here it + is assumed that any program runs only the handcontroller and does + not induce any movement. ''' - if abs(xrel) > 250: - self.g.GCommand('SPX=5000') - else: - self.g.GCommand('SPX=20000') - - if abs(yrel) > 250: - self.g.GCommand('SPY=5000') + self._send_command('ST') + if restart_programs == True: + self.execute_program() + + def execute_program(self): + self._send_command('XQ') + + def wait_until_done(self, axis): + '''Requires X,Y,Z or A,B,C etc..''' + self.g.GMotionComplete(axis.upper()) + + def read_position(self): + '''Reports position from the stages + + Notes: + Updates internal position list self.positions as well. + + Returns: + positions (list): list of positions + + ''' + position = self._send_command('RP') + position_list = position.split(',') + try: + position_list = [int(float(i)) for i in position_list] + self.positions = [item[0]/item[1] for item in zip(position_list,self.encodercounts)] + except Exception as error: + logger.exception(error) + + return self.positions + + def move_relative(self, motion_dict): + '''Command for relative motion + + Args: + motion_dict (dict): Dictionary in the form {1: 4000, 2:-234}, in general {axis_id:requested_position} + with axis_id (int) and requested_position (int). + Returns: + Carries out motion + ''' + + ''' Set speed according to requested distance unless it has been set before ''' + if any(abs(distance)>250 for distance in motion_dict.values()): + if self.speed != self.slow_speed: + self.set_speed(self.slow_speed) else: - self.g.GCommand('SPY=20000') - - if abs(zrel) > 250: - self.g.GCommand('SPZ=5000') + if self.speed != self.fast_speed: + self.set_speed(self.fast_speed) + + ''' Convert um to encodercounts ''' + command_list = self._convert_dict_to_sorted_list(motion_dict, self.num_axes) + encoder_command_list = [i[0]*i[1] for i in zip(command_list, self.encodercounts)] + + ''' Create command string and initiate movement + + Zero padding is important here as the non-commanded axes should stay where they are, + otherwise previous relative motion commands are retained and possibly excuted + again after the 'BG' command ist sent. + ''' + command_string = self._convert_sorted_list_to_galil_string(encoder_command_list, zero_padding=True) + self._send_command('PR'+command_string) + self._send_command('BG') + + def move_absolute(self, motion_dict): + ''' Command for absolute motion + + Args: + motion_dict (dict): Dictionary in the form {1: 4000, 2:-234}, in general {axis_id:requested_position} + with axis_id (int) and requested_position (int). + + ''' + + ''' Set speed according to requested distance unless it has been set before ''' + current_positions = self.read_position() + requested_positions = self._convert_dict_to_sorted_list(motion_dict, self.num_axes) + ''' Set requested positions to current value to make the list difference zero + Otherwise, the distances are not detected correctly + ''' + requested_positions = [pos[0] if pos[0] != 0 else pos[1] for pos in zip(requested_positions,current_positions)] + position_differences = self._list_difference(requested_positions,current_positions) + if any(abs(distance)>250 for distance in position_differences): + if self.speed != self.slow_speed: + self.set_speed(self.slow_speed) else: - self.g.GCommand('SPZ=50000') - - # Send movement commands - self.g.GCommand('PRX='+str(int(xrel*self.x_encodercounts_per_um))) - self.g.GCommand('PRY='+str(int(yrel*self.y_encodercounts_per_um))) - self.g.GCommand('PRZ='+str(int(zrel*self.z_encodercounts_per_um))) - self.g.GCommand('BG') + if self.speed != self.fast_speed: + self.set_speed(self.fast_speed) + + ''' Convert um to encodercounts ''' + command_list = self._convert_dict_to_sorted_list(motion_dict, self.num_axes) + encoder_command_list = [i[0]*i[1] for i in zip(command_list, self.encodercounts)] + + ''' Carry out motion + + Removing zero padding is important here as the non-commanded axes should stay where they are, + otherwise they move to position 0. + ''' + command_string = self._convert_sorted_list_to_galil_string(encoder_command_list, zero_padding=False) + self._send_command('PA'+command_string) + self._send_command('BG') + + def set_speed(self, speed): + '''Sets speed for all axes to the requested value + + Args: + speed (int): speed value + + ''' + list_axes = [i+1 for i in range(self.num_axes)] + command_dict = {i : speed for i in list_axes} + command_string = self._convert_dict_to_galil_string(command_dict, self.num_axes) + self._send_command('SP'+command_string) + self.speed = speed + + def get_speed(self): + return self.speed + + def _convert_dict_to_galil_string(self, command_dict, length, zero_padding=False): + '''Converts a dict of the form {1: 4000, 3:-234} into a string '4000,,-234' + which can be combined with a command ('RP4000,,-234') and sent to the stage controller. + + Args: + command_dict (dict): Command dictionary in the form {key (int): command value (int)} + length (int): Length of output items + zero_padding (bool): Indicates whether the values between commas should be filled with zeros + + Returns: + galil_string (str): String with values between separating commas + ''' + keylist = list(command_dict.keys()) + keylist.sort() - # Update internal values - self.xpos += xrel - self.ypos += yrel - self.zpos += zrel + galil_string = '' - def move_absolute(self, xabs = None, yabs = None, zabs = None): - '''Move absolution function, implementation is ugly. + for i in range(length): + if i+1 in keylist: + galil_string += str(command_dict[i+1]) + galil_string += ',' + else: + if zero_padding == True: + galil_string += '0' + galil_string += ',' + return galil_string + + def _convert_sorted_list_to_galil_string(self, command_list, zero_padding=True): + '''Converts a list of the form [80,123,-53] into a string '80,123,-53' + which can be combined with a command ('RP80,123,-53') and sent to the stage controller. + + Notes: + If zero_padding is set to False, zeros are removed from the output: [80,0,-53] --> '80,,-53' - The default none as an argument is dangerous. Very dangerous: - If you run move_absolute once with e.g. zabs = 0, the others get - nonetype which leads to type erros + Args: + command_list (list): Command list in the form [80,123,-53] + length (int): Length of output items + zero_padding (bool): Indicates whether the values between commas should be filled with zeros - There is also no speed control apart from some z adaptation + Returns: + galil_string (str): String with values between separating commas ''' - - # Send movement commands - if xabs != None: - ''' Adapt speed to distance ''' - position = self.read_x_position_um() - if abs(xabs-position) > 250: - self.g.GCommand('SPX=5000') + galil_string = '' + for i in range(len(command_list)): + if command_list[i] == 0 and zero_padding == False: + galil_string += ',' else: - self.g.GCommand('SPX=50000') - - string = 'PAX='+str(int(xabs*self.x_encodercounts_per_um)) - print(string) - self.g.GCommand(string) - self.xpos = xabs - - if yabs != None: - ''' Adapt speed to distance ''' - position = self.read_y_position_um() - if abs(yabs-position) > 250: - self.g.GCommand('SPY=5000') - else: - self.g.GCommand('SPY=50000') - - string = 'PAY='+str(int(yabs*self.y_encodercounts_per_um)) - print(string) - self.g.GCommand(string) - self.ypos = yabs - - if zabs != None: - ''' Adapt speed to distance ''' - position = self.read_z_position_um() - if abs(zabs-position) > 250: - self.g.GCommand('SPZ=5000') - else: - self.g.GCommand('SPZ=50000') + galil_string += str(command_list[i]) + galil_string += ',' + + return galil_string - string = 'PAZ='+str(int(zabs*self.z_encodercounts_per_um)) - print(string) - self.g.GCommand(string) - self.zpos = zabs + def _convert_dict_to_sorted_list(self, input_dict, length): + '''Converts an input dictionary into a zero-padded list of a certain length - try: - self.g.GCommand('BG') - except: - print('Error occured') - - def move_absolute_in_z(self, zabs): - '''get current position and adapt speed accordingly''' - - position = self.read_z_position_um() - if abs(zabs-position) > 250: - self.g.GCommand('SPZ=5000') - else: - self.g.GCommand('SPZ=50000') - - self.g.GCommand('PAZ='+str(int(zabs*self.z_encodercounts_per_um))) - self.g.GCommand('BG') - self.zpos = zabs - - def set_speed(self, axis, speed): - '''Sets the movement speed via the Galil SP command''' - if axis == 'x': - self.g.GCommand('SPX='+str(int(speed))) - elif axis == 'y': - self.g.GCommand('SPY='+str(int(speed))) - else: - self.g.GCommand('SPZ='+str(int(speed))) - - def set_acceleration(self, axis, acceleration): - '''Sets the acceleration via the Galil AC command''' - if axis == 'x': - self.g.GCommand('ACX='+str(int(acceleration))) - elif axis == 'y': - self.g.GCommand('ACY='+str(int(acceleration))) - else: - self.g.GCommand('ACZ='+str(int(acceleration))) - - def set_deceleration(self, axis, deceleration): - '''Sets the deceleration via the Galil DC command''' - if axis == 'x': - self.g.GCommand('DCX='+str(int(acceleration))) - elif axis == 'y': - self.g.GCommand('DCY='+str(int(acceleration))) - else: - self.g.GCommand('DCZ='+str(int(acceleration))) - - def stop_all_movements(self): - self.g.GCommand('ST') - '''After ST, also program execution stops -- this is risky, here it - is assumed that any program runs only the handcontroller and does - not induce any movement.''' - self.execute_program() - - def wait_until_done(self, axis): - self.g.GMotionComplete(axis.upper()) - - def execute_program(self): - '''Executes program stored on the Galil controller''' - self.g.GCommand('XQ') \ No newline at end of file + Args: + input_dict (dict): Input dictionary in the form {key (int): value (int)} . + length (int): Number of elements required in the output list. + Returns: + List with n = length sorted elements which are sorted + ''' + keylist = list(input_dict.keys()) + keylist.sort() + return [input_dict[i+1] if i+1 in keylist else 0 for i in range(length)] + + def _list_difference(self, list1, list2): + '''Returns the element-wise difference of two lists of the same length + + Args: + list1 (list): First list of values + list2 (list): Second list of values + ''' + return [i[0]-i[1] for i in zip(list1, list2)] diff --git a/mesoSPIM/src/devices/zoom/mesoSPIM_Zoom.py b/mesoSPIM/src/devices/zoom/mesoSPIM_Zoom.py index 736049e..dbf0791 100644 --- a/mesoSPIM/src/devices/zoom/mesoSPIM_Zoom.py +++ b/mesoSPIM/src/devices/zoom/mesoSPIM_Zoom.py @@ -8,8 +8,11 @@ import time -class DemoZoom(): +from PyQt5 import QtWidgets, QtCore, QtGui + +class DemoZoom(QtCore.QObject): def __init__(self, zoomdict): + super().__init__() self.zoomdict = zoomdict def set_zoom(self, zoom, wait_until_done=False): @@ -19,8 +22,9 @@ def set_zoom(self, zoom, wait_until_done=False): time.sleep(1) -class DynamixelZoom: +class DynamixelZoom(QtCore.QObject): def __init__(self, zoomdict, COMport, identifier=2, baudrate=1000000): + super().__init__() from .dynamixel import dynamixel_functions as dynamixel self.zoomdict = zoomdict diff --git a/mesoSPIM/src/mesoSPIM_AcquisitionManagerWindow.py b/mesoSPIM/src/mesoSPIM_AcquisitionManagerWindow.py index 3aa5cba..6de1521 100644 --- a/mesoSPIM/src/mesoSPIM_AcquisitionManagerWindow.py +++ b/mesoSPIM/src/mesoSPIM_AcquisitionManagerWindow.py @@ -5,6 +5,9 @@ import os import sys +import logging +logger = logging.getLogger(__name__) + from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5.uic import loadUi @@ -116,6 +119,9 @@ def __init__(self, parent=None): self.SetFoldersButton.clicked.connect(self.set_folder_names) self.FilenameWizardButton.clicked.connect(self.generate_filenames) + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) + + def enable(self): self.setEnabled(True) @@ -356,7 +362,7 @@ def mark_current_rotation(self): def preview_acquisition(self): row = self.get_first_selected_row() - print('selected row:', row) + # print('selected row:', row) if row is not None: self.state['selected_row'] = row self.parent.sig_state_request.emit({'state':'preview_acquisition'}) diff --git a/mesoSPIM/src/mesoSPIM_Camera.py b/mesoSPIM/src/mesoSPIM_Camera.py index 7a64b4b..6f02039 100644 --- a/mesoSPIM/src/mesoSPIM_Camera.py +++ b/mesoSPIM/src/mesoSPIM_Camera.py @@ -5,6 +5,9 @@ import time import numpy as np +import logging +logger = logging.getLogger(__name__) + from PyQt5 import QtCore, QtWidgets, QtGui from .devices.cameras.Demo_Camera import Demo_Camera @@ -15,9 +18,10 @@ pass from .mesoSPIM_State import mesoSPIM_StateSingleton +from .utils.acquisitions import AcquisitionList, Acquisition class mesoSPIM_HamamatsuCamera(QtCore.QObject): - sig_camera_status = QtCore.pyqtSignal(str) + # sig_camera_status = QtCore.pyqtSignal(str) sig_camera_frame = QtCore.pyqtSignal(np.ndarray) sig_finished = QtCore.pyqtSignal() @@ -43,6 +47,10 @@ def __init__(self, parent = None): self.camera_line_interval = self.cfg.startup['camera_line_interval'] self.camera_exposure_time = self.cfg.startup['camera_exposure_time'] + self.camera_display_live_subsampling = self.cfg.startup['camera_display_live_subsampling'] + self.camera_display_snap_subsampling = self.cfg.startup['camera_display_snap_subsampling'] + self.camera_display_acquisition_subsampling = self.cfg.startup['camera_display_acquisition_subsampling'] + ''' Wiring signals ''' self.parent.sig_state_request.connect(self.state_request_handler) @@ -55,7 +63,7 @@ def __init__(self, parent = None): self.parent.sig_get_live_image.connect(self.get_live_image) self.parent.sig_end_live.connect(self.end_live, type=3) - self.sig_camera_status.connect(lambda status: print(status)) + # self.sig_camera_status.connect(lambda status: print(status)) ''' Hamamatsu-specific code ''' self.camera_id = self.cfg.camera_parameters['camera_id'] @@ -63,7 +71,8 @@ def __init__(self, parent = None): if self.cfg.camera == 'HamamatsuOrcaFlash': self.hcam = cam.HamamatsuCameraMR(camera_id=self.camera_id) ''' Debbuging information ''' - print("camera 0 model:", self.hcam.getModelInfo(self.camera_id)) + logger.info(f'Hamamatsu Camera model: {self.hcam.getModelInfo(self.camera_id)}') + #print("camera 0 model:", self.hcam.getModelInfo(self.camera_id)) ''' Ideally, the Hamamatsu Camera properties should be set in this order ''' ''' mesoSPIM mode parameters ''' @@ -85,19 +94,30 @@ def __init__(self, parent = None): elif self.cfg.camera == 'Demo': self.hcam = Demo_Camera() + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) + def __del__(self): self.hcam.shutdown() @QtCore.pyqtSlot(dict) def state_request_handler(self, dict): for key, value in zip(dict.keys(),dict.values()): - print('Camera Thread: State request: Key: ', key, ' Value: ', value) + # print('Camera Thread: State request: Key: ', key, ' Value: ', value) ''' The request handling is done with exec() to write fewer lines of code. ''' - if key in ('camera_exposure_time','camera_line_interval','state'): + if key in ('camera_exposure_time', + 'camera_line_interval', + 'state', + 'camera_display_live_subsampling', + 'camera_display_snap_subsampling', + 'camera_display_acquisition_subsampling'): exec('self.set_'+key+'(value)') + # Log Thread ID during Live: just debugging code + elif key == 'state': + if value == 'live': + logger.info('Thread ID during live: '+str(int(QtCore.QThread.currentThreadId()))) def set_state(self, requested_state): pass @@ -143,19 +163,30 @@ def set_camera_line_interval(self, time): self.sig_update_gui_from_state.emit(True) self.state['camera_line_interval'] = time self.sig_update_gui_from_state.emit(False) + + def set_camera_display_live_subsampling(self, factor): + self.camera_display_live_subsampling = factor + + def set_camera_display_snap_subsampling(self, factor): + self.camera_display_snap_subsampling = factor + + def set_camera_display_acquisition_subsampling(self, factor): + self.camera_display_acquisition_subsampling = factor + @QtCore.pyqtSlot(Acquisition) def prepare_image_series(self, acq): ''' Row is a row in a AcquisitionList ''' - print('Cam: Preparing Image Series') + logger.info('Camera: Preparing Image Series') + #print('Cam: Preparing Image Series') self.stopflag = False ''' TODO: Needs cam delay, sweeptime, QTimer, line delay, exp_time ''' self.path = acq['folder']+'/'+acq['filename'] - print('camera path: ', self.path) + logger.info(f'Camera: Save path: {self.path}') self.z_start = acq['z_start'] self.z_end = acq['z_end'] self.z_stepsize = acq['z_step'] @@ -170,14 +201,18 @@ def prepare_image_series(self, acq): self.hcam.startAcquisition() self.cur_image = 0 - print('Cam: Finished Preparing Image Series') + logger.info(f'Camera: Finished Preparing Image Series') + #print('Cam: Finished Preparing Image Series') self.start_time = time.time() + @QtCore.pyqtSlot() def add_images_to_series(self): - + if self.cur_image == 0: + logger.info('Thread ID during add images: '+str(int(QtCore.QThread.currentThreadId()))) + # QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 1) if self.stopflag is False: - print('Camera: Adding images started') + # print('Camera: Adding images started') if self.cur_image + 1 < self.max_frame: [frames, dims] = self.hcam.getFrames() @@ -208,34 +243,40 @@ def add_images_to_series(self): # subimage = image[0:2048:4,0:2048:4] # self.sig_camera_frame.emit(subimage) - self.sig_camera_frame.emit(image) + self.sig_camera_frame.emit(image[0:2048:self.camera_display_acquisition_subsampling,0:2048:self.camera_display_acquisition_subsampling]) image = image.flatten() self.xy_stack[self.cur_image*self.fsize:(self.cur_image+1)*self.fsize] = image - print('Done with image: #', self.cur_image) + #print('Done with image: #', self.cur_image) self.cur_image += 1 - print('Camera: Adding images ended') + #print('Camera: Adding images ended') else: - print('Camera: Acquisition stop requested...') + pass + #print('Camera: Acquisition stop requested...') + @QtCore.pyqtSlot() def end_image_series(self): try: self.hcam.stopAcquisition() del self.xy_stack - print('Acq finished') - print("Saved", self.cur_image + 1, "frames") + #print('Acq finished') + #print("Saved", self.cur_image + 1, "frames") except: - print('Camera: Error when finishing acquisition.') + pass + #print('Camera: Error when finishing acquisition.') self.end_time = time.time() framerate = (self.cur_image + 1)/(self.end_time - self.start_time) - print('Framerate: ', framerate) + logger.info(f'Camera: Framerate: {framerate}') + #print('Framerate: ', framerate) self.sig_finished.emit() + @QtCore.pyqtSlot() def snap_image(self): pass + @QtCore.pyqtSlot() def prepare_live(self): self.hcam.setACQMode(mode = "run_till_abort") self.hcam.startAcquisition() @@ -243,7 +284,10 @@ def prepare_live(self): self.live_image_count = 0 self.start_time = time.time() + logger.info('Camera: Preparing Live Mode') + logger.info('Thread ID during live: '+str(int(QtCore.QThread.currentThreadId()))) + @QtCore.pyqtSlot() def get_live_image(self): [frames, _] = self.hcam.getFrames() @@ -252,16 +296,17 @@ def get_live_image(self): image = np.reshape(image, (-1, 2048)) image = np.rot90(image) - - self.sig_camera_frame.emit(image) + self.sig_camera_frame.emit(image[0:2048:self.camera_display_live_subsampling,0:2048:self.camera_display_live_subsampling]) self.live_image_count += 1 - self.sig_camera_status.emit(str(self.live_image_count)) + #self.sig_camera_status.emit(str(self.live_image_count)) + @QtCore.pyqtSlot() def end_live(self): self.hcam.stopAcquisition() self.end_time = time.time() framerate = (self.live_image_count + 1)/(self.end_time - self.start_time) - print('Framerate: ', framerate) + logger.info(f'Camera: Finished Live Mode: Framerate: {framerate}') + # class mesoSPIM_DemoCamera(mesoSPIM_Camera): # def __init__(self, config, parent = None): diff --git a/mesoSPIM/src/mesoSPIM_CameraWindow.py b/mesoSPIM/src/mesoSPIM_CameraWindow.py index c09313a..105dd66 100644 --- a/mesoSPIM/src/mesoSPIM_CameraWindow.py +++ b/mesoSPIM/src/mesoSPIM_CameraWindow.py @@ -5,6 +5,9 @@ import sys import numpy as np +import logging +logger = logging.getLogger(__name__) + from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5.uic import loadUi @@ -14,7 +17,7 @@ pg.setConfigOptions(background='w') class mesoSPIM_CameraWindow(QtWidgets.QWidget): - def __init__(self): + def __init__(self, parent=None): super().__init__() '''Set up the UI''' @@ -24,6 +27,11 @@ def __init__(self): loadUi('gui/mesoSPIM_CameraWindow.ui', self) self.setWindowTitle('mesoSPIM-Control: Camera Window') + self.parent = parent + self.cfg = parent.cfg + + + ''' Set histogram Range ''' self.graphicsView.setLevels(100,4000) @@ -33,12 +41,21 @@ def __init__(self): self.histogram.setMinimumWidth(250) self.histogram.item.vb.setMaximumWidth(250) + ''' This is flipped to account for image rotation ''' + self.y_image_width = self.cfg.camera_parameters['x_pixels'] + self.x_image_width = self.cfg.camera_parameters['y_pixels'] + ''' Initialize crosshairs ''' self.crosspen = pg.mkPen({'color': "r", 'width': 1}) - self.vLine = pg.InfiniteLine(pos=1024, angle=90, movable=False, pen=self.crosspen) - self.hLine = pg.InfiniteLine(pos=1024, angle=0, movable=False, pen=self.crosspen) + self.vLine = pg.InfiniteLine(pos=self.x_image_width/2, angle=90, movable=False, pen=self.crosspen) + self.hLine = pg.InfiniteLine(pos=self.y_image_width/2, angle=0, movable=False, pen=self.crosspen) self.graphicsView.addItem(self.vLine, ignoreBounds=True) self.graphicsView.addItem(self.hLine, ignoreBounds=True) + # print(self.vLine.getXPos()) + # print(self.hLine.getYPos()) + + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) + @QtCore.pyqtSlot(str) def display_status_message(self, string, time=0): @@ -60,7 +77,15 @@ def draw_crosshairs(self): @QtCore.pyqtSlot(np.ndarray) def set_image(self, image): self.graphicsView.setImage(image, autoLevels=False, autoHistogramRange=False, autoRange=False) - self.draw_crosshairs() + if image.shape[0] != self.y_image_width or image.shape[1] != self.x_image_width: + self.x_image_width = image.shape[0] + self.y_image_width = image.shape[1] + self.vLine.setPos(self.x_image_width/2) # Stating a single value works for orthogonal lines + self.hLine.setPos(self.y_image_width/2) # Stating a single value works for orthogonal lines + self.graphicsView.addItem(self.vLine, ignoreBounds=True) + self.graphicsView.addItem(self.hLine, ignoreBounds=True) + else: + self.draw_crosshairs() if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) diff --git a/mesoSPIM/src/mesoSPIM_Core.py b/mesoSPIM/src/mesoSPIM_Core.py index 087f308..637413d 100644 --- a/mesoSPIM/src/mesoSPIM_Core.py +++ b/mesoSPIM/src/mesoSPIM_Core.py @@ -9,6 +9,9 @@ import csv import traceback +import logging +logger = logging.getLogger(__name__) + '''PyQt5 Imports''' from PyQt5 import QtWidgets, QtCore, QtGui @@ -30,10 +33,13 @@ from .devices.lasers.mesoSPIM_LaserEnabler import mesoSPIM_LaserEnabler from .mesoSPIM_Serial import mesoSPIM_Serial +# from .mesoSPIM_DemoSerial import mesoSPIM_Serial from .mesoSPIM_WaveFormGenerator import mesoSPIM_WaveFormGenerator from .utils.acquisitions import AcquisitionList, Acquisition +from .utils.demo_threads import mesoSPIM_DemoThread + class mesoSPIM_Core(QtCore.QObject): '''This class is the pacemaker of a mesoSPIM @@ -97,12 +103,12 @@ def __init__(self, config, parent): self.parent.sig_execute_script.connect(self.execute_script) - self.parent.sig_move_relative.connect(lambda dict: self.move_relative(dict)) - self.parent.sig_move_relative_and_wait_until_done.connect(lambda dict: self.move_relative(dict, wait_until_done=True)) - self.parent.sig_move_absolute.connect(lambda dict: self.move_absolute(dict)) - self.parent.sig_move_absolute_and_wait_until_done.connect(lambda dict: self.move_absolute(dict, wait_until_done=True)) - self.parent.sig_zero_axes.connect(lambda list: self.zero_axes(list)) - self.parent.sig_unzero_axes.connect(lambda list: self.unzero_axes(list)) + self.parent.sig_move_relative.connect(self.move_relative) + # self.parent.sig_move_relative_and_wait_until_done.connect(lambda dict: self.move_relative(dict, wait_until_done=True)) + self.parent.sig_move_absolute.connect(self.move_absolute) + # self.parent.sig_move_absolute_and_wait_until_done.connect(lambda dict: self.move_absolute(dict, wait_until_done=True)) + self.parent.sig_zero_axes.connect(self.zero_axes) + self.parent.sig_unzero_axes.connect(self.unzero_axes) self.parent.sig_stop_movement.connect(self.stop_movement) self.parent.sig_load_sample.connect(self.sig_load_sample.emit) self.parent.sig_unload_sample.connect(self.sig_unload_sample.emit) @@ -111,30 +117,54 @@ def __init__(self, config, parent): self.parent.sig_save_etl_config.connect(self.sig_save_etl_config.emit) + #logger.info('Core internal thread affinity in init: '+str(id(self.thread()))) + ''' Set the Camera thread up ''' self.camera_thread = QtCore.QThread() self.camera_worker = mesoSPIM_HamamatsuCamera(self) + #logger.info('Camera worker thread affinity before moveToThread? Answer:'+str(id(self.camera_worker.thread()))) self.camera_worker.moveToThread(self.camera_thread) - self.camera_worker.sig_update_gui_from_state.connect(lambda flag: self.sig_update_gui_from_state.emit(flag)) - + self.camera_worker.sig_update_gui_from_state.connect(self.sig_update_gui_from_state.emit) + #logger.info('Camera worker thread affinity after moveToThread? Answer:'+str(id(self.camera_worker.thread()))) ''' Set the serial thread up ''' self.serial_thread = QtCore.QThread() self.serial_worker = mesoSPIM_Serial(self) self.serial_worker.moveToThread(self.serial_thread) - self.serial_worker.sig_position.connect(lambda dict: self.sig_position.emit(dict)) + + #self.serial_worker.sig_position.connect(lambda dict: self.sig_position.emit(dict)) + self.serial_worker.sig_position.connect(self.sig_position.emit) + + # ''' Setting another demo thread up ''' + # self.demo_thread = QtCore.QThread() + # self.demo_worker = mesoSPIM_DemoThread() + # self.sig_state_request.connect(self.demo_worker.report_thread_id) + # self.demo_worker.moveToThread(self.demo_thread) + # self.demo_thread.start() + ''' HICKUP DEBUGGING ''' + self.z_start_measured = 0.0 + self.z_end_measured = 0.0 + self.hickup_delta_z = 0.0 ''' Start the threads ''' self.camera_thread.start() + #logger.info('Camera worker thread affinity after starting the thread? Answer:'+str(id(self.camera_worker.thread()))) self.serial_thread.start() - print('Camera thread priority:', self.camera_thread.priority()) - print('Serial thread priority:', self.serial_thread.priority()) + # ''' Get the demo thread set up and start it ''' + + #logger.info('Camera thread running? Answer:'+str(self.camera_thread.isRunning())) + #logger.info('Serial thread running? Answer:'+str(self.serial_thread.isRunning())) + + #logger.info(f'Core: Camera Thread priority: {self.camera_thread.priority()}') + #logger.info(f'Core: Serial Thread priority: {self.serial_thread.priority()}') ''' Setting waveform generation up ''' self.waveformer = mesoSPIM_WaveFormGenerator(self) - self.waveformer.sig_update_gui_from_state.connect(lambda flag: self.sig_update_gui_from_state.emit(flag)) + self.waveformer.sig_update_gui_from_state.connect(self.sig_update_gui_from_state.emit) self.sig_state_request.connect(self.waveformer.state_request_handler) - self.sig_state_request_and_wait_until_done.connect(self.waveformer.state_request_handler, type=3) + self.sig_state_request_and_wait_until_done.connect(self.waveformer.state_request_handler) + ''' If this line is activated while the waveformer and the core live in the same thread, a deadlock results ''' + #self.sig_state_request_and_wait_until_done.connect(self.waveformer.state_request_handler, type=3) ''' Setting the shutters up ''' left_shutter_line = self.cfg.shutterdict['shutter_left'] @@ -161,6 +191,8 @@ def __init__(self, config, parent): self.stopflag = False + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) + # self.acquisition_list_rotation_position = {} def __del__(self): @@ -182,7 +214,7 @@ def __del__(self): @QtCore.pyqtSlot(dict) def state_request_handler(self, dict): for key, value in zip(dict.keys(),dict.values()): - print('Core Thread: State request: Key: ', key, ' Value: ', value) + # print('Core Thread: State request: Key: ', key, ' Value: ', value) ''' The request handling is done with exec() to write fewer lines of code. @@ -227,13 +259,18 @@ def state_request_handler(self, dict): 'laser_r_pulse_%', 'laser_r_max_amplitude', 'camera_delay_%', - 'camera_pulse_%'): + 'camera_pulse_%', + 'camera_display_live_subsampling', + 'camera_display_snap_subsampling', + 'camera_display_acquisition_subsampling'): self.sig_state_request.emit({key : value}) def set_state(self, state): if state == 'live': self.state['state']='live' self.sig_state_request.emit({'state':'live'}) + logger.info('Thread ID during live: '+str(int(QtCore.QThread.currentThreadId()))) + #logger.info('Core internal thread affinity in live: '+str(id(self.thread()))) self.live() elif state == 'run_selected_acquisition': @@ -251,7 +288,7 @@ def set_state(self, state): self.preview_acquisition() elif state == 'idle': - print('Core: Stopping requested') + # print('Core: Stopping requested') self.sig_state_request.emit({'state':'idle'}) self.stop() @@ -289,18 +326,21 @@ def send_progress(self, } self.sig_progress.emit(dict) + @QtCore.pyqtSlot(dict) def set_filter(self, filter, wait_until_done=False): if wait_until_done: self.sig_state_request_and_wait_until_done.emit({'filter' : filter}) else: self.sig_state_request.emit({'filter' : filter}) + @QtCore.pyqtSlot(dict) def set_zoom(self, zoom, wait_until_done=False): if wait_until_done: self.sig_state_request_and_wait_until_done.emit({'zoom' : zoom}) else: self.sig_state_request.emit({'zoom' : zoom}) + @QtCore.pyqtSlot(str) def set_laser(self, laser, wait_until_done=False): self.laserenabler.enable(laser) if wait_until_done: @@ -308,42 +348,56 @@ def set_laser(self, laser, wait_until_done=False): else: self.sig_state_request.emit({'laser':laser}) + @QtCore.pyqtSlot(str) def set_intensity(self, intensity, wait_until_done=False): if wait_until_done: self.sig_state_request_and_wait_until_done.emit({'intensity': intensity}) else: self.sig_state_request.emit({'intensity':intensity}) + @QtCore.pyqtSlot(float) def set_camera_exposure_time(self, time): self.sig_state_request.emit({'camera_exposure_time' : time}) + @QtCore.pyqtSlot(float) def set_camera_line_interval(self, time): self.sig_state_request.emit({'camera_line_interval' : time}) + @QtCore.pyqtSlot(dict) def move_relative(self, dict, wait_until_done=False): if wait_until_done: self.sig_move_relative_and_wait_until_done.emit(dict) else: self.sig_move_relative.emit(dict) + # @QtCore.pyqtSlot(dict) + # def move_relative_and_wait_until_done(self, dict): + # self.move_relative(dict, wait_until_done=True) + + @QtCore.pyqtSlot(dict) def move_absolute(self, dict, wait_until_done=False): if wait_until_done: self.sig_move_absolute_and_wait_until_done.emit(dict) else: self.sig_move_absolute.emit(dict) + @QtCore.pyqtSlot(list) def zero_axes(self, list): self.sig_zero_axes.emit(list) + @QtCore.pyqtSlot(list) def unzero_axes(self, list): self.sig_unzero_axes.emit(list) + @QtCore.pyqtSlot() def stop_movement(self): self.sig_stop_movement.emit() + @QtCore.pyqtSlot(str) def set_shutterconfig(self, shutterconfig): self.state['shutterconfig'] = shutterconfig - + + @QtCore.pyqtSlot() def open_shutters(self): shutterconfig = self.state['shutterconfig'] @@ -361,7 +415,8 @@ def open_shutters(self): self.shutter_left.open() self.state['shutterstate'] = True - + + @QtCore.pyqtSlot() def close_shutters(self): self.shutter_left.close() self.shutter_right.close() @@ -483,6 +538,7 @@ def run_acquisition_list(self, acq_list): def close_acquisition_list(self, acq_list): self.sig_status_message.emit('Closing Acquisition List') + if not self.stopflag: current_rotation = self.state['position']['theta_pos'] startpoint = acq_list.get_startpoint() @@ -509,15 +565,12 @@ def preview_acquisition(self): row = self.state['selected_row'] if row==None: - print('No row selected!') + pass + # print('No row selected!') else: self.sig_update_gui_from_state.emit(True) acq = self.state['acq_list'][row] - - # rotation_position = self.state['acq_list'].get_rotation_point() - - self.sig_status_message.emit('Going to start position') - + ''' Rotation handling goes here ''' current_rotation = self.state['position']['theta_pos'] startpoint = acq.get_startpoint() @@ -525,15 +578,20 @@ def preview_acquisition(self): ''' Check if sample has to be rotated, allow some tolerance ''' if current_rotation > target_rotation+0.1 or current_rotation < target_rotation-0.1: + self.sig_status_message.emit('Going to rotation position') self.sig_go_to_rotation_position_and_wait_until_done.emit() + self.sig_status_message.emit('Rotating sample') self.move_absolute({'theta_abs':target_rotation}, wait_until_done=True) + self.sig_status_message.emit('Setting Filter'') + self.set_filter(acq['filter'], wait_until_done=True) + + self.sig_status_message.emit('Going to start position') self.move_absolute(startpoint, wait_until_done=False) - self.sig_status_message.emit('Setting Filter & Shutter') + self.sig_status_message.emit('Setting Shutter') self.set_shutterconfig(acq['shutterconfig']) - self.set_filter(acq['filter'], wait_until_done=False) - self.sig_status_message.emit('Setting Zoom') + self.sig_status_message.emit('Setting Zoom & Laser') self.set_zoom(acq['zoom'], wait_until_done=False) self.set_intensity(acq['intensity'], wait_until_done=True) self.set_laser(acq['laser'], wait_until_done=True) @@ -544,6 +602,7 @@ def preview_acquisition(self): self.sig_state_request.emit({'etl_r_offset' : acq['etl_r_offset']}) self.sig_update_gui_from_state.emit(False) + self.sig_status_message.emit('Ready for preview...') self.state['state'] = 'idle' @@ -551,8 +610,8 @@ def prepare_acquisition(self, acq): ''' Housekeeping: Prepare the acquisition ''' - print('Running Acquisition #', self.acquisition_count, - ' with Filename: ', acq['filename']) + logger.info(f'Core: Running Acquisition #{self.acquisition_count} with Filename: {acq["filename"]}') + self.sig_status_message.emit('Going to start position') ''' Rotation handling goes here: @@ -589,6 +648,10 @@ def prepare_acquisition(self, acq): self.sig_status_message.emit('Preparing camera: Allocating memory') self.sig_prepare_image_series.emit(acq) self.prepare_image_series() + + ''' HICKUP DEBUGGING: Measure z position ''' + self.z_start_measured = self.state['position']['z_pos'] + self.write_metadata(acq) def run_acquisition(self, acq): @@ -622,6 +685,11 @@ def run_acquisition(self, acq): self.close_shutters() def close_acquisition(self, acq): + ''' HICKUP DEBUGGING ''' + self.z_end_measured = self.state['position']['z_pos'] + self.collect_troubleshooting_data(acq) + self.append_troubleshooting_info_to_metadata(acq) + self.sig_status_message.emit('Closing Acquisition: Saving data & freeing up memory') if self.stopflag is False: @@ -716,7 +784,7 @@ def write_metadata(self, acq): metadata_path = os.path.dirname(path)+'/'+os.path.basename(path)+'_meta.txt' - print('Metadata_path: ', metadata_path) + # print('Metadata_path: ', metadata_path) with open(metadata_path,'w') as file: self.write_line(file, 'Metadata for file', path) @@ -771,3 +839,28 @@ def execute_galil_program(self): allows hand controller to operate''' self.sig_state_request.emit({'stage_program' : 'execute'}) + ''' HICKUP DEBUGGING ''' + + def collect_troubleshooting_data(self, acq): + self.hickup_delta_z = self.z_end_measured - acq['z_end'] + print('HICKUP Difference: ', self.hickup_delta_z) + + def append_troubleshooting_info_to_metadata(self, acq): + ''' + Appends a metadata.txt file + + Path contains the file to be written + ''' + path = acq['folder']+'/'+acq['filename'] + + metadata_path = os.path.dirname(path)+'/'+os.path.basename(path)+'_meta.txt' + + with open(metadata_path,'a') as file: + ''' Adding troubleshooting information ''' + self.write_line(file) + self.write_line(file, 'TROUBLESHOOTING INFORMATION') + self.write_line(file, 'delta_z end to start after acq', str(self.hickup_delta_z) ) + self.write_line(file, 'z_start expected', acq['z_start']) + self.write_line(file, 'z_start measured', str(self.z_start_measured)) + self.write_line(file, 'z_end expected', acq['z_end']) + self.write_line(file, 'z_end measured', str(self.z_end_measured)) diff --git a/mesoSPIM/src/mesoSPIM_MainWindow.py b/mesoSPIM/src/mesoSPIM_MainWindow.py index a4d883d..cbd0382 100644 --- a/mesoSPIM/src/mesoSPIM_MainWindow.py +++ b/mesoSPIM/src/mesoSPIM_MainWindow.py @@ -5,6 +5,11 @@ import sys import copy +import time + +import logging +logger = logging.getLogger(__name__) + from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5.uic import loadUi @@ -16,6 +21,9 @@ from .mesoSPIM_Core import mesoSPIM_Core from .devices.joysticks.mesoSPIM_JoystickHandlers import mesoSPIM_JoystickHandler +from .utils.demo_threads import mesoSPIM_DemoThread + + class mesoSPIM_MainWindow(QtWidgets.QMainWindow): ''' Main application window which instantiates worker objects and moves them @@ -33,9 +41,9 @@ class mesoSPIM_MainWindow(QtWidgets.QMainWindow): sig_execute_script = QtCore.pyqtSignal(str) sig_move_relative = QtCore.pyqtSignal(dict) - sig_move_relative_and_wait_until_done = QtCore.pyqtSignal(dict) + # sig_move_relative_and_wait_until_done = QtCore.pyqtSignal(dict) sig_move_absolute = QtCore.pyqtSignal(dict) - sig_move_absolute_and_wait_until_done = QtCore.pyqtSignal(dict) + # sig_move_absolute_and_wait_until_done = QtCore.pyqtSignal(dict) sig_zero_axes = QtCore.pyqtSignal(list) sig_unzero_axes = QtCore.pyqtSignal(list) sig_stop_movement = QtCore.pyqtSignal() @@ -47,6 +55,8 @@ class mesoSPIM_MainWindow(QtWidgets.QMainWindow): sig_save_etl_config = QtCore.pyqtSignal() + sig_poke_demo_thread = QtCore.pyqtSignal() + def __init__(self, config=None): super().__init__() @@ -69,7 +79,7 @@ def __init__(self, config=None): loadUi('gui/mesoSPIM_MainWindow.ui', self) self.setWindowTitle('mesoSPIM Main Window') - self.camera_window = mesoSPIM_CameraWindow() + self.camera_window = mesoSPIM_CameraWindow(self) self.camera_window.show() self.acquisition_manager_window = mesoSPIM_AcquisitionManagerWindow(self) @@ -79,12 +89,19 @@ def __init__(self, config=None): ''' Setting up the threads ''' + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) + logger.info('Ideal thread count: '+str(int(QtCore.QThread.idealThreadCount()))) ''' Setting the mesoSPIM_Core thread up ''' self.core_thread = QtCore.QThread() + '''Entry point: Work on thread affinity here''' self.core = mesoSPIM_Core(self.cfg, self) + #logger.info('Core thread affinity before moveToThread? Answer:'+str(id(self.core.thread()))) self.core.moveToThread(self.core_thread) + self.core.waveformer.moveToThread(self.core_thread) + #logger.info('Core thread affinity after moveToThread? Answer:'+str(id(self.core.thread()))) + ''' Get buttons & connections ready ''' self.initialize_and_connect_widgets() @@ -102,27 +119,38 @@ def __init__(self, config=None): self.core.sig_warning.connect(self.display_warning) + ''' Connecting the camera frames (this is a deep connection and slightly + risky) It will break immediately when there is an API change.''' + try: + self.core.camera_worker.sig_camera_frame.connect(self.camera_window.set_image) + # print('Camera connected successfully to the display window!') + except: + logger.warning(f'Main Window: Camera not connected to display!', exc_info=True) + ''' Start the thread ''' + #self.core_thread.start(QtCore.QThread.HighPriority) self.core_thread.start(QtCore.QThread.HighPriority) + logger.info(f'Core Thread: Thread priority: {str(self.core_thread.priority())}') + #logger.info('Core thread affinity after starting the thread? Answer:'+str(id(self.core.thread()))) + + #logger.info('Core thread running? Answer:'+str(self.core_thread.isRunning())) try: - current_thread = self.thread() - current_thread.setPriority(QtCore.QThread.TimeCriticalPriority) - print('Window priority: ', current_thread.priority()) + self.thread().setPriority(QtCore.QThread.HighestPriority) + #current_thread = self.thread() + #current_thread.setPriority(QtCore.QThread.TimeCriticalPriority) + #current_thread.setPriority(4) + #logger.info(f'Main Window: Thread priority: {str(current_thread.priority())}') + logger.info('Main Window Thread priority: '+str(self.thread().priority())) except: - print('Print window priority failed') - print('Core priority: ', self.core_thread.priority()) + logger.debug(f'Main Window: Printing Thread priority failed.') + + #logger.info(f'Main Window: Core priority: {self.core_thread.priority()}') + #print('Core priority: ', self.core_thread.priority()) ''' Setting up the joystick ''' self.joystick = mesoSPIM_JoystickHandler(self) - ''' Connecting the camera frames (this is a deep connection and slightly - risky) It will break immediately when there is an API change.''' - try: - self.core.camera_worker.sig_camera_frame.connect(self.camera_window.set_image) - print('Camera connected successfully to the display window!') - except: - print('Warning: camera not connected to display!') def __del__(self): '''Cleans the threads up after deletion, waits until the threads @@ -262,7 +290,7 @@ def initialize_and_connect_widgets(self): self.goToRotationPositionButton.clicked.connect(self.sig_go_to_rotation_position.emit) self.markRotationPositionButton.clicked.connect(self.sig_mark_rotation_position.emit) - self.xyZeroButton.toggled.connect(lambda bool: print('XY toggled') if bool is True else print('XY detoggled')) + # self.xyZeroButton.toggled.connect(lambda bool: print('XY toggled') if bool is True else print('XY detoggled')) self.xyZeroButton.clicked.connect(lambda bool: self.sig_zero_axes.emit(['x','y']) if bool is True else self.sig_unzero_axes.emit(['x','y'])) self.zZeroButton.clicked.connect(lambda bool: self.sig_zero_axes.emit(['z']) if bool is True else self.sig_unzero_axes.emit(['z'])) # self.xyzZeroButton.clicked.connect(lambda bool: self.sig_zero.emit(['x','y','z']) if bool is True else self.sig_unzero.emit(['x','y','z'])) @@ -272,10 +300,11 @@ def initialize_and_connect_widgets(self): self.xyzUnloadButton.clicked.connect(self.sig_unload_sample.emit) self.LiveButton.clicked.connect(self.run_live) + self.SnapButton.clicked.connect(self.run_snap) self.RunSelectedAcquisitionButton.clicked.connect(self.run_selected_acquisition) self.RunAcquisitionListButton.clicked.connect(self.run_acquisition_list) self.StopButton.clicked.connect(lambda: self.sig_state_request.emit({'state':'idle'})) - self.StopButton.clicked.connect(lambda: print('Stopping')) + #self.StopButton.clicked.connect(lambda: print('Stopping')) self.LightsheetSwitchingModeButton.clicked.connect(self.run_lightsheet_alignment_mode) self.VisualModeButton.clicked.connect(self.run_visual_mode) @@ -286,6 +315,9 @@ def initialize_and_connect_widgets(self): self.ChooseETLcfgButton.clicked.connect(self.choose_etl_config) self.SaveETLParametersButton.clicked.connect(self.save_etl_config) + # self.ChooseSnapFolderButton.clicked.connect(self.choose_snap_folder) + # self.SnapFolderIndicator.setText(self.state['snap_folder']) + self.ETLconfigIndicator.setText(self.state['ETL_cfg_file']) self.widget_to_state_parameter_assignment=( @@ -297,6 +329,8 @@ def initialize_and_connect_widgets(self): (self.LaserIntensitySlider, 'intensity',1), (self.CameraExposureTimeSpinBox, 'camera_exposure_time',1000), (self.CameraLineIntervalSpinBox,'camera_line_interval',1000000), + # (self.CameraTriggerDelaySpinBox,'camera_delay_%',1), + # (self.CameraTriggerPulseLengthSpinBox, 'camera_pulse_%',1), (self.SweeptimeSpinBox,'sweeptime',1000), (self.LeftLaserPulseDelaySpinBox,'laser_l_delay_%',1), (self.RightLaserPulseDelaySpinBox,'laser_r_delay_%',1), @@ -328,15 +362,23 @@ def initialize_and_connect_widgets(self): self.connect_widget_to_state_parameter(widget, state_parameter, conversion_factor) ''' Connecting the microscope controls ''' + + ''' List for subsampling factors - comboboxes need a list of strings''' + subsampling_list = [str(i) for i in self.cfg.camera_parameters['subsampling']] + self.connect_combobox_to_state_parameter(self.FilterComboBox,self.cfg.filterdict.keys(),'filter') self.connect_combobox_to_state_parameter(self.ZoomComboBox,self.cfg.zoomdict.keys(),'zoom') self.connect_combobox_to_state_parameter(self.ShutterComboBox,self.cfg.shutteroptions,'shutterconfig') self.connect_combobox_to_state_parameter(self.LaserComboBox,self.cfg.laserdict.keys(),'laser') + self.connect_combobox_to_state_parameter(self.LiveSubSamplingComboBox,subsampling_list,'camera_display_live_subsampling', int_conversion = True) + self.connect_combobox_to_state_parameter(self.SnapSubSamplingComboBox,subsampling_list,'camera_display_snap_subsampling', int_conversion = True) + self.connect_combobox_to_state_parameter(self.AcquisitionSubSamplingComboBox,subsampling_list,'camera_display_acquisition_subsampling', int_conversion = True) + # self.connect_combobox_to_state_parameter(self.CameraSensorModeComboBox,['ASLM','Area'],'camera_sensor_mode') + self.LaserIntensitySlider.valueChanged.connect(lambda currentValue: self.sig_state_request.emit({'intensity': currentValue})) self.LaserIntensitySlider.setValue(self.cfg.startup['intensity']) - def connect_widget_to_state_parameter(self, widget, state_parameter, conversion_factor): ''' Helper method to (currently) connect spinboxes @@ -344,7 +386,7 @@ def connect_widget_to_state_parameter(self, widget, state_parameter, conversion_ if isinstance(widget,(QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)): self.connect_spinbox_to_state_parameter(widget, state_parameter, conversion_factor) - def connect_combobox_to_state_parameter(self, combobox, option_list, state_parameter): + def connect_combobox_to_state_parameter(self, combobox, option_list, state_parameter, int_conversion = False): ''' Helper method to connect and initialize a combobox from the config @@ -355,8 +397,12 @@ def connect_combobox_to_state_parameter(self, combobox, option_list, state_param state_parameter (str): State parameter (has to exist in the config) ''' combobox.addItems(option_list) - combobox.currentTextChanged.connect(lambda currentText: self.sig_state_request.emit({state_parameter : currentText})) - combobox.setCurrentText(self.cfg.startup[state_parameter]) + if int_conversion == False: + combobox.currentTextChanged.connect(lambda currentText: self.sig_state_request.emit({state_parameter : currentText})) + combobox.setCurrentText(self.cfg.startup[state_parameter]) + else: + combobox.currentTextChanged.connect(lambda currentParameter: self.sig_state_request.emit({state_parameter : int(currentParameter)})) + combobox.setCurrentText(str(self.cfg.startup[state_parameter])) def connect_spinbox_to_state_parameter(self, spinbox, state_parameter, conversion_factor=1): ''' @@ -410,15 +456,24 @@ def update_gui_from_state(self): self.update_widget_from_state(widget, state_parameter, conversion_factor) self.block_signals_from_controls(False) + def run_snap(self): + self.sig_state_request.emit({'state':'snap'}) + self.set_progressbars_to_busy() + self.enable_mode_control_buttons(False) + self.enable_stop_button(True) + def run_live(self): self.sig_state_request.emit({'state':'live'}) + ''' Logging code to check the thread ID during live''' + logger.info('Thread ID during live: '+str(int(QtCore.QThread.currentThreadId()))) + self.sig_poke_demo_thread.emit() self.set_progressbars_to_busy() self.enable_mode_control_buttons(False) self.enable_stop_button(True) def run_selected_acquisition(self): row = self.acquisition_manager_window.get_first_selected_row() - print('selected row:', row) + # print('selected row:', row) self.state['selected_row'] = row self.sig_state_request.emit({'state':'run_selected_acquisition'}) self.enable_mode_control_buttons(False) @@ -460,6 +515,7 @@ def enable_gui(self, boolean): def enable_mode_control_buttons(self, boolean): self.LiveButton.setEnabled(boolean) + #self.SnapButton.setEnabled(boolean) self.RunSelectedAcquisitionButton.setEnabled(boolean) self.RunAcquisitionListButton.setEnabled(boolean) self.VisualModeButton.setEnabled(boolean) @@ -521,7 +577,7 @@ def choose_etl_config(self): self.state['ETL_cfg_file'] = path self.ETLconfigIndicator.setText(path) - print('Chosen ETL CFG file:', path) + logger.info(f'Main Window: Chose ETL Config File: {path}') self.sig_state_request.emit({'ETL_cfg_file' : path}) @@ -533,6 +589,16 @@ def save_etl_config(self): def display_warning(self, string): warning = QtWidgets.QMessageBox.warning(None,'mesoSPIM Warning', string, QtWidgets.QMessageBox.Ok) - + + def choose_snap_folder(self): + pass + + # path = QtWidgets.QFileDialog.getExistingDirectory(self, 'Open csv File', self.state['snap_folder']) + + # if path: + # self.state['snap_folder'] = path + # self.SnapFolderIndicator.setText(path) + + # logger.info(f'Main Window: Chosen Snap Folder: {path}') diff --git a/mesoSPIM/src/mesoSPIM_Serial.py b/mesoSPIM/src/mesoSPIM_Serial.py index bb6afe8..c8c22a1 100644 --- a/mesoSPIM/src/mesoSPIM_Serial.py +++ b/mesoSPIM/src/mesoSPIM_Serial.py @@ -9,6 +9,9 @@ import numpy as np import time +import logging +logger = logging.getLogger(__name__) + '''PyQt5 Imports''' from PyQt5 import QtWidgets, QtCore, QtGui @@ -18,7 +21,7 @@ from .devices.filter_wheels.ludlcontrol import LudlFilterwheel from .devices.filter_wheels.mesoSPIM_FilterWheel import mesoSPIM_DemoFilterWheel from .devices.zoom.mesoSPIM_Zoom import DynamixelZoom, DemoZoom -from .mesoSPIM_Stages import mesoSPIM_PIstage, mesoSPIM_DemoStage, mesoSPIM_GalilStages, mesoSPIM_PI_f_rot_and_Galil_xyz_Stages +from .mesoSPIM_Stages import mesoSPIM_PIstage, mesoSPIM_DemoStage, mesoSPIM_GalilStages, mesoSPIM_PI_f_rot_and_Galil_xyz_Stages, mesoSPIM_PI_rot_and_Galil_xyzf_Stages, mesoSPIM_PI_rotz_and_Galil_xyf_Stages # from .mesoSPIM_State import mesoSPIM_State class mesoSPIM_Serial(QtCore.QObject): @@ -46,7 +49,7 @@ def __init__(self, parent): self.state = mesoSPIM_StateSingleton() ''' Handling of state changing requests ''' - self.parent.sig_state_request.connect(lambda dict: self.state_request_handler(dict, wait_until_done=False)) + self.parent.sig_state_request.connect(self.state_request_handler) self.parent.sig_state_request_and_wait_until_done.connect(lambda dict: self.state_request_handler(dict, wait_until_done=True), type=3) ''' Attaching the filterwheel ''' @@ -64,43 +67,53 @@ def __init__(self, parent): ''' Attaching the stage ''' if self.cfg.stage_parameters['stage_type'] == 'PI': self.stage = mesoSPIM_PIstage(self) - self.stage.sig_position.connect(lambda dict: self.sig_position.emit({'position': dict})) elif self.cfg.stage_parameters['stage_type'] == 'GalilStage': self.stage = mesoSPIM_GalilStages(self) self.stage.sig_position.connect(lambda dict: self.sig_position.emit({'position': dict})) + elif self.cfg.stage_parameters['stage_type'] == 'PI_rot_and_Galil_xyzf': + self.stage = mesoSPIM_PI_rot_and_Galil_xyzf_Stages(self) + self.stage.sig_position.connect(lambda dict: self.sig_position.emit({'position': dict})) elif self.cfg.stage_parameters['stage_type'] == 'PI_f_rot_and_Galil_xyz': self.stage = mesoSPIM_PI_f_rot_and_Galil_xyz_Stages(self) self.stage.sig_position.connect(lambda dict: self.sig_position.emit({'position': dict})) + elif self.cfg.stage_parameters['stage_type'] == 'PI_rotz_and_Galil_xyf': + self.stage = mesoSPIM_PI_rotz_and_Galil_xyf_Stages(self) + self.stage.sig_position.connect(lambda dict: self.sig_position.emit({'position': dict})) elif self.cfg.stage_parameters['stage_type'] == 'DemoStage': self.stage = mesoSPIM_DemoStage(self) - self.stage.sig_position.connect(lambda dict: self.sig_position.emit({'position': dict})) + try: + self.stage.sig_position.connect(self.report_position) + except: + print('Stage not initalized! Please check the configuratio file') ''' Wiring signals through to child objects ''' - self.parent.sig_move_relative.connect(lambda dict: self.move_relative(dict)) + self.parent.sig_move_relative.connect(self.move_relative) self.parent.sig_move_relative_and_wait_until_done.connect(lambda dict: self.move_relative(dict, wait_until_done=True), type=3) - self.parent.sig_move_absolute.connect(lambda dict: self.move_absolute(dict)) + self.parent.sig_move_absolute.connect(self.move_absolute) self.parent.sig_move_absolute_and_wait_until_done.connect(lambda dict: self.move_absolute(dict, wait_until_done=True), type=3) - self.parent.sig_zero_axes.connect(lambda list: self.sig_zero_axes.emit(list)) - self.parent.sig_unzero_axes.connect(lambda list: self.sig_unzero_axes.emit(list)) - self.parent.sig_stop_movement.connect(lambda: self.sig_stop_movement.emit()) + self.parent.sig_zero_axes.connect(self.sig_zero_axes.emit) + self.parent.sig_unzero_axes.connect(self.sig_unzero_axes.emit) + self.parent.sig_stop_movement.connect(self.sig_stop_movement.emit) self.parent.sig_load_sample.connect(self.sig_load_sample.emit) self.parent.sig_unload_sample.connect(self.sig_unload_sample.emit) self.parent.sig_mark_rotation_position.connect(self.sig_mark_rotation_position.emit) - self.parent.sig_go_to_rotation_position.connect(lambda: self.go_to_rotation_position()) + self.parent.sig_go_to_rotation_position.connect(self.go_to_rotation_position) self.parent.sig_go_to_rotation_position_and_wait_until_done.connect(lambda: self.go_to_rotation_position(wait_until_done=True), type=3) + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) + @QtCore.pyqtSlot(dict) def state_request_handler(self, dict, wait_until_done=False): for key, value in zip(dict.keys(),dict.values()): - print('Serial thread: state request: Key: ', key, ' Value: ', value) + # print('Serial thread: state request: Key: ', key, ' Value: ', value) ''' Here, the request handling is done with lots if 'ifs' ''' - print('Key: ', key, ' Value: ', value) + # print('Key: ', key, ' Value: ', value) if key == 'filter': if wait_until_done: self.set_filter(value, wait_until_done) @@ -113,33 +126,51 @@ def state_request_handler(self, dict, wait_until_done=False): self.set_zoom(value) if key == 'stage_program': self.execute_stage_program() + # Log Thread ID during Live: just debugging code + if key == 'state': + if value == 'live': + logger.info('Thread ID during live: '+str(int(QtCore.QThread.currentThreadId()))) + @QtCore.pyqtSlot(dict) def move_relative(self, dict, wait_until_done=False): + # logger.info('Thread ID during relative movement: '+str(int(QtCore.QThread.currentThreadId()))) + + # logger.info('Thread ID during move rel: '+str(int(QtCore.QThread.currentThreadId()))) if wait_until_done: self.stage.move_relative(dict, wait_until_done=True) else: self.stage.move_relative(dict) + @QtCore.pyqtSlot(dict) def move_absolute(self, dict, wait_until_done=False): if wait_until_done: self.stage.move_absolute(dict, wait_until_done=True) else: self.stage.move_absolute(dict) + @QtCore.pyqtSlot(dict) + def report_position(self, dict): + self.sig_position.emit({'position': dict}) + + @QtCore.pyqtSlot() def go_to_rotation_position(self, wait_until_done=False): if wait_until_done: self.stage.go_to_rotation_position(wait_until_done=True) else: self.stage.go_to_rotation_position() + @QtCore.pyqtSlot(str) def set_filter(self, filter, wait_until_done=False): + logger.info('Thread ID during set filter: '+str(int(QtCore.QThread.currentThreadId()))) if wait_until_done: self.filterwheel.set_filter(filter, wait_until_done=True) else: self.filterwheel.set_filter(filter, wait_until_done=False) self.state['filter'] = filter + @QtCore.pyqtSlot(str) def set_zoom(self, zoom, wait_until_done=False): + logger.info('Thread ID during set zoom: '+str(int(QtCore.QThread.currentThreadId()))) if wait_until_done: self.zoom.set_zoom(zoom, wait_until_done=True) else: diff --git a/mesoSPIM/src/mesoSPIM_Stages.py b/mesoSPIM/src/mesoSPIM_Stages.py index e7910f5..cea0ac5 100644 --- a/mesoSPIM/src/mesoSPIM_Stages.py +++ b/mesoSPIM/src/mesoSPIM_Stages.py @@ -4,10 +4,14 @@ ''' import time -from PyQt5 import QtCore +import logging +logger = logging.getLogger(__name__) +from PyQt5 import QtCore from .mesoSPIM_State import mesoSPIM_StateSingleton +# from .mesoSPIM_State import mesoSPIM_StateSingleton + class mesoSPIM_Stage(QtCore.QObject): ''' DemoStage for a mesoSPIM microscope @@ -113,7 +117,9 @@ def __init__(self, parent = None): ''' Debugging code ''' - self.sig_status_message.connect(lambda string, time: print(string)) + # self.sig_status_message.connect(lambda string, time: print(string)) + + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) def create_position_dict(self): self.position_dict = {'x_pos': self.x_pos, @@ -146,6 +152,7 @@ def report_position(self): self.sig_position.emit(self.int_position_dict) + # @QtCore.pyqtSlot(dict) def move_relative(self, dict, wait_until_done=False): ''' Move relative method ''' if 'x_rel' in dict: @@ -186,6 +193,7 @@ def move_relative(self, dict, wait_until_done=False): if wait_until_done == True: time.sleep(0.02) + # @QtCore.pyqtSlot(dict) def move_absolute(self, dict, wait_until_done=False): ''' Move absolute method ''' @@ -232,6 +240,7 @@ def move_absolute(self, dict, wait_until_done=False): if wait_until_done == True: time.sleep(3) + @QtCore.pyqtSlot() def stop(self): self.sig_status_message.emit('Stopped',0) @@ -240,14 +249,14 @@ def zero_axes(self, list): try: exec('self.int_'+axis+'_pos_offset = -self.'+axis+'_pos') except: - print('Zeroing of axis: ', axis, 'failed') + logger.info('Zeroing of axis: ', axis, 'failed') def unzero_axes(self, list): for axis in list: try: exec('self.int_'+axis+'_pos_offset = 0') except: - print('Unzeroing of axis: ', axis, 'failed') + logger.info('Unzeroing of axis: ', axis, 'failed') def load_sample(self): self.y_pos = self.cfg.stage_parameters['y_load_position'] @@ -260,7 +269,7 @@ def mark_rotation_position(self): self.x_rot_position = self.x_pos self.y_rot_position = self.y_pos self.z_rot_position = self.z_pos - print('Marking Rotation Position') + logger.info('Marking new rotation position (absolute coordinates): X: ', self.x_pos, ' Y: ', self.y_pos, ' Z: ', self.z_pos) def go_to_rotation_position(self, wait_until_done=False): ''' Move to the proper rotation position @@ -268,6 +277,7 @@ def go_to_rotation_position(self, wait_until_done=False): Not implemented in the default ''' print('Going to rotation position: NOT IMPLEMENTED / DEMO MODE') + logger.info('Going to rotation position: NOT IMPLEMENTED / DEMO MODE') class mesoSPIM_DemoStage(mesoSPIM_Stage): def __init__(self, parent = None): @@ -324,9 +334,9 @@ def __init__(self, parent = None): # 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('mesoSPIM_Stages: M-406 Emergency referencing hack: Waiting for referencing move') self.block_till_controller_is_ready() - print('M-406 Emergency referencing hack done') + logger.info('mesoSPIM_Stages: M-406 Emergency referencing hack done') # print('Again: Referencing status 3: ', self.pidevice.qFRF(3)) # print('Again: Referencing status 5: ', self.pidevice.qFRF(5)) @@ -338,9 +348,9 @@ def __del__(self): try: '''Close the PI connection''' self.pidevice.unload() - print('Stage disconnected') + logger.info('Stage disconnected') except: - print('Error while disconnecting the PI stage') + logger.info('Error while disconnecting the PI stage') def report_position(self): positions = self.pidevice.qPOS(self.pidevice.axes) @@ -549,19 +559,20 @@ def __init__(self, parent = None): 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() - print('Stages disconnected') + logger.info('Galil stages disconnected') except: - print('Error while disconnecting the Galil stage') + logger.info('Error while disconnecting the Galil stage') def report_position(self): self.x_pos = self.xyz_stage.read_position('x') @@ -613,7 +624,7 @@ def move_relative(self, dict, wait_until_done=False): 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!',1000) + self.sig_status_message.emit('Relative movement stopped: theta Motion limit would be reached!',1000) if 'f_rel' in dict: f_rel = dict['f_rel'] @@ -633,7 +644,7 @@ def move_absolute(self, dict, wait_until_done=False): Lots of implementation details in here, should be replaced by a facade ''' - print(dict) + #print(dict) # if ('x_abs', 'y_abs', 'z_abs', 'f_abs') in dict: x_abs = dict['x_abs'] @@ -685,6 +696,11 @@ class mesoSPIM_PI_f_rot_and_Galil_xyz_Stages(mesoSPIM_Stage): 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 ''' @@ -695,10 +711,8 @@ def __init__(self, parent = None): self.z_encodercounts_per_um = self.cfg.xyz_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.xyz_stage = StageControlGalil(self.cfg.xyz_galil_parameters['port'],[self.x_encodercounts_per_um, + self.y_encodercounts_per_um,self.z_encodercounts_per_um]) ''' self.f_stage = StageControlGalil(COMport = self.cfg.f_galil_parameters['COMport'], x_encodercounts_per_um = 0, @@ -737,13 +751,18 @@ def __init__(self, parent = None): ''' 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)) @@ -754,18 +773,21 @@ def __init__(self, parent = None): def __del__(self): try: '''Close the Galil connection''' - self.xyz_stage.close_stage() + self.xyz_stage.close() self.f_stage.close_stage() - print('Stages disconnected') + logger.info('Galil stages disconnected') except: - print('Error while disconnecting the Galil stage') + logger.info('Error while disconnecting the Galil stages') def report_position(self): positions = self.pidevice.qPOS(self.pidevice.axes) - 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') + ''' + 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'] @@ -780,32 +802,38 @@ def report_position(self): self.create_internal_position_dict() self.sig_position.emit(self.int_position_dict) + #print(self.int_position_dict) def move_relative(self, dict, 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 dict: x_rel = dict['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)) + xyz_motion_dict.update({1:int(x_rel)}) else: self.sig_status_message.emit('Relative movement stopped: X Motion limit would be reached!',1000) if 'y_rel' in dict: y_rel = dict['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)) + xyz_motion_dict.update({2:int(y_rel)}) else: self.sig_status_message.emit('Relative movement stopped: Y Motion limit would be reached!',1000) if 'z_rel' in dict: z_rel = dict['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)) + xyz_motion_dict.update({3:int(z_rel)}) else: self.sig_status_message.emit('Relative movement stopped: z Motion limit would be reached!',1000) + + if xyz_motion_dict != {}: + self.xyz_stage.move_relative(xyz_motion_dict) if 'theta_rel' in dict: theta_rel = dict['theta_rel'] @@ -834,28 +862,27 @@ def move_absolute(self, dict, wait_until_done=False): Lots of implementation details in here, should be replaced by a facade ''' - print(dict) + 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 - else: - x_abs = None + 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 - else: - y_abs = None - + 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 - else: - z_abs = None - - self.xyz_stage.move_absolute(xabs=x_abs,yabs=y_abs,zabs=z_abs) - + 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') @@ -882,17 +909,17 @@ def move_absolute(self, dict, wait_until_done=False): self.pitools.waitontarget(self.pidevice) def stop(self): - self.xyz_stage.stop_all_movements() + self.xyz_stage.stop(restart_programs=True) self.pidevice.STP(noraise=True) def load_sample(self): - self.xyz_stage.move_absolute(xabs=self.int_x_pos, yabs=self.cfg.stage_parameters['y_load_position'], zabs=self.int_z_pos) + 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(xabs=self.int_x_pos, yabs=self.cfg.stage_parameters['y_unload_position'], zabs=self.int_z_pos) + self.xyz_stage.move_absolute({1:self.int_x_pos, 2:self.cfg.stage_parameters['y_unload_position'], 3:self.int_z_pos}) def go_to_rotation_position(self, wait_until_done=False): - self.xyz_stage.move_absolute(xabs=self.x_rot_position, yabs=self.y_rot_position, zabs=self.z_rot_position) + self.xyz_stage.move_absolute({1:self.x_rot_position, 2:self.y_rot_position, 3:self.z_rot_position}) if wait_until_done == True: self.xyz_stage.wait_until_done('XYZ') @@ -910,4 +937,538 @@ def block_till_controller_is_ready(self): def execute_program(self): '''Executes program stored on the Galil controller''' - self.xyz_stage.execute_program() \ No newline at end of file + 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() + sig_mark_rotation_position = 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 src.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') + + ''' Stage 5 close to good focus''' + self.startfocus = self.cfg.stage_parameters['startfocus'] + self.f_stage.move_absolute({3: self.startfocus}) + #self.pidevice.MOV(5,self.startfocus/1000) + + def __del__(self): + try: + '''Close the Galil connection''' + self.xyz_stage.close() + self.f_stage.close_stage() + logger.info('Galil stages disconnected') + except: + logger.info('Error while disconnecting the Galil stages') + + 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.create_position_dict() + + self.theta_pos = positions['1'] + + 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, dict, 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 dict: + x_rel = dict['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!',1000) + + if 'y_rel' in dict: + y_rel = dict['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!',1000) + + if 'z_rel' in dict: + z_rel = dict['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!',1000) + + if xyz_motion_dict != {}: + self.xyz_stage.move_relative(xyz_motion_dict) + + if 'theta_rel' in dict: + theta_rel = dict['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!',1000) + + if 'f_rel' in dict: + f_rel = dict['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!',1000) + + 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!',1000) + + 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!',1000) + + 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 go_to_rotation_position(self, wait_until_done=False): + self.move_absolute({'x_abs':self.x_rot_position, 'y_abs':self.y_rot_position, 'z_abs':self.z_rot_position}) + if wait_until_done == True: + self.xyz_stage.wait_until_done('XYZ') + + 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() + +class mesoSPIM_PI_rotz_and_Galil_xyf_Stages(mesoSPIM_Stage): + ''' + Expects following microscope configuration: + + Sample XYF movement: Galil controller with 3 axes + Z-Movement and Rotation: PI C-884 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() + sig_mark_rotation_position = 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 src.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 = 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) + + #self.pidevice.FRF(1) + 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') + + ''' Stage 5 close to good focus''' + self.startfocus = self.cfg.stage_parameters['startfocus'] + self.xyf_stage.move_absolute({3: self.startfocus}) + #self.pidevice.MOV(5,self.startfocus/1000) + + def __del__(self): + try: + '''Close the Galil connection''' + self.xyf_stage.close() + logger.info('Galil stages disconnected') + except: + logger.info('Error while disconnecting the Galil stages') + + 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.create_position_dict() + + self.z_pos = round(positions['2']*1000,2) + self.theta_pos = positions['1'] + + 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, dict, 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 dict: + x_rel = dict['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!',1000) + + if 'y_rel' in dict: + y_rel = dict['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!',1000) + + if 'z_rel' in dict: + z_rel = dict['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!',1000) + + if 'theta_rel' in dict: + theta_rel = dict['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!',1000) + + if 'f_rel' in dict: + f_rel = dict['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!',1000) + + 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) + + + 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 + + ''' + 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_abs = x_abs - self.int_x_pos_offset + xyf_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 + xyf_motion_dict.update({2:y_abs}) + + if 'f_abs' in dict: + f_abs = dict['f_abs'] + f_abs = f_abs - self.int_f_pos_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_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!',1000) + + 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!',1000) + + if wait_until_done == True: + self.xyf_stage.wait_until_done('XYZ') + self.pitools.waitontarget(self.pidevice) + + def stop(self): + self.xyf_stage.stop(restart_programs=True) + self.pidevice.STP(noraise=True) + + def load_sample(self): + self.xyf_stage.move_absolute({2:self.cfg.stage_parameters['y_load_position']}) + + def unload_sample(self): + self.xyf_stage.move_absolute({2:self.cfg.stage_parameters['y_unload_position']}) + + def go_to_rotation_position(self, wait_until_done=False): + ''' This has to be done in absolute coordinates of the stages to avoid problems with the + internal position offset (when the stage is zeroed). ''' + xy_motion_dict = {1:self.x_rot_position, 2: self.y_rot_position} + self.xyf_stage.move_absolute(xy_motion_dict) + self.pidevice.MOV({2 : self.z_rot_position/1000}) + + if wait_until_done == True: + self.xyf_stage.wait_until_done('XYZ') + self.pitools.waitontarget(self.pidevice) + + 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.xyf_stage.execute_program() \ No newline at end of file diff --git a/mesoSPIM/src/mesoSPIM_State.py b/mesoSPIM/src/mesoSPIM_State.py index 9e8aaeb..9043571 100644 --- a/mesoSPIM/src/mesoSPIM_State.py +++ b/mesoSPIM/src/mesoSPIM_State.py @@ -89,7 +89,11 @@ def __init__(self): 'camera_delay_%' : 10, 'camera_pulse_%' : 1, 'camera_exposure_time':0.02, - 'camera_line_interval':0.000075,} + 'camera_line_interval':0.000075, + 'camera_display_live_subsampling': 1, + 'camera_display_snap_subsampling': 1, + 'camera_display_acquisition_subsampling': 2, + } def __len__(self): return len(self._state_dict) diff --git a/mesoSPIM/src/mesoSPIM_WaveFormGenerator.py b/mesoSPIM/src/mesoSPIM_WaveFormGenerator.py index b04f877..6b286da 100644 --- a/mesoSPIM/src/mesoSPIM_WaveFormGenerator.py +++ b/mesoSPIM/src/mesoSPIM_WaveFormGenerator.py @@ -5,6 +5,9 @@ import numpy as np import csv +import logging +logger = logging.getLogger(__name__) + '''National Instruments Imports''' import nidaqmx from nidaqmx.constants import AcquisitionType, TaskMode @@ -38,6 +41,8 @@ def __init__(self, parent): cfg_file = self.cfg.startup['ETL_cfg_file'] self.state['ETL_cfg_file'] = cfg_file self.update_etl_parameters_from_csv(cfg_file, self.state['laser'], self.state['zoom']) + + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) self.state['galvo_l_amplitude'] = self.cfg.startup['galvo_l_amplitude'] self.state['galvo_r_amplitude'] = self.cfg.startup['galvo_r_amplitude'] @@ -49,7 +54,7 @@ def __init__(self, parent): @QtCore.pyqtSlot(dict) def state_request_handler(self, dict): for key, value in zip(dict.keys(),dict.values()): - print('Waveform Generator: State request: Key: ', key, ' Value: ', value) + # print('Waveform Generator: State request: Key: ', key, ' Value: ', value) ''' The request handling is done ''' @@ -89,19 +94,24 @@ def state_request_handler(self, dict): self.state[key] = value self.sig_update_gui_from_state.emit(False) self.create_waveforms() - print('Waveform change') + # print('Waveform change') elif key in ('ETL_cfg_file'): self.state[key] = value self.update_etl_parameters_from_csv(value, self.state['laser'], self.state['zoom']) - print('ETL CFG File changed') + # print('ETL CFG File changed') elif key in ('zoom'): self.update_etl_parameters_from_zoom(value) - print('zoom change') + #print('zoom change') elif key in ('laser'): self.state[key] = value self.create_waveforms() self.update_etl_parameters_from_laser(value) - print('laser change') + #print('laser change') + + # Log Thread ID during Live: just debugging code + elif key == 'state': + if value == 'live': + logger.info('Thread ID during live: '+str(int(QtCore.QThread.currentThreadId()))) def calculate_samples(self): samplerate, sweeptime = self.state.get_parameter_list(['samplerate','sweeptime']) @@ -276,6 +286,7 @@ def update_etl_parameters_from_csv(self, cfg_path, laser, zoom): self.create_waveforms() + @QtCore.pyqtSlot() def save_etl_parameters_to_csv(self): ''' Saves the current ETL left/right offsets and amplitudes from the values to the ETL csv files diff --git a/mesoSPIM/src/utils/demo_threads.py b/mesoSPIM/src/utils/demo_threads.py new file mode 100644 index 0000000..51d803d --- /dev/null +++ b/mesoSPIM/src/utils/demo_threads.py @@ -0,0 +1,17 @@ +import logging +logger = logging.getLogger(__name__) +import time + +from PyQt5 import QtWidgets, QtCore, QtGui + +class mesoSPIM_DemoThread(QtCore.QObject): + def __init__(self): + super().__init__() + + logger.info('Demo Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) + + @QtCore.pyqtSlot() + def report_thread_id(self): + logger.info('Demo Thread ID while running: '+str(int(QtCore.QThread.currentThreadId()))) + + diff --git a/mesoSPIM/src/utils/demo_threads1.py b/mesoSPIM/src/utils/demo_threads1.py new file mode 100644 index 0000000..4156bc8 --- /dev/null +++ b/mesoSPIM/src/utils/demo_threads1.py @@ -0,0 +1,18 @@ +import logging +logger = logging.getLogger(__name__) +import time + +from PyQt5 import QtWidgets, QtCore, QtGui + +class mesoSPIM_DemoThread(QtCore.QObject): + def __init__(self): + super().__init__() + + logger.info('Thread ID at Startup: '+str(int(QtCore.QThread.currentThreadId()))) + + def report_thread_id(self): + for i in range(0,101): + #time.sleep(0.01) + QtCore.QThread.msleep(10) + logger.info('Thread ID while running: '+str(int(QtCore.QThread.currentThreadId()))) + diff --git a/mesoSPIM/src/utils/models.py b/mesoSPIM/src/utils/models.py index 41834a1..1ad56c2 100644 --- a/mesoSPIM/src/utils/models.py +++ b/mesoSPIM/src/utils/models.py @@ -102,10 +102,10 @@ def setData(self, index, value, role = QtCore.Qt.EditRole): ''' self._table[row][self._table[row].keys()[column]] = value self.dataChanged.emit(index, index) - print('Data changed') + #print('Data changed') return True except: - print('Data NOT changed') + #print('Data NOT changed') return False def setDataFromState(self, row, state_parameter): @@ -188,7 +188,7 @@ def mimeData(self, indices): Here, the model needs to provide a serialization of the entries in a QMimeData object ''' - print('MimeData called') + #print('MimeData called') mimeData = super().mimeData(indices) # print(indices[0].row()) @@ -204,25 +204,27 @@ def dropMimeData(self, data, action, row, col, parent): Always move the entire row, and don't allow column "shifting" ''' - print('MimeData dropped') + #print('MimeData dropped') if action == QtCore.Qt.MoveAction: - print('Row: ', row) - print('Col: ', col) - print('Data:', data.text()) - print('HTML:', data.html()) + pass + #print('Row: ', row) + #print('Col: ', col) + #print('Data:', data.text()) + #print('HTML:', data.html()) # print() # self.insertRow(row) # print('Data methods: ', dir(data)) # return super().dropMimeData(data, action, row, col, parent) if action == QtCore.Qt.CopyAction: - print('Copy action detected.') + #print('Copy action detected.') + pass return True def moveRows(self, source_parent, source_row, count, destination_parent, destination_row): self.rowsAboutToBeMoved.emit(source_parent, source_row, source_row+count-1, destination_parent, destination_row) - print('Moving rows: ', source_row, ' #Rows ', count, ' to destination ', destination_row) + #print('Moving rows: ', source_row, ' #Rows ', count, ' to destination ', destination_row) extracted_list = [] try: @@ -242,7 +244,7 @@ def moveRows(self, source_parent, source_row, count, destination_parent, destina self.send_data_changed() return True except: - print('moveRows failed') + #print('moveRows failed') return False self.rowsMoved.emit(source_parent, source_row, source_row+count-1, destination_parent, destination_row)