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)